diff --git a/src/lib.rs b/src/lib.rs index f3e153e..12c4fac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,14 @@ mod message; mod mtterror; +mod name; use message::{ Action, Addition, CalcValue, Calculation, Clock, CreateDoc, Field, FieldType, Include, Message, - Name, NameType, Operand, Path, Queue, RegMsg, Register, Session, + Operand, Path, Queue, RegMsg, Register, Session, }; pub use message::{MsgAction, Query}; use mtterror::MTTError; +use name::{Name, NameType}; use std::sync::mpsc::{channel, Receiver}; use uuid::Uuid; diff --git a/src/message.rs b/src/message.rs index e5fd82e..88ac6fc 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,5 +1,6 @@ +use super::MTTError; +use crate::name::{Name, NameType, Names}; use chrono::prelude::*; -use isolang::Language; use std::{ collections::{HashMap, HashSet}, ops::{Add, AddAssign}, @@ -11,16 +12,6 @@ use std::{ time::Duration, }; use uuid::Uuid; -use super::MTTError; - -/* -#[cfg(test)] -mod support_test { - use std::time::Duration; - - pub static TIMEOUT: Duration = Duration::from_millis(500); -} -*/ #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum Action { @@ -573,60 +564,11 @@ mod route_ids { } } -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub enum NameType { - ID(Uuid), - Name(Name), - None, -} - -impl From<&NameType> for NameType { - fn from(value: &NameType) -> Self { - value.clone() - } -} - -impl From for NameType { - fn from(value: Name) -> Self { - Self::Name(value) - } -} - -impl From<&Name> for NameType { - fn from(value: &Name) -> Self { - let name = value.clone(); - Self::from(name) - } -} - -impl From for NameType { - fn from(value: Uuid) -> Self { - Self::ID(value) - } -} - -impl From<&Uuid> for NameType { - fn from(value: &Uuid) -> Self { - let id = value.clone(); - Self::from(id) - } -} - -impl ToString for NameType { - fn to_string(&self) -> String { - match self { - Self::ID(data) => data.to_string(), - Self::Name(data) => data.to_string(), - Self::None => "'{None}'".to_string(), - } - } -} - #[derive(Clone, Debug)] pub struct Path { - msg_id: Include, - doc: Include, - action: Include, + pub msg_id: Include, + pub doc: Include, + pub action: Include, } impl Path { @@ -639,439 +581,6 @@ impl Path { } } -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct Name { - name: String, - lang: Language, -} - -impl Name { - fn new(name: &str, lang: Language) -> Self { - Self { - name: name.to_lowercase(), - lang: lang, - } - } - - fn get_language(&self) -> &Language { - &self.lang - } - - pub fn english(name: &str) -> Self { - Self::new(name, Language::from_639_1("en").unwrap()) - } - - #[allow(dead_code)] - fn japanese(name: &str) -> Self { - Self::new(name, Language::from_639_1("ja").unwrap()) - } -} - -impl ToString for Name { - fn to_string(&self) -> String { - self.name.clone() - } -} - -#[derive(Clone, Debug, PartialEq)] -struct Names { - names: HashMap, - ids: HashMap>, -} - -impl Names { - fn new() -> Self { - Self { - names: HashMap::new(), - ids: HashMap::new(), - } - } - - fn add_names(&mut self, names: Vec) -> Result { - for name in names.iter() { - if self.names.contains_key(&name) { - return Err(MTTError::NameDuplicate(name.clone())); - } - } - let mut id = Uuid::new_v4(); - while self.ids.contains_key(&id) { - id = Uuid::new_v4(); - } - for name in names.iter() { - self.names.insert(name.clone(), id.clone()); - let mut holder: HashMap = HashMap::new(); - holder.insert(name.get_language().clone(), name.clone()); - self.ids.insert(id.clone(), holder); - } - Ok(id) - } - - /* - fn add_translation(&mut self, name: Name, translation: Name) -> Result { - let id = match self.get_id(&name) { - Ok(data) => data.clone(), - Err(err) => return Err(err), - }; - match self.get_id(&translation) { - Ok(_) => return Err(MTTError::NameDuplicate(translation)), - Err(_) => {} - } - let holder = self.ids.get_mut(&id).unwrap(); - holder.insert(translation.get_language().clone(), translation.clone()); - self.names.insert(translation, id); - Ok(id.clone()) - } - - fn get_name(&self, id: &Uuid, lang: &Language) -> Result { - match self.ids.get(id) { - Some(langdb) => match langdb.get(lang) { - Some(name) => Ok(name.clone()), - None => Err(MTTError::NameMissingTranslation(lang.clone())), - }, - None => Err(MTTError::NameInvalidID(id.clone())), - } - } - */ - - fn get_id(&self, name: NT) -> Result - where - NT: Into, - { - match name.into() { - NameType::Name(data) => match self.names.get(&data) { - Some(id) => Ok(id.clone()), - None => Err(MTTError::NameNotFound(data.clone())), - }, - NameType::ID(data) => { - if self.ids.contains_key(&data) { - Ok(data) - } else { - if data == Uuid::nil() { - Ok(data) - } else { - Err(MTTError::NameNotFound(Name::english( - data.to_string().as_str(), - ))) - } - } - } - NameType::None => Ok(Uuid::nil()), - } - } - - fn path_to_route(&self, path: &Path) -> Result { - let doc_id = match &path.doc { - Include::Just(id_info) => match id_info { - NameType::ID(id) => { - if self.ids.contains_key(&id) { - Include::Just(id.clone()) - } else { - return Err(MTTError::NameInvalidID(id.clone())); - } - } - NameType::Name(name) => { - let id = match self.get_id(name) { - Ok(data) => data, - Err(err) => return Err(err), - }; - Include::Just(id.clone()) - } - NameType::None => Include::Just(Uuid::nil()), - }, - Include::All => Include::All, - }; - Ok(Route::new(path.msg_id.clone(), doc_id, path.action.clone())) - } - - /* - fn is_empty(&self) -> bool { - self.names.is_empty() - } - */ -} - -#[cfg(test)] -mod names { - use super::*; - - #[test] - fn are_names_lowercase() { - let name1 = Name::new("session", Language::from_639_1("en").unwrap()); - let name2 = Name::new("Session", Language::from_639_1("en").unwrap()); - let name3 = Name::english("SESSION"); - assert_eq!(name1, name2); - assert_eq!(name1, name3); - } - - #[test] - fn are_name_ids_unique() { - let mut names = Names::new(); - let data = ["one", "two", "three", "four", "five"]; - let mut ids: HashSet = HashSet::new(); - for item in data.iter() { - let name = Name::english(item); - ids.insert(names.add_names([name].to_vec()).unwrap()); - } - assert_eq!(ids.len(), data.len()); - } - - /* - #[test] - fn does_id_return_name() { - let mut names = Names::new(); - let data = ["one", "two"]; - let mut ids: HashMap = HashMap::new(); - for item in data.iter() { - let name = Name::english(item.clone()); - ids.insert(name.clone(), names.add_names([name].to_vec()).unwrap()); - } - for (name, id) in ids.iter() { - assert_eq!( - &names - .get_name(id, &Language::from_639_1("en").unwrap()) - .unwrap(), - name - ); - assert_eq!(&names.get_id(name).unwrap(), id); - } - } - */ - - #[test] - fn errors_on_name_not_found() { - let names = Names::new(); - let name = Name::english("missing"); - let result = names.get_id(&name); - match result { - Ok(_) => unreachable!("got {:?}, should have been error", result), - Err(err) => match err { - MTTError::NameNotFound(output) => assert_eq!(output, name), - _ => unreachable!("got {:?}, should have been name not found", err), - }, - } - } - - /* - #[test] - fn errors_on_bad_id() { - let mut names = Names::new(); - let id = Uuid::new_v4(); - let result = names.get_name(&id, &Language::from_639_1("en").unwrap()); - match result { - Ok(_) => unreachable!("got {:?}, should be invalid id error", result), - Err(err) => match err { - MTTError::NameInvalidID(data) => assert_eq!(data, id), - _ => unreachable!("got {:?}, should have been invalid id", err), - }, - } - } - - #[test] - fn errors_on_missing_translation() { - let mut names = Names::new(); - let name = Name::english("task"); - let lang = Language::from_639_1("ja").unwrap(); - let id = names.add_names([name].to_vec()).unwrap(); - let result = names.get_name(&id, &lang); - match result { - Ok(_) => unreachable!("got {:?}, should be invalid id error", result), - Err(err) => match err { - MTTError::NameMissingTranslation(data) => assert_eq!(data, lang), - _ => unreachable!("got {:?}, should have been invalid id", err), - }, - } - } - */ - - #[test] - fn errors_on_duplicate_names() { - let mut names = Names::new(); - let data = "test"; - let name = Name::english(data); - names.add_names([name.clone()].to_vec()).unwrap(); - let output = names.add_names([name.clone()].to_vec()); - match output { - Ok(_) => unreachable!( - "got {:?}, should have produced duplicate name error", - output - ), - Err(err) => match err { - MTTError::NameDuplicate(result) => assert_eq!(result, name), - _ => unreachable!("got {:?}, should have been duplicate name", err), - }, - } - } - - /* - #[test] - fn allows_alternate_names() { - let mut names = Names::new(); - let data = "test"; - let alt = "テスト"; - let english = Name::english(data); - let japanese = Name::japanese(alt); - let id = names.add_names([english.clone()].to_vec()).unwrap(); - let result = names.add_translation(english, japanese.clone()).unwrap(); - assert_eq!(result, id); - let output = names.get_name(&id, &Language::from_639_1("ja").unwrap()); - assert_eq!(output.unwrap().to_string(), alt); - assert_eq!(names.get_id(&japanese).unwrap(), id); - } - - #[test] - fn errors_on_bad_translation() { - let mut names = Names::new(); - let data = "test"; - let alt = "テスト"; - let english = Name::english(data); - let japanese = Name::japanese(alt); - let result = names.add_translation(japanese.clone(), english); - match result { - Ok(_) => unreachable!("got {:?}, should be invalid id error", result), - Err(err) => match err { - MTTError::NameNotFound(output) => assert_eq!(output, japanese), - _ => unreachable!("got {:?}, should have been invalid id", err), - }, - } - } - - #[test] - fn errors_on_translation_duplicates() { - let mut names = Names::new(); - let data = "test"; - let alt = "テスト"; - let english = Name::english(data); - let japanese = Name::japanese(alt); - let id = names.add_names([english.clone()].to_vec()).unwrap(); - let id = names.add_names([japanese.clone()].to_vec()).unwrap(); - let result = names.add_translation(english, japanese.clone()); - match result { - Ok(_) => unreachable!( - "got {:?}, should have produced duplicate name error", - result - ), - Err(err) => match err { - MTTError::NameDuplicate(result) => assert_eq!(result, japanese), - _ => unreachable!("got {:?}, should have been duplicate name", err), - }, - } - } - */ - - #[test] - fn convert_path_to_route_with_ids() { - let mut names = Names::new(); - let data = "data"; - let english = Name::english(data); - let id = names.add_names([english.clone()].to_vec()).unwrap(); - let msg_id = Uuid::new_v4(); - let action = Action::Query; - let path = Path::new( - Include::Just(msg_id.clone()), - Include::Just(id.into()), - Include::Just(action.clone()), - ); - let result = names.path_to_route(&path).unwrap(); - assert_eq!(result.msg_id, Include::Just(msg_id)); - assert_eq!(result.doc_type, Include::Just(id)); - assert_eq!(result.action, Include::Just(action)); - } - - #[test] - fn convert_path_name_to_route() { - let mut names = Names::new(); - let data = "data"; - let english = Name::english(data); - let id = names.add_names([english.clone()].to_vec()).unwrap(); - let msg_id = Uuid::new_v4(); - let action = Action::Error; - let path = Path::new( - Include::Just(msg_id.clone()), - Include::Just(english.into()), - Include::Just(action.clone()), - ); - let result = names.path_to_route(&path).unwrap(); - assert_eq!(result.msg_id, Include::Just(msg_id)); - assert_eq!(result.doc_type, Include::Just(id)); - assert_eq!(result.action, Include::Just(action)); - } - - #[test] - fn convert_path_with_no_document_to_route() { - let names = Names::new(); - let msg_id = Uuid::new_v4(); - let action = Action::Show; - let path = Path::new( - Include::Just(msg_id.clone()), - Include::Just(NameType::None), - Include::Just(action.clone()), - ); - let result = names.path_to_route(&path).unwrap(); - assert_eq!(result.msg_id, Include::Just(msg_id)); - assert_eq!(result.doc_type, Include::Just(Uuid::nil())); - assert_eq!(result.action, Include::Just(action)); - } - - #[test] - fn convert_path_to_route_all_documents() { - let names = Names::new(); - let msg_id = Uuid::new_v4(); - let action = Action::Query; - let path = Path::new( - Include::Just(msg_id.clone()), - Include::All, - Include::Just(action.clone()), - ); - let result = names.path_to_route(&path).unwrap(); - assert_eq!(result.msg_id, Include::Just(msg_id)); - match result.doc_type { - Include::All => {} - Include::Just(_) => unreachable!("should return all"), - } - assert_eq!(result.action, Include::Just(action)); - } - - #[test] - fn convert_path_with_bad_id() { - let names = Names::new(); - let msg_id = Uuid::new_v4(); - let id = Uuid::new_v4(); - let action = Action::Query; - let path = Path::new( - Include::Just(msg_id.clone()), - Include::Just(id.into()), - Include::Just(action.clone()), - ); - match names.path_to_route(&path) { - Ok(data) => unreachable!("got {:?}, should have been an error", data), - Err(err) => match err { - MTTError::NameInvalidID(output) => assert_eq!(output, id), - _ => unreachable!("got {:?}, should have gotten invalid id", err), - }, - } - } - - #[test] - fn convert_path_with_bad_name() { - let names = Names::new(); - let msg_id = Uuid::new_v4(); - let name = Name::english("wrong"); - let action = Action::Query; - let path = Path::new( - Include::Just(msg_id.clone()), - Include::Just(name.clone().into()), - Include::Just(action.clone()), - ); - match names.path_to_route(&path) { - Ok(data) => unreachable!("got {:?}, should have been an error", data), - Err(err) => match err { - MTTError::NameNotFound(output) => assert_eq!(output, name), - _ => unreachable!("got {:?}, should have gotten invalid id", err), - }, - } - } -} - #[derive(Clone, Debug)] pub enum RegMsg { AddRoute(Path), @@ -1115,14 +624,14 @@ impl Register { } #[derive(Clone, Debug, PartialEq)] -struct Route { +pub struct Route { action: Include, doc_type: Include, msg_id: Include, } impl Route { - fn new(msg_id: Include, doc: Include, action: Include) -> Self { + pub fn new(msg_id: Include, doc: Include, action: Include) -> Self { Self { action: action, doc_type: doc, @@ -1571,8 +1080,8 @@ impl Router { #[cfg(test)] mod routers { - use crate::support_tests::TIMEOUT; use super::*; + use crate::support_tests::TIMEOUT; #[test] fn can_pass_message() { @@ -1681,8 +1190,8 @@ impl Queue { #[cfg(test)] mod queues { - use crate::support_tests::TIMEOUT; use super::*; + use crate::support_tests::TIMEOUT; use std::sync::mpsc::RecvTimeoutError; struct TestQueue { @@ -5244,8 +4753,8 @@ impl DocumentFile { #[cfg(test)] mod document_files { - use crate::support_tests::TIMEOUT; use super::*; + use crate::support_tests::TIMEOUT; use std::{sync::mpsc::RecvTimeoutError, thread::sleep}; fn standard_paths() -> Vec { @@ -6925,8 +6434,8 @@ mod document_files { #[cfg(test)] mod createdocs { - use crate::support_tests::TIMEOUT; use super::*; + use crate::support_tests::TIMEOUT; struct TestCreateDoc { queue: Queue, @@ -7333,8 +6842,8 @@ impl MessageLog { #[cfg(test)] mod message_logs { - use crate::support_tests::TIMEOUT; use super::*; + use crate::support_tests::TIMEOUT; #[test] fn does_log_store_messages() { @@ -7458,8 +6967,8 @@ impl Session { #[cfg(test)] mod sessions { - use crate::support_tests::TIMEOUT; use super::*; + use crate::support_tests::TIMEOUT; use std::{sync::mpsc::RecvTimeoutError, thread::sleep}; struct Setup { diff --git a/src/mtterror.rs b/src/mtterror.rs index 16d413d..0e4bab7 100644 --- a/src/mtterror.rs +++ b/src/mtterror.rs @@ -1,5 +1,6 @@ +use super::message::{Field, FieldType}; +use crate::name::Name; use isolang::Language; -use super::message::{Field, FieldType, Name}; use uuid::Uuid; #[derive(Clone, Debug)] @@ -20,6 +21,7 @@ pub enum MTTError { NameDuplicate(Name), NameInvalidID(Uuid), NameMissingTranslation(Language), + NameNotUniquePerLanguage(Name), NameNotFound(Name), QueryCannotChangeData, RouteRequiresDocumentID, diff --git a/src/name.rs b/src/name.rs new file mode 100644 index 0000000..501b1d4 --- /dev/null +++ b/src/name.rs @@ -0,0 +1,272 @@ +use crate::{ + message::{Include, Path, Route}, + mtterror::MTTError, +}; +use isolang::Language; +use std::collections::HashMap; +use uuid::Uuid; + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum NameType { + ID(Uuid), + Name(Name), + None, +} + +impl From<&NameType> for NameType { + fn from(value: &NameType) -> Self { + value.clone() + } +} + +impl From for NameType { + fn from(value: Name) -> Self { + Self::Name(value) + } +} + +impl From<&Name> for NameType { + fn from(value: &Name) -> Self { + let name = value.clone(); + Self::from(name) + } +} + +impl From for NameType { + fn from(value: Uuid) -> Self { + Self::ID(value) + } +} + +impl From<&Uuid> for NameType { + fn from(value: &Uuid) -> Self { + let id = value.clone(); + Self::from(id) + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Name { + name: String, + lang: Language, +} + +impl Name { + fn new(name: &str, lang: Language) -> Self { + Self { + name: name.to_lowercase(), + lang: lang, + } + } + + fn get_language(&self) -> &Language { + &self.lang + } + + pub fn english(name: &str) -> Self { + Self::new(name, Language::from_639_1("en").unwrap()) + } + + pub fn japanese(name: &str) -> Self { + Self::new(name, Language::from_639_1("ja").unwrap()) + } +} + +impl ToString for Name { + fn to_string(&self) -> String { + self.name.clone() + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Names { + names: HashMap, + ids: HashMap>, +} + +impl Names { + pub fn new() -> Self { + Self { + names: HashMap::new(), + ids: HashMap::new(), + } + } + + pub fn add_names(&mut self, names: Vec) -> Result { + let mut languages: Vec<&Language> = Vec::new(); + for name in names.iter() { + let lang = name.get_language(); + if languages.contains(&lang) { + return Err(MTTError::NameNotUniquePerLanguage(name.clone())); + } else { + languages.push(lang); + } + if self.names.contains_key(&name) { + return Err(MTTError::NameDuplicate(name.clone())); + } + } + let mut id = Uuid::new_v4(); + while self.ids.contains_key(&id) { + id = Uuid::new_v4(); + } + for name in names.iter() { + self.names.insert(name.clone(), id.clone()); + let mut holder: HashMap = HashMap::new(); + holder.insert(name.get_language().clone(), name.clone()); + self.ids.insert(id.clone(), holder); + } + Ok(id) + } + + pub fn get_id(&self, name: NT) -> Result + where + NT: Into, + { + match name.into() { + NameType::Name(data) => match self.names.get(&data) { + Some(id) => Ok(id.clone()), + None => Err(MTTError::NameNotFound(data.clone())), + }, + NameType::ID(data) => { + if self.ids.contains_key(&data) { + Ok(data) + } else { + if data == Uuid::nil() { + Ok(data) + } else { + Err(MTTError::NameInvalidID(data)) + } + } + } + NameType::None => Ok(Uuid::nil()), + } + } + + pub fn path_to_route(&self, path: &Path) -> Result { + let doc_id = match &path.doc { + Include::Just(id_info) => match id_info { + NameType::ID(id) => { + if self.ids.contains_key(&id) { + Include::Just(id.clone()) + } else { + return Err(MTTError::NameInvalidID(id.clone())); + } + } + NameType::Name(name) => { + let id = match self.get_id(name) { + Ok(data) => data, + Err(err) => return Err(err), + }; + Include::Just(id.clone()) + } + NameType::None => Include::Just(Uuid::nil()), + }, + Include::All => Include::All, + }; + Ok(Route::new(path.msg_id.clone(), doc_id, path.action.clone())) + } +} + +#[cfg(test)] +mod names { + use super::*; + use std::collections::HashSet; + + #[test] + fn are_names_lowercase() { + let name1 = Name::new("session", Language::from_639_1("en").unwrap()); + let name2 = Name::new("Session", Language::from_639_1("en").unwrap()); + let name3 = Name::english("SESSION"); + assert_eq!(name1.to_string(), "session".to_string()); + assert_eq!(name1, name2); + assert_eq!(name1, name3); + } + + #[test] + fn does_new_id_match_retrieval_id() { + let name = Name::english("tester"); + let mut names = Names::new(); + let id = names.add_names(vec![name.clone()]).unwrap(); + assert_eq!(names.get_id(name).unwrap(), id); + assert_eq!(names.get_id(id).unwrap(), id); + } + + #[test] + fn multiple_languages_can_stored_on_same_id() { + let english = Name::english("tester"); + let japanese = Name::japanese("テスト"); + let mut names = Names::new(); + let id = names + .add_names(vec![english.clone(), japanese.clone()]) + .unwrap(); + assert_eq!(names.get_id(english).unwrap(), id); + assert_eq!(names.get_id(japanese).unwrap(), id); + } + + #[test] + fn are_name_ids_unique() { + let mut names = Names::new(); + let data = ["one", "two", "three", "four", "five"]; + let mut ids: HashSet = HashSet::new(); + for item in data.iter() { + let name = Name::english(item); + ids.insert(names.add_names([name].to_vec()).unwrap()); + } + assert_eq!(ids.len(), data.len()); + } + + #[test] + fn errors_on_duplicates() { + let name = Name::english("duplicate"); + let mut names = Names::new(); + names.add_names(vec![name.clone()]).unwrap(); + match names.add_names(vec![name.clone()]) { + Ok(data) => unreachable!("got {:?}, should have been duplicate error", data), + Err(err) => match err { + MTTError::NameDuplicate(result) => assert_eq!(result, name), + _ => unreachable!("got {:?}, should have been duplicate error", err), + }, + } + } + + #[test] + fn errors_if_same_language_is_used() { + let name1 = Name::english("test"); + let name2 = Name::japanese("テスト"); + let name3 = Name::english("tester"); + let mut names = Names::new(); + match names.add_names(vec![name1, name2, name3.clone()]) { + Ok(data) => unreachable!("got {:?}, should have been needs to be unique", data), + Err(err) => match err { + MTTError::NameNotUniquePerLanguage(data) => assert_eq!(data, name3), + _ => unreachable!("got {:?}, should have been name needs to be unique", err), + }, + } + } + + #[test] + fn errors_on_bad_name() { + let name = Name::english(Uuid::new_v4().to_string().as_str()); + let names = Names::new(); + match names.get_id(name.clone()) { + Ok(data) => unreachable!("got {:?}, should have been missing error", data), + Err(err) => match err { + MTTError::NameNotFound(result) => assert_eq!(result, name), + _ => unreachable!("got {:?}, should have been missing error", err), + }, + } + } + + #[test] + fn errors_on_bad_id() { + let id = Uuid::new_v4(); + let names = Names::new(); + match names.get_id(id.clone()) { + Ok(data) => unreachable!("got {:?}, should have been missing error", data), + Err(err) => match err { + MTTError::NameInvalidID(result) => assert_eq!(result, id), + _ => unreachable!("got {:?}, should have been missing error", err), + }, + } + } +}