2026-02-12 22:49:19 -05:00
|
|
|
use crate::mtterror::{ErrorID, MTTError};
|
2025-12-26 13:02:15 -05:00
|
|
|
use isolang::Language;
|
2026-02-27 08:36:22 -05:00
|
|
|
use std::{
|
|
|
|
|
collections::{HashMap, HashSet},
|
|
|
|
|
fmt,
|
|
|
|
|
};
|
2025-12-26 13:02:15 -05:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub enum NameType {
|
2026-02-27 08:36:22 -05:00
|
|
|
ID(NameID),
|
2025-12-26 13:02:15 -05:00
|
|
|
Name(Name),
|
|
|
|
|
None,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&NameType> for NameType {
|
|
|
|
|
fn from(value: &NameType) -> Self {
|
|
|
|
|
value.clone()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Name> 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 08:36:22 -05:00
|
|
|
impl From<NameID> for NameType {
|
|
|
|
|
fn from(value: NameID) -> Self {
|
2025-12-26 13:02:15 -05:00
|
|
|
Self::ID(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 08:36:22 -05:00
|
|
|
impl From<&NameID> for NameType {
|
|
|
|
|
fn from(value: &NameID) -> Self {
|
2025-12-26 13:02:15 -05:00
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 12:24:20 -05:00
|
|
|
impl fmt::Display for Name {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
write!(f, "{}", self.name)
|
2025-12-26 13:02:15 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 07:12:59 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
|
pub mod test_support {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
pub fn random_name() -> Name {
|
|
|
|
|
Name::english(Uuid::new_v4().to_string().as_str())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 08:36:22 -05:00
|
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub struct NameID {
|
|
|
|
|
data: Uuid,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl NameID {
|
|
|
|
|
fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
data: Uuid::new_v4(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn nil() -> Self {
|
|
|
|
|
Self { data: Uuid::nil() }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
pub mod name_id_support {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
pub fn test_name_id() -> NameID {
|
|
|
|
|
NameID::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod name_ids {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn are_name_ids_random() {
|
|
|
|
|
let id1 = NameID::new();
|
|
|
|
|
let id2 = NameID::new();
|
|
|
|
|
assert_ne!(id1, id2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-26 13:02:15 -05:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
|
pub struct Names {
|
2026-02-27 08:36:22 -05:00
|
|
|
names: HashMap<Name, NameID>,
|
|
|
|
|
ids: HashMap<NameID, HashMap<Language, Name>>,
|
2025-12-26 13:02:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Names {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
names: HashMap::new(),
|
|
|
|
|
ids: HashMap::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 08:36:22 -05:00
|
|
|
pub fn add_names(&mut self, names: Vec<Name>) -> Result<NameID, MTTError> {
|
2025-12-26 13:02:15 -05:00
|
|
|
let mut languages: Vec<&Language> = Vec::new();
|
|
|
|
|
for name in names.iter() {
|
|
|
|
|
let lang = name.get_language();
|
|
|
|
|
if languages.contains(&lang) {
|
2026-02-25 10:16:50 -05:00
|
|
|
let err = MTTError::new(ErrorID::NameLanguageNotUnique);
|
2026-02-12 22:49:19 -05:00
|
|
|
return Err(err);
|
2025-12-26 13:02:15 -05:00
|
|
|
} else {
|
|
|
|
|
languages.push(lang);
|
|
|
|
|
}
|
|
|
|
|
if self.names.contains_key(&name) {
|
2026-02-25 10:16:50 -05:00
|
|
|
let err = MTTError::new(ErrorID::NameAlreadyExists);
|
2026-02-12 22:49:19 -05:00
|
|
|
return Err(err);
|
2025-12-26 13:02:15 -05:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-27 08:36:22 -05:00
|
|
|
let mut id = NameID::new();
|
2025-12-26 13:02:15 -05:00
|
|
|
while self.ids.contains_key(&id) {
|
2026-02-27 08:36:22 -05:00
|
|
|
id = NameID::new();
|
2025-12-26 13:02:15 -05:00
|
|
|
}
|
|
|
|
|
for name in names.iter() {
|
|
|
|
|
self.names.insert(name.clone(), id.clone());
|
|
|
|
|
let mut holder: HashMap<Language, Name> = HashMap::new();
|
|
|
|
|
holder.insert(name.get_language().clone(), name.clone());
|
|
|
|
|
self.ids.insert(id.clone(), holder);
|
|
|
|
|
}
|
|
|
|
|
Ok(id)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 08:36:22 -05:00
|
|
|
pub fn get_id<NT>(&self, name: NT) -> Result<NameID, MTTError>
|
2025-12-26 13:02:15 -05:00
|
|
|
where
|
|
|
|
|
NT: Into<NameType>,
|
|
|
|
|
{
|
2026-02-25 13:44:43 -05:00
|
|
|
let name_type = name.into();
|
|
|
|
|
match name_type.clone() {
|
2025-12-26 13:02:15 -05:00
|
|
|
NameType::Name(data) => match self.names.get(&data) {
|
|
|
|
|
Some(id) => Ok(id.clone()),
|
2026-02-25 13:44:43 -05:00
|
|
|
None => Err(MTTError::new(ErrorID::NameNotFound(name_type))),
|
2025-12-26 13:02:15 -05:00
|
|
|
},
|
|
|
|
|
NameType::ID(data) => {
|
|
|
|
|
if self.ids.contains_key(&data) {
|
|
|
|
|
Ok(data)
|
|
|
|
|
} else {
|
2026-02-27 08:36:22 -05:00
|
|
|
if data == NameID::nil() {
|
2025-12-26 13:02:15 -05:00
|
|
|
Ok(data)
|
|
|
|
|
} else {
|
2026-02-25 13:44:43 -05:00
|
|
|
Err(MTTError::new(ErrorID::NameNotFound(name_type)))
|
2025-12-26 13:02:15 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-27 08:36:22 -05:00
|
|
|
NameType::None => Ok(NameID::nil()),
|
2025-12-26 13:02:15 -05:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-27 08:36:22 -05:00
|
|
|
|
|
|
|
|
pub fn get_ids(&self) -> HashSet<NameID> {
|
|
|
|
|
self.ids.keys().cloned().collect()
|
|
|
|
|
}
|
2025-12-26 13:02:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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);
|
2026-02-27 08:36:22 -05:00
|
|
|
assert_eq!(names.get_id(&id).unwrap(), id);
|
2025-12-26 13:02:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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"];
|
2026-02-27 08:36:22 -05:00
|
|
|
let mut ids: HashSet<NameID> = HashSet::new();
|
2025-12-26 13:02:15 -05:00
|
|
|
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),
|
2026-02-25 10:16:50 -05:00
|
|
|
Err(err) => match err.get_error_ids().back().unwrap() {
|
2026-02-12 22:49:19 -05:00
|
|
|
ErrorID::NameAlreadyExists => {}
|
2025-12-26 13:02:15 -05:00
|
|
|
_ => 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),
|
2026-02-25 10:16:50 -05:00
|
|
|
Err(err) => match err.get_error_ids().back().unwrap() {
|
2026-02-12 22:49:19 -05:00
|
|
|
ErrorID::NameLanguageNotUnique => {}
|
2025-12-26 13:02:15 -05:00
|
|
|
_ => 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());
|
2026-02-25 13:44:43 -05:00
|
|
|
let expected: NameType = name.clone().into();
|
2025-12-26 13:02:15 -05:00
|
|
|
let names = Names::new();
|
|
|
|
|
match names.get_id(name.clone()) {
|
|
|
|
|
Ok(data) => unreachable!("got {:?}, should have been missing error", data),
|
2026-02-25 10:16:50 -05:00
|
|
|
Err(err) => match err.get_error_ids().back().unwrap() {
|
2026-02-25 13:44:43 -05:00
|
|
|
ErrorID::NameNotFound(data) => assert_eq!(data, &expected),
|
2025-12-26 13:02:15 -05:00
|
|
|
_ => unreachable!("got {:?}, should have been missing error", err),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn errors_on_bad_id() {
|
2026-02-27 08:36:22 -05:00
|
|
|
let id = NameID::new();
|
2026-02-25 13:44:43 -05:00
|
|
|
let expected: NameType = id.clone().into();
|
2025-12-26 13:02:15 -05:00
|
|
|
let names = Names::new();
|
|
|
|
|
match names.get_id(id.clone()) {
|
|
|
|
|
Ok(data) => unreachable!("got {:?}, should have been missing error", data),
|
2026-02-25 10:16:50 -05:00
|
|
|
Err(err) => match err.get_error_ids().back().unwrap() {
|
2026-02-25 13:44:43 -05:00
|
|
|
ErrorID::NameNotFound(data) => assert_eq!(data, &expected),
|
2025-12-26 13:02:15 -05:00
|
|
|
_ => unreachable!("got {:?}, should have been missing error", err),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-27 08:36:22 -05:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn able_to_get_name_ids() {
|
|
|
|
|
let count = 5;
|
|
|
|
|
let mut data: Vec<Name> = Vec::new();
|
|
|
|
|
for i in 0..count {
|
|
|
|
|
let name = Name::english(format!("name{}", i).as_str());
|
|
|
|
|
data.push(name);
|
|
|
|
|
}
|
|
|
|
|
let mut names = Names::new();
|
|
|
|
|
for name in data.iter() {
|
|
|
|
|
names.add_names(vec![name.clone()]).unwrap();
|
|
|
|
|
}
|
|
|
|
|
let mut result = names.get_ids();
|
|
|
|
|
assert_eq!(result.len(), count);
|
|
|
|
|
for name in data.iter() {
|
|
|
|
|
let id = names.get_id(name).unwrap();
|
|
|
|
|
assert!(result.remove(&id), "id {:?} was missing", id);
|
|
|
|
|
}
|
|
|
|
|
assert_eq!(result.len(), 0, "still has {:?} ids", result);
|
|
|
|
|
}
|
2025-12-26 13:02:15 -05:00
|
|
|
}
|