use super::{Database, ErrorCode, FromCache, MTTError, Store, ToCache, ENTRY}; use async_std::{channel::Receiver, path::PathBuf}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use std::{ collections::{HashMap, VecDeque}, iter::Iterator, }; struct IDGenerator { ids: Option>, } impl IDGenerator { fn new() -> Self { Self { ids: None } } fn with_ids(ids: T) -> Self where T: Into>, D: Into, { let id_list = ids.into(); let mut data = VecDeque::new(); for id in id_list { data.push_back(id.into()); } Self { ids: Some(data) } } } impl Iterator for IDGenerator { type Item = String; fn next(&mut self) -> Option { match &self.ids { Some(id_list) => { let mut ids = id_list.clone(); let output = ids.pop_front(); self.ids = Some(ids); output } None => Some(thread_rng().sample_iter(&Alphanumeric).take(64).collect()), } } } #[cfg(test)] mod genid { use super::*; #[test] fn unique_ids() { let mut gen = IDGenerator::new(); let mut output: Vec = Vec::new(); for _ in 0..10 { let id = gen.next().unwrap(); assert!(!output.contains(&id), "{} found in {:?}", id, output); output.push(id); } } #[test] fn controlled_ids() { let ids = ["one", "two", "three"]; let mut gen = IDGenerator::with_ids(ids.clone()); for id in ids { assert_eq!(id, gen.next().unwrap()); } } } pub struct Cache { data: HashMap, ids: IDGenerator, } impl Cache { pub async fn new

(_dir: P) -> Self where P: Into, { let mut data = HashMap::new(); data.insert(ENTRY.to_string(), FromCache::Str(Store::new())); Self { data: data, ids: IDGenerator::new(), } } async fn with_ids(dir: P, ids: T) -> Self where P: Into, T: Into>, D: Into, { let mut output = Self::new(dir).await; output.ids = IDGenerator::with_ids(ids); output } fn next_id(&mut self) -> String { let mut id: String; loop { id = self.ids.next().unwrap(); match self.get(&id) { FromCache::Error(_) => break, _ => (), } } id } pub async fn listen(&mut self, listener: Receiver) { loop { match listener.recv().await.unwrap() { ToCache::Get(data) => { data.result.send(self.get(data.data)).await.unwrap(); } ToCache::Commit(data) => { data.result.send(self.commit(data.data)).await.unwrap(); } } } } pub fn get(&self, id: S) -> FromCache where S: Into, { let idd = id.into(); match self.data.get(&idd) { Some(data) => data.clone(), None => FromCache::Error(MTTError::from_code(ErrorCode::IDNotFound(idd))), } } pub fn commit(&mut self, data: Store) -> FromCache { let entry_data = self.data.get(ENTRY).unwrap(); let mut store = match entry_data { FromCache::Str(ep) => ep.clone(), _ => { unreachable!() } }; for name in data.list() { let id = self.next_id(); match store.add_by_id(name, &id) { Ok(_) => { self.data.insert(id, FromCache::DB(Database::new())); } Err(err) => return FromCache::Error(err), } } self.data .insert(ENTRY.to_string(), FromCache::Str(store)) .unwrap(); FromCache::Ok } } #[cfg(test)] mod engine { use super::*; use tempfile::tempdir; #[async_std::test] async fn get_entry() { let dir = tempdir().unwrap(); let cache = Cache::new(dir.path()).await; let expected: Vec = Vec::new(); let result = cache.get(ENTRY); match result { FromCache::Str(store) => assert_eq!(store.list(), expected), _ => assert!(false, "{:?} should be FromCache::Str", result), } } #[async_std::test] async fn get_bad_entry() -> Result<(), MTTError> { let dir = tempdir().unwrap(); let cache = Cache::new(dir.path()).await; let ids = ["bad1", "bad2"]; for id in ids { let output = cache.get(id); match output { FromCache::Error(err) => match err.code { ErrorCode::IDNotFound(_) => { assert!( err.to_string().contains(id), "Had error: {}, Did not contain: {}", err.to_string(), id ); } _ => return Err(MTTError::new(format!("{:?} is not IDNotFound", err.code))), }, _ => { return Err(MTTError::new(format!( "{:?} is not FromCache::Error", output ))) } } } Ok(()) } #[async_std::test] async fn commit_database() { // remove this one for the one below, maybe. let dir = tempdir().unwrap(); let mut cache = Cache::new(dir.path()).await; let mut store = Store::new(); let db = "garfield"; store.add(db).unwrap(); cache.commit(store.clone()); let output = cache.get(ENTRY); match output { FromCache::Str(result) => assert_eq!(result.list(), store.list()), _ => assert!(false, "{:?} is not FromCache::Str", output), } } #[async_std::test] async fn add_database_entry() { let id = "an_id"; let name = "garfield"; let dir = tempdir().unwrap(); let mut cache = Cache::with_ids(dir.path(), [id]).await; let mut store = Store::new(); store.add(name).unwrap(); cache.commit(store.clone()); let db_out = cache.get(id); match db_out { FromCache::DB(_) => (), _ => assert!( false, "{:?} is not FromCache::DB -- cache is {:?}", db_out, cache.data ), } let store_out = cache.get(ENTRY); match store_out { FromCache::Str(updated_store) => match updated_store.get(name) { Some(output) => { assert_eq!(output.id, Some(id.to_string())); assert!(output.data.is_none(), "Should have removed the database."); } None => assert!(true, "Store should have stored the database."), }, _ => assert!( false, "{:?} is not FromCache::Str -- cache is {:?}", db_out, cache.data ), } } #[async_std::test] async fn ids_are_not_overwritten() { let ids = ["first", "first", "second"]; let names = ["barney", "fred"]; let dir = tempdir().unwrap(); let mut cache = Cache::with_ids(dir.path(), ids).await; let mut store1 = Store::new(); store1.add(names[0]).unwrap(); let mut store2 = Store::new(); store2.add(names[1]).unwrap(); cache.commit(store1); cache.commit(store2); assert_eq!( cache.data.len(), 3, "cache.data had the following entries {:?}", cache.data.keys() ); } #[async_std::test] async fn no_duplicate_ids() { let ids = ["one", "two"]; let dir = tempdir().unwrap(); let mut cache = Cache::with_ids(dir.path(), ids).await; cache .data .insert(ids[0].to_string(), FromCache::DB(Database::new())); assert_eq!(cache.next_id(), ids[1]); } } #[cfg(test)] mod messages { use super::{ super::{start_db, ToCacheMsg}, *, }; use async_std::channel::unbounded; use tempfile::tempdir; #[async_std::test] async fn get_the_store() { let dir = tempdir().unwrap(); let mtt = start_db(dir.path()).await.unwrap(); let in_s = mtt.to_cache.clone(); let (out_s, out_r) = unbounded(); let msg = ToCacheMsg { data: ENTRY.to_string(), result: out_s, }; in_s.send(ToCache::Get(msg)).await.unwrap(); let result = out_r.recv().await.unwrap(); match result { FromCache::Str(_) => (), _ => assert!(false, "{:?} is not FromCache::Str", result), } } #[async_std::test] async fn get_bad_id() { let dir = tempdir().unwrap(); let mtt = start_db(dir.path()).await.unwrap(); let in_s = mtt.to_cache.clone(); let (out_s, out_r) = unbounded(); let msg = ToCacheMsg { data: "bad_id!".to_string(), result: out_s, }; in_s.send(ToCache::Get(msg)).await.unwrap(); let output = out_r.recv().await.unwrap(); match output { FromCache::Error(_) => (), _ => assert!(false, "{:?} is not FromCache::Error", output), } } }