From 50a4431316a57f2efc2949e58518052496c135cb Mon Sep 17 00:00:00 2001 From: Jeff Baskin Date: Mon, 29 May 2023 15:42:32 -0400 Subject: [PATCH] Setting up for data storage. --- src/morethantext/cache.rs | 30 ++ src/morethantext/error.rs | 252 +++------------ src/morethantext/mod-3.rs | 640 ++++++++++++++++++++++++++++++++++++++ src/morethantext/mod.rs | 639 ++----------------------------------- 4 files changed, 733 insertions(+), 828 deletions(-) create mode 100644 src/morethantext/cache.rs create mode 100644 src/morethantext/mod-3.rs diff --git a/src/morethantext/cache.rs b/src/morethantext/cache.rs new file mode 100644 index 0000000..62ad209 --- /dev/null +++ b/src/morethantext/cache.rs @@ -0,0 +1,30 @@ +use async_std::{channel::Receiver, path::PathBuf}; + +pub struct Cache; + +impl Cache { + pub async fn new

(_dir: P) -> Self + where + P: Into, + { + Self {} + } + + pub async fn listen(&self, listener: Receiver) { + loop { + listener.recv().await.unwrap(); + } + } +} + +#[cfg(test)] +mod engine { + use super::*; + use tempfile::tempdir; + + #[async_std::test] + async fn create() { + let dir = tempdir().unwrap(); + Cache::new(dir.path()).await; + } +} diff --git a/src/morethantext/error.rs b/src/morethantext/error.rs index ed37284..682c980 100644 --- a/src/morethantext/error.rs +++ b/src/morethantext/error.rs @@ -1,95 +1,57 @@ -use async_std::path::PathBuf; use std::{error::Error, fmt}; #[derive(Debug)] pub enum ErrorCode { + // General Undefined(String), - // Read Write Errors - CorruptFile, - // Data Type Errors - DataTypeIncorrect(String), - // Entry Errors - EntryExists(PathBuf), - EntryWriteFailure(PathBuf), - EntryReadFailure(PathBuf), - EntryDeleteFailure(PathBuf), - // Cache - CacheReadWrite, } impl fmt::Display for ErrorCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ErrorCode::Undefined(msg) => write!(f, "{}", msg), - ErrorCode::DataTypeIncorrect(dtype) => write!(f, "data type '{}' is not valid", dtype), - ErrorCode::CorruptFile => write!(f, "corrupt file"), - ErrorCode::EntryExists(path) => write!( - f, - "entry '{}' already exists", - path.file_name().unwrap().to_str().unwrap() - ), - ErrorCode::EntryWriteFailure(path) => write!( - f, - "entry '{}' write failure", - path.file_name().unwrap().to_str().unwrap() - ), - ErrorCode::EntryReadFailure(path) => write!( - f, - "entry '{}' read failure", - path.file_name().unwrap().to_str().unwrap() - ), - ErrorCode::EntryDeleteFailure(path) => write!( - f, - "entry '{}' delete failure", - path.file_name().unwrap().to_str().unwrap() - ), - ErrorCode::CacheReadWrite => write!(f, "cache read write"), + } + } +} + +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); } } } #[derive(Debug)] -pub struct DBError { - pub code: ErrorCode, - src: Option>, +pub struct MTTError { + code: ErrorCode, } -impl DBError { - pub fn new(msg: S) -> Self +impl MTTError { + fn new(msg: S) -> Self where S: Into, { + let text = msg.into(); Self { - code: ErrorCode::Undefined(msg.into()), - src: None, + code: ErrorCode::Undefined(text), } } - pub fn from_code(code: ErrorCode) -> Self { - Self { - code: code, - src: None, - } - } - - pub fn add_source(&mut self, src: E) - where - E: Error + 'static, - { - self.src = Some(Box::new(src)); + fn from_code(code: ErrorCode) -> Self { + Self { code: code } } } -impl Error for DBError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match &self.src { - Some(err) => Some(err.as_ref()), - None => None, - } - } -} +impl Error for MTTError {} -impl fmt::Display for DBError { +impl fmt::Display for MTTError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.code) } @@ -100,160 +62,28 @@ mod errors { use super::*; #[test] - fn with_str() { - let msg = "something happened"; - let err = DBError::new(msg); - assert!( - err.to_string() == msg, - "Got: {} -- Want: {}", - err.to_string(), - msg - ); - assert!( - err.source().is_none(), - "Error should initialize with no source." - ); + 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 with_string() { - let msg = "it went boom".to_string(); - let err = DBError::new(msg.clone()); - assert!( - err.to_string() == msg, - "Got: {} -- Want: {}", - err.to_string(), - msg - ); - assert!( - err.source().is_none(), - "Error should initialize with no source." - ); - } - - #[test] - fn using_error_code() { - let msg = "utter failure"; - let code = ErrorCode::Undefined(msg.to_string()); - let err = DBError::from_code(code); + fn create_with_string() { + let msg = "three"; + let err = MTTError::new(msg.to_string()); assert_eq!(err.to_string(), msg); - assert!(err.source().is_none(), "Should be no source"); } #[test] - fn with_source() { - let msg = "but this caused the problem"; - let mut par = DBError::new("parent error"); - let src = DBError::new(msg); - par.add_source(src); - let output = par.source(); - assert!(output.is_some(), "Should return source."); - let source = output.unwrap(); - assert!(source.to_string() == msg); - } -} - -#[cfg(test)] -mod codes { - use super::*; - use async_std::path::PathBuf; - - const ITEMS: [&str; 2] = ["first", "second"]; - - fn create_path_buffer() -> Vec { - let mut output = Vec::new(); - for item in ITEMS { - let mut path = PathBuf::new(); - path.push("thepath"); - path.push(item); - output.push(path); - } - output - } - - #[test] - fn undefined_display() { - for item in ITEMS { - let err = ErrorCode::Undefined(item.to_string()); - assert_eq!(err.to_string(), item); - } - } - - #[test] - fn incorrect_data_type() { - for item in ITEMS { - let err = ErrorCode::DataTypeIncorrect(item.to_string()); - assert_eq!( - err.to_string(), - format!("data type '{}' is not valid", item) - ); - } - } - - #[test] - fn corrupt_file() { - assert_eq!(ErrorCode::CorruptFile.to_string(), "corrupt file"); - } - - #[test] - fn entry_exists() { - for path in create_path_buffer() { - let err = ErrorCode::EntryExists(path.clone()); - assert_eq!( - err.to_string(), - format!( - "entry '{}' already exists", - path.file_name().unwrap().to_str().unwrap() - ) - ); - } - } - - #[test] - fn entry_write_failure() { - for path in create_path_buffer() { - let err = ErrorCode::EntryWriteFailure(path.clone()); - assert_eq!( - err.to_string(), - format!( - "entry '{}' write failure", - path.file_name().unwrap().to_str().unwrap() - ) - ); - } - } - - #[test] - fn entry_read_failure() { - for path in create_path_buffer() { - let err = ErrorCode::EntryReadFailure(path.clone()); - assert_eq!( - err.to_string(), - format!( - "entry '{}' read failure", - path.file_name().unwrap().to_str().unwrap() - ) - ); - } - } - - #[test] - fn entry_delete_failure() { - for path in create_path_buffer() { - let err = ErrorCode::EntryDeleteFailure(path.clone()); - assert_eq!( - err.to_string(), - format!( - "entry '{}' delete failure", - path.file_name().unwrap().to_str().unwrap() - ) - ); - } - } - - #[test] - fn cache_read_write_failure() { - let err = ErrorCode::CacheReadWrite; - assert_eq!(err.to_string(), "cache read write"); + 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), + } } } diff --git a/src/morethantext/mod-3.rs b/src/morethantext/mod-3.rs new file mode 100644 index 0000000..2fd4479 --- /dev/null +++ b/src/morethantext/mod-3.rs @@ -0,0 +1,640 @@ +use async_std::{ + channel::{unbounded, Receiver, Sender}, + path::PathBuf, + task::spawn, +}; +use std::{collections::HashMap, error::Error, fmt}; + +const ENTRY: &str = "EntryPoint"; + +#[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]); + } +} diff --git a/src/morethantext/mod.rs b/src/morethantext/mod.rs index f10c14b..3e5746d 100644 --- a/src/morethantext/mod.rs +++ b/src/morethantext/mod.rs @@ -1,650 +1,55 @@ +mod cache; +mod error; + use async_std::{ - channel::{unbounded, Receiver, Sender}, + channel::{unbounded, 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), - } - } -} +use cache::Cache; +use error::{ErrorCode, MTTError}; #[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), +struct Data { + id: String, } #[derive(Clone)] pub struct MoreThanText { - session: Vec, - cache: Sender>, + to_cache: Sender, } impl MoreThanText { - async fn new(cache: Sender>) -> Result { - Ok(Self { - session: [ENTRY.to_string()].to_vec(), - cache: cache, - }) + fn new(to_cache: Sender) -> Self { + Self { to_cache: to_cache } + } + + async fn session(&self) { } } #[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() { + async fn create_new() { 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), - }, - } + let mtt = start_db(dir.path()).await.unwrap(); + mtt.session().await; } } -pub async fn start_db

(_dir: P) -> Result +pub async fn start_db

(dir: P) -> Result where P: Into, { + let path = dir.into(); let (s, r) = unbounded(); spawn(async move { - loop { - r.recv().await.unwrap(); - } + let cache = Cache::new(path).await; + cache.listen(r).await; }); - 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]); - } + Ok(MoreThanText::new(s)) }