use crate::mtterror::{ErrorID, MTTError}; use isolang::Language; use std::{collections::HashMap, fmt}; 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 fmt::Display for Name { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.name) } } #[cfg(test)] pub mod test_support { use super::*; pub fn random_name() -> Name { Name::english(Uuid::new_v4().to_string().as_str()) } } #[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) { let err = MTTError::new(ErrorID::NameLanguageNotUnique); return Err(err); } else { languages.push(lang); } if self.names.contains_key(&name) { let err = MTTError::new(ErrorID::NameAlreadyExists); return Err(err); } } 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, { let name_type = name.into(); match name_type.clone() { NameType::Name(data) => match self.names.get(&data) { Some(id) => Ok(id.clone()), None => Err(MTTError::new(ErrorID::NameNotFound(name_type))), }, NameType::ID(data) => { if self.ids.contains_key(&data) { Ok(data) } else { if data == Uuid::nil() { Ok(data) } else { Err(MTTError::new(ErrorID::NameNotFound(name_type))) } } } NameType::None => Ok(Uuid::nil()), } } } #[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.get_error_ids().back().unwrap() { ErrorID::NameAlreadyExists => {} _ => 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.get_error_ids().back().unwrap() { ErrorID::NameLanguageNotUnique => {} _ => 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 expected: NameType = name.clone().into(); let names = Names::new(); match names.get_id(name.clone()) { Ok(data) => unreachable!("got {:?}, should have been missing error", data), Err(err) => match err.get_error_ids().back().unwrap() { ErrorID::NameNotFound(data) => assert_eq!(data, &expected), _ => unreachable!("got {:?}, should have been missing error", err), }, } } #[test] fn errors_on_bad_id() { let id = Uuid::new_v4(); let expected: NameType = id.clone().into(); let names = Names::new(); match names.get_id(id.clone()) { Ok(data) => unreachable!("got {:?}, should have been missing error", data), Err(err) => match err.get_error_ids().back().unwrap() { ErrorID::NameNotFound(data) => assert_eq!(data, &expected), _ => unreachable!("got {:?}, should have been missing error", err), }, } } }