mod client; mod data; mod error; mod message; mod router; mod session; use client::{Client, ClientMsg}; use message::{Message, MsgData}; use router::Router; use session::{Session, SessionData, SessionMsg}; use std::{ collections::HashMap, ops::Deref, sync::mpsc::{channel, Sender}, }; struct Request; #[derive(Clone)] enum Field { Static(String), } impl From for Field { fn from(value: String) -> Self { Field::Static(value) } } impl From<&str> for Field { fn from(value: &str) -> Self { Field::Static(value.into()) } } #[cfg(test)] mod fields { use super::*; #[test] fn string_to_field() { let entries = ["test1".to_string(), "test2".to_string()]; for data in entries { match data.clone().into() { Field::Static(result) => assert_eq!(result, data), } } } #[test] fn str_to_field() { let entries = ["test1", "test2"]; for data in entries { match data.into() { Field::Static(result) => assert_eq!(result, data), } } } } struct Record { data: HashMap, } impl Record { fn new() -> Self { Self { data: HashMap::new(), } } fn add(&mut self, name: S, data: F) where S: Into, F: Into, { self.data.insert(name.into(), data.into()); } } impl Deref for Record { type Target = HashMap; fn deref(&self) -> &Self::Target { &self.data } } #[cfg(test)] mod records { use super::*; #[test] fn initialize() { let rec = Record::new(); assert!(rec.is_empty()); } #[test] fn bad_get_return() { let rec = Record::new(); match rec.get("empty") { Some(_) => unreachable!("Should_have returned a None"), None => {} } } #[test] fn add_data() { let name = "name1x"; let data = "data1"; let mut rec = Record::new(); rec.add(name, data); match rec.get(name) { None => unreachable!("Should return data"), Some(result) => match result { Field::Static(txt) => assert_eq!(txt, data), }, } } #[test] fn add_data_strings() { let name = "field".to_string(); let data = "info".to_string(); let mut rec = Record::new(); rec.add(name.clone(), data.clone()); match rec.get(&name) { None => unreachable!("Should return data"), Some(result) => match result { Field::Static(txt) => assert_eq!(txt, &data), }, } } } #[derive(Debug)] enum ResponseError { ColumnNumberMisMatch, MissionColumn(String), } struct Response { data: HashMap>, counter: usize, } impl Response { fn new() -> Self { Self { data: HashMap::new(), counter: 0, } } fn add(&mut self, rec: Record) -> Result<(), ResponseError> { if self.data.is_empty() { for (key, value) in rec.iter() { let mut store = Vec::new(); store.push(value.clone()); self.data.insert(key.to_string(), store); } } else { if rec.len() != self.data.len() { return Err(ResponseError::ColumnNumberMisMatch); } for (key, value) in rec.iter() { match self.data.get_mut(key) { Some(data) => data.push(value.clone()), None => return Err(ResponseError::MissionColumn(key.to_string())), } } } Ok(()) } } impl Iterator for Response { type Item = Record; fn next(&mut self) -> Option { if self.data.is_empty() { return None; } let mut rec = Record::new(); for (key, value) in self.data.iter() { if self.counter >= value.len() { return None; } rec.add(key, value[self.counter].clone()); } self.counter += 1; Some(rec) } } #[cfg(test)] mod responses { use super::*; #[test] fn create_response() { let mut res = Response::new(); assert!(res.data.is_empty()); assert!(res.next().is_none()); } #[test] fn add_records() { let mut res = Response::new(); let columns = ["col1", "col2"]; let count = 3; let field_cnt = &count * columns.len(); let mut fields = 0..field_cnt; for _ in 0..count { let mut rec = Record::new(); for col in columns { rec.add(col, fields.next().unwrap().to_string()); } res.add(rec); } fields = 0..field_cnt; for _ in 0..count { match res.next() { Some(rec) => { assert_eq!(rec.len(), columns.len()); for col in columns { match rec.get(col).unwrap() { Field::Static(txt) => { assert_eq!(txt.clone(), fields.next().unwrap().to_string()) } } } } None => unreachable!("Should have returned data"), } } assert!(res.next().is_none(), "exceeded the correct of records"); } #[test] fn number_of_columns_mismatch() { let mut res = Response::new(); let mut rec1 = Record::new(); let mut rec2 = Record::new(); rec1.add("one", "one"); rec2.add("one", "one"); rec2.add("two", "two"); res.add(rec1).unwrap(); match res.add(rec2) { Ok(_) => unreachable!("Should not accept additional value"), Err(err) => match err { ResponseError::ColumnNumberMisMatch => {} _ => unreachable!("should havee been a mismatch error"), }, } } #[test] fn missing_field_error() { let mut res = Response::new(); let mut rec1 = Record::new(); let mut rec2 = Record::new(); rec1.add("one", "one"); rec2.add("one", "one"); rec2.add("two", "two"); res.add(rec2).unwrap(); match res.add(rec1) { Ok(_) => unreachable!("Should not accept additional value"), Err(err) => match err { ResponseError::ColumnNumberMisMatch => {} _ => unreachable!("should havee been a mismatch error"), }, } } #[test] fn wrong_column_name() { let mut res = Response::new(); let mut rec1 = Record::new(); let mut rec2 = Record::new(); rec1.add("one", "one"); rec2.add("two", "two"); res.add(rec1).unwrap(); match res.add(rec2) { Ok(_) => unreachable!("Should not accept additional value"), Err(err) => match err { ResponseError::MissionColumn(txt) => assert_eq!(txt, "two"), _ => unreachable!("should have been missing cloumn"), }, } } } /// Support functions for Messages. pub trait Msg { fn to_msgdata(&self) -> MsgData; } #[cfg(test)] mod test_message { use super::*; pub enum Tester { Test1, Test2, } impl Msg for Tester { fn to_msgdata(&self) -> MsgData { match self { Tester::Test1 => MsgData::Test1, Tester::Test2 => MsgData::Test2, } } } } /// Application client to MoreThanText pub struct MoreThanText { session: Option, tx: Sender, } impl MoreThanText { /// Create a MoreThanText database. /// /// Example: /// /// ``` /// use morethantext::MoreThanText; /// /// MoreThanText::new(); /// ``` pub fn new() -> Self { let (tx, rx) = channel(); let mut senders = Vec::new(); senders.push(Client::start(tx.clone())); senders.push(Session::start(tx.clone())); Router::start(senders, rx); Self { session: None, tx: tx, } } /// Opens an existing or new session with the database. /// /// If the string is None, incorrect, or expired, /// a new session will be created. /// /// Example: /// /// ``` /// use morethantext::MoreThanText; /// /// let mut mtt = MoreThanText::new(); /// mtt.open_session(None); /// mtt.open_session(Some("7b1ff340-7dfa-4f29-b144-601384e54423".to_string())); /// ``` pub fn open_session(&mut self, id: Option) { let (tx, rx) = channel(); let req = ClientMsg::OpenSession(id, tx); let msg = Message::new(&req); self.tx.send(msg).unwrap(); match rx.recv().unwrap().get_message() { MsgData::Session(data) => match data { SessionMsg::Opened(sess) => self.session = Some(sess.clone()), _ => {} }, _ => {} }; } /// Get the session id /// /// Example: /// /// ``` /// use morethantext::MoreThanText; /// /// let mut mtt = MoreThanText::new(); /// let id = mtt.get_id(); /// ``` pub fn get_id(&self) -> String { match &self.session { Some(id) => id.to_string(), None => "".to_string(), } } } impl Clone for MoreThanText { fn clone(&self) -> Self { Self { session: None, tx: self.tx.clone(), } } } #[cfg(test)] mod mtt_client { use super::*; #[test] fn default_values() { let mtt = MoreThanText::new(); assert!(mtt.session.is_none()); } #[test] fn new_session() { let mut mtt = MoreThanText::new(); mtt.open_session(None); assert!(mtt.session.is_some()); } #[test] fn session_ids_are_unique() { let mut mtt = MoreThanText::new(); mtt.open_session(None); let id1 = mtt.get_id(); mtt.open_session(None); assert_ne!(mtt.get_id(), id1); } #[test] fn cloned_clients_have_no_session() { let mut mtt = MoreThanText::new(); mtt.open_session(None); let result = mtt.clone(); assert!(result.session.is_none()); } }