use super::{Database, DBError, ErrorCode, FileData, SessionData, Store}; use async_std::{ fs::{read, remove_file, write}, path::{Path, PathBuf}, }; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use std::{ cell::Cell, slice, str, time::{Duration, Instant}, }; const ENTRY: &str = "EntryPoint"; #[derive(Clone)] enum DataType { DBMap(Store), TableMap(Database), } impl DataType { fn new(data_type: &str) -> Result { match data_type { "store" => Ok(DataType::DBMap(Store::new())), _ => Err(DBError::from_code(ErrorCode::DataTypeIncorrect( data_type.to_string(), ))), } } } impl SessionData for DataType { fn add(&mut self, key: &str, value: &str, data: &str) -> Result, DBError> { match self { DataType::DBMap(dbs) => dbs.add(key, value, data), DataType::TableMap(_) => todo!(), } } fn eq(&self, key: &str, value: &str) -> Result, DBError> { match self { DataType::DBMap(dbs) => dbs.eq(key, value), DataType::TableMap(_) => todo!(), } } fn list(&self, keys: Vec<&str>) -> Result, DBError> { match self { DataType::DBMap(dbs) => dbs.list(keys), DataType::TableMap(_) => todo!(), } } } impl FileData for DataType { fn to_bytes(&self) -> Vec { let mut output = Vec::new(); match self { DataType::DBMap(store) => { output.append(&mut "DBMap".as_bytes().to_vec()); output.push(0); output.append(&mut store.to_bytes()); }, DataType::TableMap(_) => todo!(), } output } fn from_bytes(data: &mut slice::Iter) -> Result { let mut header: Vec = Vec::new(); loop { let letter = match data.next() { Some(a) => a.clone(), None => 0, }; if letter == 0 { break; } else { header.push(letter); } } let header = match str::from_utf8(&header) { Ok(item) => item, Err(_) => return Err(DBError::from_code(ErrorCode::CorruptFile)), }; match header { "DBMap" => match Store::from_bytes(data) { Ok(store) => Ok(DataType::DBMap(store)), Err(err) => Err(err), }, _ => Err(DBError::from_code(ErrorCode::CorruptFile)), } } } struct Entry { data: DataType, filename: PathBuf, last_used: Cell, } impl Entry { async fn new

(filename: P, data: DataType) -> Result where P: Into, { let pathbuf = filename.into(); if pathbuf.as_path().exists().await { return Err(DBError::from_code(ErrorCode::EntryExists(pathbuf))); } else { match write(&pathbuf, data.to_bytes()).await { Ok(_) => (), Err(err) => { let mut error = DBError::from_code(ErrorCode::EntryWriteFailure(pathbuf)); error.add_source(err); return Err(error); } } } Ok(Self { data: data, filename: pathbuf, last_used: Cell::new(Instant::now()), }) } async fn get

