use async_std::{ channel::{unbounded, Receiver, Sender}, path::PathBuf, task::spawn, }; use std::{collections::HashMap, error::Error, fmt}; const ENTRY: &str = "EntryPoint"; trait Requests { fn add(kind: &str, key: &str, value: Storage) -> Result<(), MTTError> { Err(MTTError::new("not supported")) } fn get() -> Vec { Vec::new() } } #[derive(Debug)] enum ErrorCode { // General Undefined(String), // Cache EntryNotFound(String), InvalidCommitData, // Store DatabaseAlreadyExists(String), } impl fmt::Display for ErrorCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ErrorCode::Undefined(msg) => write!(f, "{}", msg), ErrorCode::EntryNotFound(id) => write!(f, "entry '{}' was not found", id), ErrorCode::InvalidCommitData => write!(f, "commit data was not a database store"), ErrorCode::DatabaseAlreadyExists(name) => { write!(f, "database '{}' already exists", name) } } } } mod errorcodes { use super::*; const ITEMS: [&str; 2] = ["one", "two"]; #[test] fn undefined_display() { for item in ITEMS { let err = ErrorCode::Undefined(item.to_string()); assert_eq!(err.to_string(), item); } } #[test] fn bad_entry() { for item in ITEMS { let err = ErrorCode::EntryNotFound(item.to_string()); assert_eq!(err.to_string(), format!("entry '{}' was not found", item)); } } #[test] fn invalid_commit_data() { let err = ErrorCode::InvalidCommitData; assert_eq!(err.to_string(), "commit data was not a database store"); } #[test] fn database_already_exists() { for item in ITEMS { let err = ErrorCode::DatabaseAlreadyExists(item.to_string()); assert_eq!( err.to_string(), format!("database '{}' already exists", item) ); } } } #[derive(Debug)] pub struct MTTError { code: ErrorCode, } impl MTTError { fn new(msg: S) -> Self where S: Into, { let text = msg.into(); Self { code: ErrorCode::Undefined(text), } } fn from_code(code: ErrorCode) -> Self { Self { code: code } } } impl Error for MTTError {} impl fmt::Display for MTTError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.code) } } #[cfg(test)] mod errors { use super::*; #[test] fn create_with_str() { let msgs = ["one", "two"]; for msg in msgs { let err = MTTError::new(msg); assert_eq!(err.to_string(), msg); } } #[test] fn create_with_string() { let msg = "three"; let err = MTTError::new(msg.to_string()); assert_eq!(err.to_string(), msg); } #[test] fn create_from_code() { let code = ErrorCode::Undefined("oops".to_string()); let err = MTTError::from_code(code); match err.code { ErrorCode::Undefined(_) => (), _ => assert!(false, "{:?} is not undefined", err.code), } } #[test] fn create_missing_entry() { let code = ErrorCode::EntryNotFound("an_id".to_string()); let err = MTTError::from_code(code); match err.code { ErrorCode::EntryNotFound(_) => (), _ => assert!(false, "{:?} is not undefined", err.code), } } } #[derive(Clone, Debug)] struct Storage { id: Option, data: Option, // delete: bool, } impl Storage { fn from_id(id: S) -> Self where S: Into, { Self { id: Some(id.into()), data: None, } } fn from_datatype(dt: DataType) -> Self { Self { id: None, data: Some(dt), } } } #[cfg(test)] mod storage { use super::*; #[test] fn from_id_with_str() { let ids = ["first", "second"]; for id in ids { let output = Storage::from_id(id); assert_eq!(output.id, Some(id.to_string())); assert!( output.data.is_none(), "The storage data should have been Non." ); } } #[test] fn from_id_with_string() { let id = "my_id".to_string(); let output = Storage::from_id(id.clone()); assert_eq!(output.id, Some(id)); } #[test] fn from_store() { let output = Storage::from_datatype(DataType::new("store")); assert!(output.id.is_none(), "id should be None."); assert!(output.data.is_some(), "There should be data"); let result = output.data.unwrap(); match result { DataType::DBMap(_) => (), _ => assert!(false, "{:?} should have been DataType::DBMap.", result), } } #[test] fn from_database() { let output = Storage::from_datatype(DataType::new("database")); let result = output.data.unwrap(); match result { DataType::TableMap(_) => (), _ => assert!(false, "{:?} should have been DataType::TableMap.", result), } } } #[derive(Clone, Debug)] struct Store { data: HashMap, } impl Store { fn new() -> Self { Self { data: HashMap::new(), } } fn add_new(&mut self, name: S) -> Result<(), MTTError> where S: Into, { let dbname = name.into(); match self.get(&dbname) { Some(_) => Err(MTTError::from_code(ErrorCode::DatabaseAlreadyExists( dbname, ))), None => { self.data .insert(dbname, Storage::from_datatype(DataType::new("database"))); Ok(()) } } } fn get(&self, name: &str) -> Option<&Storage> { self.data.get(name) } } #[cfg(test)] mod stores { use super::*; #[test] fn get_no_database() -> Result<(), MTTError> { let store = Store::new(); match store.get("missing_name") { Some(_) => Err(MTTError::new("should have returned None")), None => Ok(()), } } #[test] fn add_database_str() { let mut store = Store::new(); let names = ["first", "second"]; for name in names { store.add_new(name).unwrap(); let output = store.get(name).unwrap(); assert!(output.data.is_some(), "There should be a data type."); match output.data.clone().unwrap() { DataType::TableMap(_) => (), _ => assert!( false, "{:?} should have been DataType::TableMap.", output.data ), } assert!(output.id.is_none(), "Should not have an id."); } } #[test] fn add_database_string() { let mut store = Store::new(); let name = "third".to_string(); store.add_new(name.clone()).unwrap(); let output = store.get(&name).unwrap(); match output.data.clone().unwrap() { DataType::TableMap(_) => (), _ => assert!( false, "{:?} should have been DataType::TableMap.", output.data ), } } #[test] fn no_duplicate_database_names() -> Result<(), MTTError> { let mut store = Store::new(); let name = "duplicate"; store.add_new(name).unwrap(); match store.add_new(name) { Ok(_) => Err(MTTError::new("should have been an error")), Err(err) => match err.code { ErrorCode::DatabaseAlreadyExists(dbname) => { assert_eq!(dbname, name); Ok(()) } _ => Err(MTTError::new(format!( "{:?} should have been DatabaseAlreadyExists.", err.code ))), }, } } } #[derive(Clone, Debug)] struct Database; #[cfg(test)] mod databases { use super::*; #[test] fn create() { Database::new(); } } impl Database { fn new() -> Self { Self {} } } #[derive(Clone, Debug)] enum DataType { DBMap(Store), TableMap(Database), } impl DataType { fn new(dtype: &str) -> DataType { match dtype { "store" => Self::DBMap(Store::new()), "database" => Self::TableMap(Database::new()), _ => unreachable!(), } } } #[cfg(test)] mod datatypes { use super::*; #[test] fn create_store() { let dtype = DataType::new("store"); match dtype { DataType::DBMap(_) => (), _ => assert!(false, "{:?} is not incorrect data type", dtype), } } #[test] fn create_database() { let dtype = DataType::new("database"); match dtype { DataType::TableMap(_) => (), _ => assert!(false, "{:?} is not incorrect data type", dtype), } } } #[derive(Debug)] enum FromCache { Ok, Data(HashMap), Error(MTTError), } struct CacheQuery { ids: Vec, reply: Sender, } struct CacheCommit { reply: Sender, data: DataType, } impl CacheCommit { fn new(data: DataType, channel: Sender) -> Result { match data { DataType::DBMap(_) => (), _ => return Err(MTTError::from_code(ErrorCode::InvalidCommitData)), } Ok(Self { data: data, reply: channel, }) } } mod commits { use super::*; #[test] fn create() -> Result<(), MTTError> { let (s, _) = unbounded(); match CacheCommit::new(DataType::new("store"), s) { Ok(output) => match output.data { DataType::DBMap(_) => Ok(()), _ => Err(MTTError::new(format!( "{:?} should have been DBMap", output.data ))), }, Err(err) => Err(err), } } #[test] fn bad_data_type() -> Result<(), MTTError> { let (s, _) = unbounded(); match CacheCommit::new(DataType::new("database"), s) { Ok(_) => Err(MTTError::new("CacheCommit::new did not return error")), Err(err) => match err.code { ErrorCode::InvalidCommitData => Ok(()), _ => Err(MTTError::new(format!( "{:?} is not the correct error", err.code ))), }, } } } enum ToCache { Query(CacheQuery), Commit(CacheCommit), } #[derive(Clone)] pub struct MoreThanText { session: Vec, cache: Sender>, } impl MoreThanText { async fn new(cache: Sender>) -> Result { Ok(Self { session: [ENTRY.to_string()].to_vec(), cache: cache, }) } } #[cfg(test)] mod mtt { use super::*; #[async_std::test] async fn create() { let (s, _) = unbounded(); let mtt = MoreThanText::new(s).await.unwrap(); assert_eq!(mtt.session, [ENTRY]); } } struct Cache; impl Cache { async fn new

