diff --git a/src/data/id.rs b/src/data/id.rs index 160dfea..145e060 100644 --- a/src/data/id.rs +++ b/src/data/id.rs @@ -1,4 +1,7 @@ -use crate::data::record::{Field, FieldRecord}; +use crate::{ + data::record::Field, + error::{ErrorType, MTTError}, +}; use std::fmt; use uuid::Uuid; @@ -8,20 +11,33 @@ pub struct ID { } impl ID { - pub fn new() -> Self { + pub fn new(data: Uuid) -> Self { + Self { data: data } + } + + pub fn random() -> Self { Self { data: Uuid::new_v4(), } } } -impl FieldRecord for ID { - fn new_field() -> Field { - Field::ID(ID::new()) - } +impl TryFrom for ID { + type Error = MTTError; - fn get_type() -> String { - "id".to_string() + fn try_from(value: Uuid) -> Result { + Ok(Self::new(value)) + } +} + +impl TryFrom<&str> for ID { + type Error = MTTError; + + fn try_from(value: &str) -> Result { + match Uuid::try_from(value) { + Ok(id) => Ok(ID::new(id)), + Err(_) => Err(MTTError::new(ErrorType::FieldIDInvalid(value.to_string()))), + } } } @@ -31,42 +47,52 @@ impl fmt::Display for ID { } } -#[cfg(test)] -mod fielddata { - use super::*; - - #[test] - fn new_ids_are_unique() { - let mut ids: Vec = Vec::new(); - for _ in 1..10 { - match ID::new_field() { - Field::ID(id) => { - let id_string = id.to_string(); - assert!(!ids.contains(&id_string), "'{}' repeated", id_string); - ids.push(id_string); - } - } - } - } - - #[test] - fn get_type() { - assert_eq!(ID::get_type(), "id"); - } -} - #[cfg(test)] mod id { use super::*; #[test] - fn id_are_unique() { + fn try_from_uuid() { + let data = Uuid::new_v4(); + let id = ID::try_from(data.clone()).unwrap(); + assert_eq!(id.to_string(), data.to_string()); + } + + #[test] + fn try_from_str() { + let holder = Uuid::new_v4().to_string(); + let data = holder.as_str(); + let id = ID::try_from(data).unwrap(); + assert_eq!(id.to_string(), data); + } + + #[test] + fn try_from_bad_str() { + let bad = "BadUuid"; + match ID::try_from(bad) { + Ok(_) => unreachable!("Should have failed to create an ID"), + Err(err) => match err.get_code() { + ErrorType::FieldIDInvalid(id) => assert_eq!(id, bad), + _ => unreachable!("Returned wrong error"), + }, + } + } + + #[test] + fn create_new() { + let data = Uuid::new_v4(); + let id = ID::new(data.clone()); + assert_eq!(id.to_string(), data.to_string()); + } + + #[test] + fn random_is_unique() { let mut ids: Vec = Vec::new(); - for _ in 1..10 { - let id = ID::new(); - let id_string = id.to_string(); - assert!(!ids.contains(&id_string), "'{}' repeated", id_string); - ids.push(id_string); + for _ in 0..10 { + let id = ID::random(); + let holder = id.to_string(); + assert!(!ids.contains(&holder), "'{}' is a duplicate entry", id); + ids.push(holder); } } } diff --git a/src/data/mod.rs b/src/data/mod.rs index fd654d7..d2b1b4f 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,15 +1,29 @@ mod id; mod record; -use crate::data::record::Record; +use crate::{ + data::{ + id::ID, + record::{Field, Record}, + }, + error::{ErrorType, MTTError}, +}; use std::collections::HashMap; +enum FieldType { + ID, +} + struct FieldDef; impl FieldDef { fn new() -> Self { Self {} } + + fn get_type(&self) -> FieldType { + FieldType::ID + } } struct Table { @@ -23,19 +37,35 @@ impl Table { } } - fn len(&self) -> usize { - self.fields.len() + fn description(&self) -> &HashMap { + &self.fields } - fn add_field(&mut self, name: &str, field_type: &str) -> Result<(), String> { + fn add_field(&mut self, name: &str, field_type: FieldType) -> Result<(), MTTError> { match self.fields.get(name) { - Some(_) => Err("duplicate field name".to_string()), + Some(_) => { + let err = ErrorType::TableAddFieldDuplicate(name.to_string()); + Err(MTTError::new(err)) + } None => { self.fields.insert(name.to_string(), FieldDef::new()); Ok(()) } } } + + fn add_record(&mut self, rec: Record) -> Result { + for (key, _) in rec.iter() { + match self.fields.get(key) { + Some(_) => {} + None => { + let err = ErrorType::TableRecordInvalidFieldName(key.to_string()); + return Err(MTTError::new(err)); + } + } + } + Ok(rec) + } } #[cfg(test)] @@ -45,23 +75,72 @@ mod table { #[test] fn new_table() { let tbl = Table::new(); - assert_eq!(tbl.len(), 0); + let data = tbl.description(); + assert_eq!(data.len(), 0); } #[test] fn add_field() { let mut tbl = Table::new(); - tbl.add_field("one", "id"); - assert_eq!(tbl.len(), 1); + let field_name = "something "; + tbl.add_field(field_name, FieldType::ID); + let data = tbl.description(); + assert_eq!(data.len(), 1); + match data.get(field_name) { + Some(field_info) => match field_info.get_type() { + FieldType::ID => {} + _ => unreachable!("incorrect field type"), + }, + None => unreachable!("should return field definition"), + } } #[test] fn error_on_duplicate_name() { let mut tbl = Table::new(); - tbl.add_field("one", "id"); - match tbl.add_field("one", "id") { + let name = "one"; + tbl.add_field(name, FieldType::ID); + match tbl.add_field(name, FieldType::ID) { Ok(_) => unreachable!(" Should not duplicates."), - Err(_) => {} + Err(err) => match err.get_code() { + ErrorType::TableAddFieldDuplicate(result) => assert_eq!(result, name), + _ => unreachable!("should produce a duplicate name error"), + }, + } + } + + #[test] + fn add_record() { + let mut tbl = Table::new(); + let name = "id"; + let field_data = ID::random(); + tbl.add_field(name, FieldType::ID).unwrap(); + let mut data: HashMap = HashMap::new(); + data.insert(name.to_string(), field_data.clone().into()); + let rec = Record::new(data); + let result = tbl.add_record(rec).unwrap(); + assert_eq!( + result.get(name).unwrap().to_string(), + field_data.to_string() + ); + } + + #[test] + fn add_record_incorrect_field_name() { + let mut tbl = Table::new(); + let name = "id"; + let field_data = ID::random(); + let mut data: HashMap = HashMap::new(); + data.insert(name.to_string(), field_data.clone().into()); + let rec = Record::new(data); + match tbl.add_record(rec) { + Ok(_) => unreachable!("should have produced an error"), + Err(err) => match err.get_code() { + ErrorType::TableRecordInvalidFieldName(result) => { + assert_eq!(result, name); + } + _ => unreachable!("should have been invalid name error"), + }, } } } diff --git a/src/data/record.rs b/src/data/record.rs index d1d2de0..62da269 100644 --- a/src/data/record.rs +++ b/src/data/record.rs @@ -1,40 +1,14 @@ use crate::data::id::ID; -use std::{collections::HashMap, fmt}; +use std::{collections::HashMap, fmt, ops::Deref}; #[derive(Clone)] pub enum Field { ID(ID), } -impl Field { - fn new(field_type: &str) -> Field { - ID::new_field() - } -} - -pub trait FieldRecord { - fn new_field() -> Field; - fn get_type() -> String; -} - -pub struct Record { - data: HashMap, -} - -impl Record { - fn new(data: HashMap) -> Self { - Self { data: data } - } - - fn len(&self) -> usize { - self.data.len() - } - - fn get(&self, name: &str) -> Field { - match self.data.get(name) { - Some(data) => data.clone(), - None => unreachable!(), - } +impl From for Field { + fn from(value: ID) -> Self { + Field::ID(value) } } @@ -46,16 +20,33 @@ impl fmt::Display for Field { } } +pub struct Record { + data: HashMap, +} + +impl Record { + pub fn new(data: HashMap) -> Self { + Self { data: data } + } +} + +impl Deref for Record { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + #[cfg(test)] mod fields { use super::*; #[test] - fn creaAte_new_id() { - match Field::new("id") { - Field::ID(_) => {} - _ => unreachable!("Fould should be an ID type."), - } + fn from_id() { + let id = ID::random(); + let field = Field::from(id.clone()); + assert_eq!(field.to_string(), id.to_string()); } } @@ -71,18 +62,17 @@ mod records { } #[test] - fn new_record_more_data() { - let a = ID::new_field(); - let b = ID::new_field(); - let c = ID::new_field(); + fn record_iter() { + let a = Field::from(ID::random()); + let b = Field::from(ID::random()); + let c = Field::from(ID::random()); let mut data: HashMap = HashMap::new(); data.insert("a".to_string(), a.clone()); data.insert("b".to_string(), b.clone()); data.insert("c".to_string(), c.clone()); - let rec = Record::new(data); - assert_eq!(rec.len(), 3); - assert_eq!(rec.get("a").to_string(), a.to_string(), "record a"); - assert_eq!(rec.get("b").to_string(), b.to_string(), "record b"); - assert_eq!(rec.get("c").to_string(), c.to_string(), "record c"); + let rec = Record::new(data.clone()); + for (key, value) in rec.iter() { + assert_eq!(value.to_string(), data.get(key).unwrap().to_string()); + } } } diff --git a/src/error.rs b/src/error.rs index 5114bc1..9347691 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,28 +1,42 @@ use std::{error::Error, fmt}; #[derive(Debug)] -enum ErrorType { +pub enum ErrorType { + FieldIDInvalid(String), TableAddFieldDuplicate(String), + TableRecordInvalidFieldName(String), } impl fmt::Display for ErrorType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + ErrorType::FieldIDInvalid(data) => write!(f, "'{}' is not a valid uuid", data), ErrorType::TableAddFieldDuplicate(data) => write!(f, "field '{}' already exists", data), + ErrorType::TableRecordInvalidFieldName(data) => { + write!(f, "invalid field name '{}'", data) + } } } } #[derive(Debug)] -struct MTTError { +pub struct MTTError { err: ErrorType, } +impl From for MTTError { + fn from(value: ErrorType) -> Self { + MTTError::new(value) + } +} + impl MTTError { - fn new(err: ErrorType) -> Self { - Self { - err: err, - } + pub fn new(err: ErrorType) -> Self { + Self { err: err } + } + + pub fn get_code(&self) -> &ErrorType { + &self.err } } @@ -37,15 +51,50 @@ impl fmt::Display for MTTError { #[cfg(test)] mod errors { use super::*; + use rand::{distributions::Alphanumeric, Rng}; + + fn rand_str() -> String { + rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(5) + .map(char::from) + .collect() + } #[test] - fn get_error() { - let err = MTTError::new(ErrorType::TableAddFieldDuplicate("tester".to_string())); - assert_eq!(err.to_string(), "field 'tester' already exists"); - assert!(err.source().is_none()); - let err = MTTError::new(ErrorType::TableAddFieldDuplicate("other".to_string())); - assert_eq!(err.to_string(), "field 'other' already exists"); - assert!(err.source().is_none()); + fn from_invalid_id() { + let data = rand_str(); + let etype = ErrorType::FieldIDInvalid(data.clone()); + let result = MTTError::from(etype); + match result.get_code() { + ErrorType::FieldIDInvalid(txt) => assert_eq!(txt, &data), + _ => unreachable!("should have been ErrorType::FieldIDInvalid"), + } + assert_eq!( + result.to_string(), + format!("'{}' is not a valid uuid", data) + ); + } + + #[test] + fn from_duplicate_table_name() { + let data = rand_str(); + let etype = ErrorType::TableAddFieldDuplicate(data.clone()); + let result = MTTError::from(etype); + match result.get_code() { + ErrorType::TableAddFieldDuplicate(txt) => assert_eq!(txt, &data), + _ => unreachable!("should have been ErrorType::FieldIDInvalid"), + } + assert_eq!( + result.to_string(), + format!("field '{}' already exists", data) + ); + } + + #[test] + fn error_type_strings() { + let data = rand_str(); + let etype = ErrorType::TableRecordInvalidFieldName(data.clone()); + assert_eq!(etype.to_string(), format!("invalid field name '{}'", data)); } } -