use super::{DBError, FileData}; use std::{collections::HashMap, slice, str}; #[derive(Clone)] pub struct Databases { db_map: HashMap, } impl Databases { pub fn new() -> Self { Self { db_map: HashMap::new(), } } fn add_database(&mut self, name: &str, id: &str) -> Result { if name.len() == 0 { return Err(DBError::new("database names cannot be empty")); } else if self.db_map.contains_key(name) { return Err(DBError::new("database already exists")); } else { self.db_map.insert(name.to_string(), id.to_string()); Ok(id.to_string()) } } fn get_database(&self, name: &str) -> Option { self.db_map.get(name).cloned() } fn show(&self) -> Vec { let mut names: Vec = self.db_map.clone().into_keys().collect(); names.sort(); names } } impl FileData for Databases { fn to_bytes(&self) -> Vec { let mut output = Vec::new(); for (name, id) in self.db_map.iter() { output.append(&mut name.as_bytes().to_vec()); output.push(0); output.append(&mut id.as_bytes().to_vec()); output.push(0); } output } fn from_bytes(data: &mut slice::Iter) -> Result { let mut output = Databases::new(); let mut name: Vec = Vec::new(); let mut id: Vec = Vec::new(); let mut get_id = false; let mut letter: u8; loop { match data.next() { Some(a) => letter = a.clone(), None => { if !name.is_empty() { return Err(DBError::new("file corruption")); } break; } } if letter == 0 { if get_id { match output .add_database(str::from_utf8(&name).unwrap(), str::from_utf8(&id).unwrap()) { Ok(_) => (), Err(err) => { let mut error = DBError::new("file corruption"); error.add_source(err); return Err(error); } }; name.clear(); id.clear(); } get_id = !get_id; } else { if get_id { id.push(letter); } else { name.push(letter); } } } Ok(output) } } #[cfg(test)] mod functions { use super::*; #[test] fn add_entry() { let name = "fred"; let id = "123456"; let mut dbs = Databases::new(); let output = dbs.add_database(name, id).unwrap(); assert_eq!(output, id.to_string()); } #[test] fn add_entry_no_name() { let name = ""; let id = "123456"; let mut dbs = Databases::new(); match dbs.add_database(name, id) { Ok(_) => assert!(false, "There should have been an error"), Err(err) => assert_eq!(err.to_string(), "database names cannot be empty"), } } #[test] fn entry_cannot_be_over_written() { let name = "barney"; let id = "abcde"; let mut dbs = Databases::new(); dbs.add_database(name, id).unwrap(); match dbs.add_database(name, "09876") { Ok(_) => assert!(false, "There should have been an error"), Err(err) => assert_eq!(err.to_string(), "database already exists"), } let output = dbs.get_database(name); assert_eq!(output, Some(id.to_string())); } #[test] fn get_bad_database() { let dbs = Databases::new(); let output = dbs.get_database("missing"); assert_eq!(output, None); } #[test] fn list_databases() { let mut dbs = Databases::new(); dbs.add_database("zebra", "a").unwrap(); dbs.add_database("alpha", "a").unwrap(); dbs.add_database("charlie", "a").unwrap(); dbs.add_database("wilma", "a").unwrap(); dbs.add_database("frank", "a").unwrap(); let expected = ["alpha", "charlie", "frank", "wilma", "zebra"]; let output = dbs.show(); assert_eq!(output, expected); } } #[cfg(test)] mod filedata { use super::*; use std::error::Error; #[test] fn to_bytes_new() { let dbs = Databases::new(); let expected: Vec = Vec::new(); let output = dbs.to_bytes(); assert_eq!(output, expected); } #[test] fn to_bytes_with_database() { let mut dbs = Databases::new(); let name = "something"; let id = "id"; dbs.add_database(name, id).unwrap(); let mut expected: Vec = Vec::new(); expected.append(&mut name.as_bytes().to_vec()); expected.push(0); expected.append(&mut id.as_bytes().to_vec()); expected.push(0); let output = dbs.to_bytes(); assert_eq!(output, expected); } #[test] fn from_bytes() { let mut dbs = Databases::new(); dbs.add_database("one", "1").unwrap(); dbs.add_database("two", "2").unwrap(); dbs.add_database("three", "3").unwrap(); let data = dbs.to_bytes(); let mut feed = data.iter(); let output = Databases::from_bytes(&mut feed).unwrap(); assert_eq!(output.db_map, dbs.db_map); } #[test] fn from_bytes_incomplete_name() { let data = "notName".as_bytes(); let mut feed = data.iter(); match Databases::from_bytes(&mut feed) { Ok(_) => assert!(false, "This should have failed."), Err(err) => assert_eq!(err.to_string(), "file corruption"), } } #[test] fn from_bytes_incomplete_id() { let mut data = "proper".as_bytes().to_vec(); data.push(0); data.append(&mut "nope".as_bytes().to_vec()); let mut feed = data.iter(); match Databases::from_bytes(&mut feed) { Ok(_) => assert!(false, "This should have failed."), Err(err) => assert_eq!(err.to_string(), "file corruption"), } } #[test] fn from_bytes_handles_error() { let mut data = "duplicate".as_bytes().to_vec(); data.push(0); data.append(&mut "first".as_bytes().to_vec()); data.push(0); data.append(&mut "duplicate".as_bytes().to_vec()); data.push(0); data.append(&mut "second".as_bytes().to_vec()); data.push(0); let mut feed = data.iter(); match Databases::from_bytes(&mut feed) { Ok(_) => assert!(false, "This should have failed."), Err(err) => { assert_eq!(err.to_string(), "file corruption"); assert!( err.source().is_some(), "Should state file corruption cause." ); assert_eq!(err.source().unwrap().to_string(), "database already exists") } } } }