(filename: P) -> Result where P: Into, { let pathbuf = filename.into(); let content = match read(&pathbuf).await { Ok(text) => text, Err(err) => { let mut error = DBError::from_code(ErrorCode::EntryReadFailure(pathbuf)); error.add_source(err); return Err(error); } }; let data = match DataType::from_bytes(&mut content.iter()) { Ok(raw) => raw, Err(err) => { let mut error = DBError::from_code(ErrorCode::EntryReadFailure(pathbuf)); error.add_source(err); return Err(error); } }; Ok(Self { data: data, filename: pathbuf, last_used: Cell::new(Instant::now()), }) } fn elapsed(&self) -> Duration { self.last_used.get().elapsed() } fn data(&self) -> DataType { self.last_used.set(Instant::now()); self.data.clone() } async fn update(&mut self, data: DataType) -> Result<(), DBError> { self.last_used.set(Instant::now()); match write(&self.filename, data.to_bytes()).await { Ok(_) => (), Err(err) => { let mut error = DBError::from_code(ErrorCode::EntryWriteFailure(self.filename.clone())); error.add_source(err); return Err(error); } }; self.data = data; Ok(()) } async fn remove(&self) -> Result<(), DBError> { match remove_file(&self.filename).await { Ok(_) => Ok(()), Err(err) => { let mut error = DBError::from_code(ErrorCode::EntryDeleteFailure(self.filename.clone())); error.add_source(err); Err(error) } } } } struct Cache; impl Cache { async fn new

(dir: P) -> Result where P: Into, { let pathbuf = dir.into(); let entry = pathbuf.as_path().join(ENTRY); match Entry::get(entry.clone()).await { Ok(_) => Ok(Self {}), Err(_) => { let store = DataType::new("store").unwrap(); match Entry::new(entry, store).await { Ok(_) => Ok(Self {}), Err(err) => { let mut error = DBError::from_code(ErrorCode::CacheReadWrite); error.add_source(err); Err(error) }, } }, } } } #[cfg(test)] mod datatype_sesssion { use super::*; #[test] fn invalid_cache_type() -> Result<(), DBError> { match DataType::new("dkhgdl") { Ok(_) => Err(DBError::new("invalid data type should raise an error")), Err(err) => match err.code { ErrorCode::DataTypeIncorrect(_) => Ok(()), _ => Err(DBError::new("Invalid error code")), }, } } #[test] fn create_storage() { let dbs = DataType::new("store").unwrap(); let expected: Vec = Vec::new(); assert_eq!(dbs.list(["database"].to_vec()).unwrap(), expected); } #[test] fn update_storage() { let mut dbs = DataType::new("store").unwrap(); let name = "new_database"; let id = "someid"; dbs.add("database", name, id).unwrap(); assert_eq!(dbs.eq("database", name).unwrap(), [id].to_vec()); assert_eq!(dbs.list(["database"].to_vec()).unwrap(), [name].to_vec()); } } #[cfg(test)] mod datatype_file { use super::*; #[test] fn new_store_bytes() { let dbs = DataType::new("store").unwrap(); let mut expected = "DBMap".as_bytes().to_vec(); expected.push(0); assert_eq!(dbs.to_bytes(), expected); } #[test] fn store_bytes_with_info() { let name = "title"; let id = "king"; let mut store = Store::new(); let mut dt_store = DataType::new("store").unwrap(); let mut expected = dt_store.to_bytes(); store.add("database", name, id).unwrap(); expected.append(&mut store.to_bytes()); dt_store.add("database", name, id).unwrap(); assert_eq!(dt_store.to_bytes(), expected); } #[test] fn read_empty_store() { let dt_store = DataType::new("store").unwrap(); let data = dt_store.to_bytes(); let mut feed = data.iter(); let output = DataType::from_bytes(&mut feed).unwrap(); assert_eq!( dt_store.list(["database"].to_vec()).unwrap(), output.list(["database"].to_vec()).unwrap() ); } #[test] fn read_store_info() { let mut dt_store = DataType::new("store").unwrap(); dt_store.add("database", "raven", "beastboy").unwrap(); let data = dt_store.to_bytes(); let mut feed = data.iter(); let output = DataType::from_bytes(&mut feed).unwrap(); assert_eq!( dt_store.list(["database"].to_vec()).unwrap(), output.list(["database"].to_vec()).unwrap() ); } #[test] fn read_bad_header() -> Result<(), DBError> { let data = "sdghsdl".as_bytes().to_vec(); let mut feed = data.iter(); match DataType::from_bytes(&mut feed) { Ok(_) => Err(DBError::new("should have raised an error")), Err(err) => match err.code { ErrorCode::CorruptFile => Ok(()), _ => Err(DBError::new("incorrect error")), }, } } #[test] fn read_bad_store() -> Result<(), DBError> { let mut data = "DBMap".as_bytes().to_vec(); data.push(0); data.append(&mut "sdfgs".as_bytes().to_vec()); let mut feed = data.iter(); match DataType::from_bytes(&mut feed) { Ok(_) => Err(DBError::new("should have raised an error")), Err(err) => match err.code { ErrorCode::CorruptFile => Ok(()), _ => Err(DBError::new("incorrect error code")), } } } } #[cfg(test)] mod entry { use super::*; use std::error::Error; use tempfile::tempdir; #[async_std::test] async fn get_elapsed_time() { let dir = tempdir().unwrap(); let data = DataType::new("store").unwrap(); let filepath = dir.path().join("count"); let filename = filepath.to_str().unwrap(); let item = Entry::new(filename.to_string(), data).await.unwrap(); assert!( Duration::from_secs(1) > item.elapsed(), "last_used should have been now." ); item.last_used .set(Instant::now() - Duration::from_secs(500)); assert!( Duration::from_secs(499) < item.elapsed(), "The duration should have increased." ); } #[async_std::test] async fn create() { let dir = tempdir().unwrap(); let mut data = DataType::new("store").unwrap(); data.add("database", "roger", "moore").unwrap(); let filepath = dir.path().join("wiliam"); let filename = filepath.to_str().unwrap(); let item = Entry::new(filename.to_string(), data.clone()) .await .unwrap(); assert!( Duration::from_secs(1) > item.elapsed(), "last_used should have been now." ); let output = item.data(); assert_eq!( data.list(["database"].to_vec()).unwrap(), output.list(["database"].to_vec()).unwrap() ); assert!(filepath.is_file(), "Should have created the entry file."); let content = read(&filepath).await.unwrap(); assert_eq!(content, data.to_bytes()); } #[async_std::test] async fn create_errors_on_bad_files() -> Result<(), DBError> { let dir = tempdir().unwrap(); let data = DataType::new("store").unwrap(); let filepath = dir.path().join("bad").join("path"); let filename = filepath.to_str().unwrap(); match Entry::new(filename.to_string(), data).await { Ok(_) => Err(DBError::new("bad file names should raise an error")), Err(err) => match err.code { ErrorCode::EntryWriteFailure(_) => { assert!(err.source().is_some(), "Must include the source error."); assert!(err .source() .unwrap() .to_string() .contains("could not write to file")); Ok(()) } _ => Err(DBError::new("incorrect error code")), }, } } #[async_std::test] async fn create_does_not_over_writes() -> Result<(), DBError> { let dir = tempdir().unwrap(); let id = "wicked"; let file = dir.path().join(id); let filename = file.to_str().unwrap(); write(&file, b"previous").await.unwrap(); let data = DataType::new("store").unwrap(); match Entry::new(filename.to_string(), data).await { Ok(_) => { return Err(DBError::new( "Should produce an error for an existing Entry", )) } Err(err) => match err.code { ErrorCode::EntryExists(_) => Ok(()), _ => Err(DBError::new("incorrect error code")), }, } } #[async_std::test] async fn get_updates_last_used() { let dir = tempdir().unwrap(); let data = DataType::new("store").unwrap(); let filepath = dir.path().join("holder"); let filename = filepath.to_str().unwrap(); let item = Entry::new(filename.to_string(), data).await.unwrap(); item.last_used .set(Instant::now() - Duration::from_secs(300)); item.data(); assert!( Duration::from_secs(1) > item.elapsed(), "last_used should have been reset." ); } #[async_std::test] async fn update_entry() { let dir = tempdir().unwrap(); let mut data = DataType::new("store").unwrap(); let filepath = dir.path().join("changing"); let filename = filepath.to_str().unwrap(); let mut item = Entry::new(filename.to_string(), data.clone()) .await .unwrap(); item.last_used .set(Instant::now() - Duration::from_secs(500)); data.add("database", "new", "stuff").unwrap(); item.update(data.clone()).await.unwrap(); assert!( Duration::from_secs(1) > item.elapsed(), "last_used should have been reset." ); let output = item.data(); assert_eq!( data.list(["database"].to_vec()).unwrap(), output.list(["database"].to_vec()).unwrap() ); let content = read(&filepath).await.unwrap(); assert_eq!(content, data.to_bytes()); } #[async_std::test] async fn update_write_errors() -> Result<(), DBError> { let dir = tempdir().unwrap(); let data = DataType::new("store").unwrap(); let filepath = dir.path().join("changing"); let filename = filepath.to_str().unwrap(); let mut item = Entry::new(filename.to_string(), data.clone()) .await .unwrap(); drop(dir); match item.update(data).await { Ok(_) => Err(DBError::new("file writes should return an error")), Err(err) => match err.code { ErrorCode::EntryWriteFailure(_) => { assert!(err.source().is_some(), "Must include the source error."); assert!(err .source() .unwrap() .to_string() .contains("could not write to file")); Ok(()) }, _ => Err(DBError::new("incorrect error code")), } } } #[async_std::test] async fn retrieve() { let dir = tempdir().unwrap(); let mut data = DataType::new("store").unwrap(); data.add("database", "something_old", "3.14159").unwrap(); let filepath = dir.path().join("existing"); let filename = filepath.to_str().unwrap(); let item = Entry::new(filename.to_string(), data.clone()) .await .unwrap(); let output = Entry::get(filename).await.unwrap(); assert_eq!( output.data().list(["database"].to_vec()).unwrap(), data.list(["database"].to_vec()).unwrap() ); assert_eq!(output.filename.to_str().unwrap(), filename); assert!( Duration::from_secs(1) > item.elapsed(), "last_used should have been reset." ); } #[async_std::test] async fn retrieve_file_missing() -> Result<(), DBError> { let dir = tempdir().unwrap(); let filepath = dir.path().join("justnotthere"); let filename = filepath.to_str().unwrap(); match Entry::get(filename).await { Ok(_) => Err(DBError::new("should have returned an error")), Err(err) => match err.code { ErrorCode::EntryReadFailure(_) => { assert!(err.source().is_some(), "Error should have a source."); assert!( err.source() .unwrap() .to_string() .contains("could not read file"), "Source Error Message: {}", err.source().unwrap().to_string() ); Ok(()) } _ => Err(DBError::new("incorrect error code")), } } } #[async_std::test] async fn retrieve_corrupt_file() -> Result<(), DBError> { let dir = tempdir().unwrap(); let filepath = dir.path().join("garbage"); let filename = filepath.to_str().unwrap(); write(&filepath, b"jhsdfghlsdf").await.unwrap(); match Entry::get(filename).await { Ok(_) => Err(DBError::new("should have returned an error")), Err(err) => match err.code { ErrorCode::EntryReadFailure(_) => { assert!(err.source().is_some(), "Error should have a source."); assert!( err.source().unwrap().to_string().contains("corrupt file"), "Source Error Message: {}", err.source().unwrap().to_string() ); Ok(()) }, _ => Err(DBError::new("incorrect error code")), } } } #[async_std::test] async fn delete() { let dir = tempdir().unwrap(); let filepath = dir.path().join("byebye"); let filename = filepath.to_str().unwrap(); let data = DataType::new("store").unwrap(); let item = Entry::new(filename.to_string(), data.clone()) .await .unwrap(); item.remove().await.unwrap(); assert!(!filepath.exists(), "Entry file should be removed."); } #[async_std::test] async fn delete_bad_file() -> Result<(), DBError> { let dir = tempdir().unwrap(); let filepath = dir.path().join("itsnotthere"); let filename = filepath.to_str().unwrap(); let data = DataType::new("store").unwrap(); let item = Entry::new(filename.to_string(), data.clone()) .await .unwrap(); remove_file(filename).await.unwrap(); match item.remove().await { Ok(_) => Err(DBError::new("should have produced an error")), Err(err) => match err.code { ErrorCode::EntryDeleteFailure(_) => { assert!(err.source().is_some(), "Error should have a source."); assert!( err.source() .unwrap() .to_string() .contains("could not remove file"), "Source Error Message: {}", err.source().unwrap().to_string() ); Ok(()) }, _ => Err(DBError::new("incorrect error code")), } } } } #[cfg(test)] mod cache { use super::*; use std::error::Error; use tempfile::tempdir; #[async_std::test] async fn create() { let dir = tempdir().unwrap(); Cache::new(dir.path().to_str().unwrap()).await.unwrap(); let epoint = dir.path().join(ENTRY); assert!( epoint.is_file(), "{} did not get created.", epoint.display() ); let entry = Entry::get(epoint.to_str().unwrap()).await.unwrap(); assert_eq!( entry.data().list(["database"].to_vec()).unwrap(), Vec::::new() ); } #[async_std::test] async fn entry_failure() -> Result<(), DBError> { let dir = tempdir().unwrap(); let path = dir.path().join("bad").join("path"); match Cache::new(path).await { Ok(_) => Err(DBError::new("Should have produced an error.")), Err(err) => { match err.code { ErrorCode::CacheReadWrite => { assert!(err.source().is_some(), "Error should have a source."); assert!( err.source().unwrap().to_string().contains("write failure"), "Source Error Message: {}", err.source().unwrap().to_string() ); Ok(()) }, _ => Err(DBError::new("incorrect error code")), } } } } #[async_std::test] async fn existing_entry_point() { let dir = tempdir().unwrap(); let data = DataType::new("store").unwrap(); Entry::new(dir.path().join(ENTRY), data.clone()) .await .unwrap(); let cache = Cache::new(dir.path()).await.unwrap(); } #[async_std::test] async fn corrupt_enty_point() -> Result<(), DBError> { let dir = tempdir().unwrap(); let file = dir.path().join(ENTRY); write(file, b"Really bad data.").await.unwrap(); match Cache::new(dir.path()).await { Ok(_) => Err(DBError::new("should have errored")), Err(_) => Ok(()), } } }