(_dir: P) -> Result where P: Into, { Ok(Self {}) } async fn query(&self, qry: &Vec) -> Result, MTTError> { let mut output = HashMap::new(); for id in qry { if id == ENTRY { output.insert(ENTRY.to_string(), DataType::new("store")); } else { return Err(MTTError::from_code(ErrorCode::EntryNotFound( id.to_string(), ))); } } Ok(output) } async fn commit(&self) -> Result<(), MTTError> { Ok(()) } async fn start(&self, listener: Receiver) { loop { match listener.recv().await.unwrap() { ToCache::Query(qry) => match self.query(&qry.ids).await { Ok(data) => qry.reply.send(FromCache::Data(data)).await.unwrap(), Err(error) => qry.reply.send(FromCache::Error(error)).await.unwrap(), }, ToCache::Commit(commit) => match self.commit().await { Ok(_) => commit.reply.send(FromCache::Ok).await.unwrap(), Err(error) => commit.reply.send(FromCache::Error(error)).await.unwrap(), }, } } } } #[cfg(test)] mod caches { use super::*; use tempfile::tempdir; async fn start_cache

(dir: P) -> Sender where P: Into, { let (s, r) = unbounded(); let datadir = dir.into(); spawn(async move { let cache = Cache::new(datadir).await.unwrap(); cache.start(r).await; }); s } async fn send_request(data: Vec<&str>, channel: Sender) -> FromCache { let mut ids = Vec::new(); for id in data.iter() { ids.push(id.to_string()); } let (s, r) = unbounded(); let msg = ToCache::Query(CacheQuery { ids: ids, reply: s }); channel.send(msg).await.unwrap(); r.recv().await.unwrap() } #[async_std::test] async fn create() { let dir = tempdir().unwrap(); let s_cache = start_cache(dir.path()).await; let result = send_request(vec![ENTRY], s_cache).await; match result { FromCache::Data(data) => match data.get(ENTRY) { Some(output) => match output { DataType::DBMap(_) => (), _ => assert!(false, "{:?} is not a database store.", output), }, None => assert!(false, "Should contain entry point."), }, _ => assert!(false, "{:?} should have been a store.", result), } } #[async_std::test] async fn bad_entry() { let dir = tempdir().unwrap(); let s_cache = start_cache(dir.path()).await; let result = send_request(vec!["bad_id"], s_cache).await; match result { FromCache::Error(_) => (), _ => assert!(false, "{:?} should have been an error.", result), } } #[async_std::test] async fn empty_commit() { let dir = tempdir().unwrap(); let s_cache = start_cache(dir.path()).await; let (s, r) = unbounded(); let msg = ToCache::Commit(CacheCommit::new(DataType::new("store"), s).unwrap()); s_cache.send(msg).await.unwrap(); let result = r.recv().await.unwrap(); match result { FromCache::Ok => (), _ => assert!(false, "{:?} should have been an Ok.", result), } } #[async_std::test] async fn get_store() { let dir = tempdir().unwrap(); let cache = Cache::new(dir.path()).await.unwrap(); let output = cache.query(&[ENTRY.to_string()].to_vec()).await.unwrap(); let result = output.get(ENTRY).unwrap(); match result { DataType::DBMap(_) => (), _ => assert!(false, "{:?} should have been an Ok.", result), } } #[async_std::test] async fn bad_get() { let dir = tempdir().unwrap(); let cache = Cache::new(dir.path()).await.unwrap(); let bad_id = "really_bad_id"; match cache.query(&[bad_id.to_string()].to_vec()).await { Ok(_) => assert!(false, "Should have produced an error."), Err(err) => match err.code { ErrorCode::EntryNotFound(id) => assert_eq!(id, bad_id), _ => assert!(false, "{:?} should have been EntryNotFound.", err.code), }, } } } pub async fn start_db

(_dir: P) -> Result where P: Into, { let (s, r) = unbounded(); spawn(async move { loop { r.recv().await.unwrap(); } }); Ok(MoreThanText::new(s).await.unwrap()) } #[cfg(test)] mod db_start_up { use super::*; use tempfile::tempdir; #[async_std::test] async fn initial_session() { let dir = tempdir().unwrap(); let mtt = start_db(dir.path()).await.unwrap(); assert_eq!(mtt.session, [ENTRY]); } }