mod client; mod data; mod error; mod message; mod queue; 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, Receiver, Sender}, }; use uuid::Uuid; #[derive(Clone)] enum Field { Static(String), Uuid(Uuid), } impl From for Field { fn from(value: String) -> Self { match Uuid::try_from(value.as_str()) { Ok(data) => return Field::Uuid(data), Err(_) => {} } Field::Static(value) } } impl From<&str> for Field { fn from(value: &str) -> Self { match Uuid::try_from(value) { Ok(data) => return Field::Uuid(data), Err(_) => {} } Field::Static(value.into()) } } impl From for Field { fn from(value: Uuid) -> Self { Field::Uuid(value) } } #[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), _ => unreachable!("shouuld have been a static field"), } } } #[test] fn str_to_field() { let entries = ["test1", "test2"]; for data in entries { match data.into() { Field::Static(result) => assert_eq!(result, data), _ => unreachable!("shouuld have been a static field"), } } } #[test] fn uuid_to_field() { let id = Uuid::new_v4(); match id.into() { Field::Uuid(result) => assert_eq!(result, id), _ => unreachable!("should have been a uuid field"), } } #[test] fn uuid_string_to_field() { let id = Uuid::new_v4(); let id_string = id.to_string(); match id_string.into() { Field::Uuid(result) => assert_eq!(result, id), _ => unreachable!("should have been a uuid field"), } } #[test] fn uuid_str_to_field() { let id = Uuid::new_v4(); let id_string = id.to_string(); let id_str = id_string.as_str(); match id_str.into() { Field::Uuid(result) => assert_eq!(result, id), _ => unreachable!("should have been a uuid field"), } } } #[derive(Clone)] struct Request { id: Option, tx: Sender, } impl Request { fn new(id: Option) -> (Self, Receiver) where F: Into, { let result: Option; match id { Some(data) => { result = Some(data.into()); } None => result = None, } let (tx, rx) = channel(); (Self { id: result, tx: tx }, rx) } fn get_session(&self) -> &Option { return &self.id; } } #[cfg(test)] mod create_request { use super::*; pub fn empty_request() -> (Request, Receiver) { let id: Option = None; Request::new(id) } } #[cfg(test)] mod requests { use super::*; #[test] fn create_request_no_id() { let input: Option = None; let (req, _) = Request::new(input); assert!(req.id.is_none()); } #[test] fn create_request_with_uuid() { let id = Uuid::new_v4(); let (req, _) = Request::new(Some(id)); match req.id { Some(field) => match (field) { Field::Uuid(data) => assert_eq!(data, id), _ => unreachable!("Should have been a uuid"), }, None => unreachable!("Should producer data"), } } #[test] fn return_session() { let id = Uuid::new_v4(); let (req, _) = Request::new(Some(id)); match req.get_session() { Some(result) => { match result { Field::Uuid(data) => assert_eq!(data, &id), _ => unreachable!("should have returned a uuid field"), }; } None => unreachable!("should have returned a uuid"), } } } 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), _ => unreachable!("Should have been static"), }, } } #[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), _ => unreachable!("should have been statis"), }, } } } #[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()) } _ => unreachable!("should have been static"), } } } 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()); } }