Compare commits
	
		
			2 Commits
		
	
	
		
			6d61af5136
			...
			659a2758bb
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 659a2758bb | |||
| 685ddfe32d | 
| @@ -7,7 +7,7 @@ use tide::{ | |||||||
| mod morethantext; | mod morethantext; | ||||||
| mod settings; | mod settings; | ||||||
|  |  | ||||||
| use morethantext::MoreThanText; | use morethantext::{start_db, MoreThanText}; | ||||||
| use settings::Settings; | use settings::Settings; | ||||||
|  |  | ||||||
| #[async_std::main] | #[async_std::main] | ||||||
| @@ -20,7 +20,7 @@ async fn main() -> tide::Result<()> { | |||||||
| } | } | ||||||
|  |  | ||||||
| async fn app_setup(data_dir: &str) -> tide::Server<MoreThanText> { | async fn app_setup(data_dir: &str) -> tide::Server<MoreThanText> { | ||||||
|     let db = MoreThanText::new(data_dir).await.unwrap(); |     let db = start_db(data_dir).await.unwrap(); | ||||||
|     let mut app = tide::with_state(db); |     let mut app = tide::with_state(db); | ||||||
|     app.at("/").get(home); |     app.at("/").get(home); | ||||||
|     app.with( |     app.with( | ||||||
|   | |||||||
							
								
								
									
										402
									
								
								src/morethantext/mod-2.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										402
									
								
								src/morethantext/mod-2.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,402 @@ | |||||||
|  | mod database; | ||||||
|  | mod entry; | ||||||
|  | mod error; | ||||||
|  | mod store; | ||||||
|  |  | ||||||
|  | use async_std::path::PathBuf; | ||||||
|  | use database::Database; | ||||||
|  | use entry::Entry; | ||||||
|  | use error::{DBError, ErrorCode}; | ||||||
|  | use rand::{distributions::Alphanumeric, thread_rng, Rng}; | ||||||
|  | use std::{slice, str}; | ||||||
|  | use store::Store; | ||||||
|  |  | ||||||
|  | const ENTRY: &str = "EntryPoint"; | ||||||
|  |  | ||||||
|  | trait ID { | ||||||
|  |     fn next(&self) -> String; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | trait FileData<F> { | ||||||
|  |     fn to_bytes(&self) -> Vec<u8>; | ||||||
|  |     fn from_bytes(data: &mut slice::Iter<u8>) -> Result<F, DBError>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | trait SessionData { | ||||||
|  |     fn add(&mut self, key: &str, value: &str, data: &str) -> Result<Vec<String>, DBError>; | ||||||
|  |     fn eq(&self, key: &str, value: &str) -> Result<Vec<String>, DBError>; | ||||||
|  |     fn list(&self, keys: Vec<&str>) -> Result<Vec<String>, DBError>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone)] | ||||||
|  | pub enum DataType { | ||||||
|  |     DBMap(Store), | ||||||
|  |     TableMap(Database), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl DataType { | ||||||
|  |     fn new(data_type: &str) -> Result<Self, DBError> { | ||||||
|  |         match data_type { | ||||||
|  |             "store" => Ok(DataType::DBMap(Store::new())), | ||||||
|  |             "database" => Ok(DataType::TableMap(Database::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<Vec<String>, DBError> { | ||||||
|  |         match self { | ||||||
|  |             DataType::DBMap(dbs) => dbs.add(key, value, data), | ||||||
|  |             DataType::TableMap(_) => todo!(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn eq(&self, key: &str, value: &str) -> Result<Vec<String>, DBError> { | ||||||
|  |         match self { | ||||||
|  |             DataType::DBMap(dbs) => dbs.eq(key, value), | ||||||
|  |             DataType::TableMap(_) => todo!(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn list(&self, keys: Vec<&str>) -> Result<Vec<String>, DBError> { | ||||||
|  |         match self { | ||||||
|  |             DataType::DBMap(dbs) => dbs.list(keys), | ||||||
|  |             DataType::TableMap(_) => todo!(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl FileData<Self> for DataType { | ||||||
|  |     fn to_bytes(&self) -> Vec<u8> { | ||||||
|  |         let mut output = Vec::new(); | ||||||
|  |         match self { | ||||||
|  |             DataType::DBMap(_) => output.append(&mut "DBMap".as_bytes().to_vec()), | ||||||
|  |             DataType::TableMap(_) => output.append(&mut "TableMap".as_bytes().to_vec()), | ||||||
|  |         } | ||||||
|  |         output.push(0); | ||||||
|  |         match self { | ||||||
|  |             DataType::DBMap(store) => output.append(&mut store.to_bytes()), | ||||||
|  |             DataType::TableMap(_) => (), | ||||||
|  |         } | ||||||
|  |         output | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn from_bytes(data: &mut slice::Iter<u8>) -> Result<Self, DBError> { | ||||||
|  |         let mut header: Vec<u8> = 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), | ||||||
|  |             }, | ||||||
|  |             "TableMap" => Ok(DataType::new("database").unwrap()), | ||||||
|  |             _ => Err(DBError::from_code(ErrorCode::CorruptFile)), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone)] | ||||||
|  | pub struct MoreThanText { | ||||||
|  |     next_id: &dyn Fn() -> String, | ||||||
|  |     session: Vec<String>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl MoreThanText { | ||||||
|  |     pub async fn new<P>(dir: P) -> Result<Self, DBError> | ||||||
|  |     where | ||||||
|  |         P: Into<PathBuf>, | ||||||
|  |     { | ||||||
|  |         let pathbuf = dir.into(); | ||||||
|  |         let entry = pathbuf.as_path().join(ENTRY); | ||||||
|  |         match Entry::get(entry.clone()).await { | ||||||
|  |             Ok(_) => (), | ||||||
|  |             Err(_) => { | ||||||
|  |                 let store = DataType::new("store").unwrap(); | ||||||
|  |                 match Entry::new(entry, store).await { | ||||||
|  |                     Ok(_) => (), | ||||||
|  |                     Err(err) => { | ||||||
|  |                         let mut error = DBError::from_code(ErrorCode::CacheReadWrite); | ||||||
|  |                         error.add_source(err); | ||||||
|  |                         return Err(error); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Ok(Self { | ||||||
|  |             next_id: fn id() -> String { thread_rng().sample_iter(&Alphanumeric).take(64).collect() }, | ||||||
|  |             session: [ENTRY.to_string()].to_vec(), | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn set_session(&mut self, sess: Vec<String>) { | ||||||
|  |         self.session = sess; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async fn new_entry(&self, _name: &str) -> Self { | ||||||
|  |         self.clone() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod datatype { | ||||||
|  |     use super::*; | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn bad_data_type() -> Result<(), DBError> { | ||||||
|  |         let dt = "bufcuss"; | ||||||
|  |         match DataType::new(dt) { | ||||||
|  |             Ok(_) => Err(DBError::new("should have produced an error")), | ||||||
|  |             Err(err) => match err.code { | ||||||
|  |                 ErrorCode::DataTypeIncorrect(value) => { | ||||||
|  |                     assert_eq!(value, dt, "Incorrect input value"); | ||||||
|  |                     Ok(()) | ||||||
|  |                 } | ||||||
|  |                 _ => { | ||||||
|  |                     let mut error = DBError::new("incorrect error"); | ||||||
|  |                     error.add_source(err); | ||||||
|  |                     Err(error) | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn create_store() -> Result<(), DBError> { | ||||||
|  |         match DataType::new("store") { | ||||||
|  |             Ok(dt) => match dt { | ||||||
|  |                 DataType::DBMap(_) => Ok(()), | ||||||
|  |                 _ => Err(DBError::new("incorrect data type")), | ||||||
|  |             }, | ||||||
|  |             Err(err) => Err(err), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn create_database() -> Result<(), DBError> { | ||||||
|  |         match DataType::new("database") { | ||||||
|  |             Ok(dt) => match dt { | ||||||
|  |                 DataType::TableMap(_) => Ok(()), | ||||||
|  |                 _ => Err(DBError::new("incorrect data type")), | ||||||
|  |             }, | ||||||
|  |             Err(err) => Err(err), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod datatype_sesssion { | ||||||
|  |     use super::*; | ||||||
|  |  | ||||||
|  |     #[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 new_database_bytes() { | ||||||
|  |         let db = DataType::new("database").unwrap(); | ||||||
|  |         let mut expected = "TableMap".as_bytes().to_vec(); | ||||||
|  |         expected.push(0); | ||||||
|  |         assert_eq!(db.to_bytes(), expected); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn read_empty_database() { | ||||||
|  |         let dt = DataType::new("database").unwrap(); | ||||||
|  |         let data = dt.to_bytes(); | ||||||
|  |         let mut feed = data.iter(); | ||||||
|  |         match DataType::from_bytes(&mut feed).unwrap() { | ||||||
|  |             DataType::TableMap(_) => (), | ||||||
|  |             _ => assert!(false, "Incorrect data type"), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[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 db { | ||||||
|  |     use super::*; | ||||||
|  |     use async_std::fs::write; | ||||||
|  |     use std::error::Error; | ||||||
|  |     use tempfile::tempdir; | ||||||
|  |  | ||||||
|  |     #[async_std::test] | ||||||
|  |     async fn create() { | ||||||
|  |         let dir = tempdir().unwrap(); | ||||||
|  |         let mtt = MoreThanText::new(dir.path()).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::<String>::new() | ||||||
|  |         ); | ||||||
|  |         let sess = [ENTRY]; | ||||||
|  |         assert_eq!(mtt.session, sess); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[async_std::test] | ||||||
|  |     async fn entry_failure() -> Result<(), DBError> { | ||||||
|  |         let dir = tempdir().unwrap(); | ||||||
|  |         let path = dir.path().join("bad").join("path"); | ||||||
|  |         match MoreThanText::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() -> Result<(), DBError> { | ||||||
|  |         let dir = tempdir().unwrap(); | ||||||
|  |         let data = DataType::new("store").unwrap(); | ||||||
|  |         Entry::new(dir.path().join(ENTRY), data.clone()) | ||||||
|  |             .await | ||||||
|  |             .unwrap(); | ||||||
|  |         match MoreThanText::new(dir.path()).await { | ||||||
|  |             Ok(_) => Ok(()), | ||||||
|  |             Err(err) => Err(err), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[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 MoreThanText::new(dir.path()).await { | ||||||
|  |             Ok(_) => Err(DBError::new("should have errored")), | ||||||
|  |             Err(_) => Ok(()), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[async_std::test] | ||||||
|  |     async fn set_session() { | ||||||
|  |         let dir = tempdir().unwrap(); | ||||||
|  |         let mut mtt = MoreThanText::new(dir.path()).await.unwrap(); | ||||||
|  |         let sess = ["different".to_string()]; | ||||||
|  |         mtt.set_session(sess.to_vec()); | ||||||
|  |         assert_eq!(mtt.session, sess); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[async_std::test] | ||||||
|  |     async fn add_a_database() { | ||||||
|  |         let dir = tempdir().unwrap(); | ||||||
|  |         let mtt = MoreThanText::new(dir.path()).await.unwrap(); | ||||||
|  |         mtt.new_entry("wilbur").await; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,435 +1,101 @@ | |||||||
| mod database; | use async_std::{ | ||||||
| mod entry; |     channel::{unbounded, Sender}, | ||||||
| mod error; |     path::PathBuf, | ||||||
| mod store; |     task::spawn, | ||||||
|  | }; | ||||||
| use async_std::path::PathBuf; | use std::{error::Error, fmt}; | ||||||
| use database::Database; |  | ||||||
| use entry::Entry; |  | ||||||
| use error::{DBError, ErrorCode}; |  | ||||||
| use rand::{distributions::Alphanumeric, thread_rng, Rng}; |  | ||||||
| use std::{slice, str}; |  | ||||||
| use store::Store; |  | ||||||
|  |  | ||||||
| const ENTRY: &str = "EntryPoint"; | const ENTRY: &str = "EntryPoint"; | ||||||
|  |  | ||||||
| trait ID { | #[derive(Debug)] | ||||||
|     fn next(&self) -> String; | pub struct MTTError { | ||||||
|  |     msg: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| trait FileData<F> { | impl MTTError { | ||||||
|     fn to_bytes(&self) -> Vec<u8>; |     fn new<S>(msg: S) -> Self where S: Into<String> { | ||||||
|     fn from_bytes(data: &mut slice::Iter<u8>) -> Result<F, DBError>; |         let text = msg.into(); | ||||||
|  |         Self { | ||||||
|  |             msg: text, | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| trait SessionData { | impl Error for MTTError {} | ||||||
|     fn add(&mut self, key: &str, value: &str, data: &str) -> Result<Vec<String>, DBError>; |  | ||||||
|     fn eq(&self, key: &str, value: &str) -> Result<Vec<String>, DBError>; | impl fmt::Display for MTTError { | ||||||
|     fn list(&self, keys: Vec<&str>) -> Result<Vec<String>, DBError>; |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
|  |         write!(f, "{}", self.msg) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[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); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub enum DataType { | struct Store; | ||||||
|  |  | ||||||
|  | #[derive(Clone)] | ||||||
|  | enum DataType { | ||||||
|     DBMap(Store), |     DBMap(Store), | ||||||
|     TableMap(Database), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl DataType { |  | ||||||
|     fn new(data_type: &str) -> Result<Self, DBError> { |  | ||||||
|         match data_type { |  | ||||||
|             "store" => Ok(DataType::DBMap(Store::new())), |  | ||||||
|             "database" => Ok(DataType::TableMap(Database::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<Vec<String>, DBError> { |  | ||||||
|         match self { |  | ||||||
|             DataType::DBMap(dbs) => dbs.add(key, value, data), |  | ||||||
|             DataType::TableMap(_) => todo!(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn eq(&self, key: &str, value: &str) -> Result<Vec<String>, DBError> { |  | ||||||
|         match self { |  | ||||||
|             DataType::DBMap(dbs) => dbs.eq(key, value), |  | ||||||
|             DataType::TableMap(_) => todo!(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn list(&self, keys: Vec<&str>) -> Result<Vec<String>, DBError> { |  | ||||||
|         match self { |  | ||||||
|             DataType::DBMap(dbs) => dbs.list(keys), |  | ||||||
|             DataType::TableMap(_) => todo!(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl FileData<Self> for DataType { |  | ||||||
|     fn to_bytes(&self) -> Vec<u8> { |  | ||||||
|         let mut output = Vec::new(); |  | ||||||
|         match self { |  | ||||||
|             DataType::DBMap(_) => output.append(&mut "DBMap".as_bytes().to_vec()), |  | ||||||
|             DataType::TableMap(_) => output.append(&mut "TableMap".as_bytes().to_vec()), |  | ||||||
|         } |  | ||||||
|         output.push(0); |  | ||||||
|         match self { |  | ||||||
|             DataType::DBMap(store) => output.append(&mut store.to_bytes()), |  | ||||||
|             DataType::TableMap(_) => (), |  | ||||||
|         } |  | ||||||
|         output |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn from_bytes(data: &mut slice::Iter<u8>) -> Result<Self, DBError> { |  | ||||||
|         let mut header: Vec<u8> = 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), |  | ||||||
|             }, |  | ||||||
|             "TableMap" => Ok(DataType::new("database").unwrap()), |  | ||||||
|             _ => Err(DBError::from_code(ErrorCode::CorruptFile)), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub struct IDs; |  | ||||||
|  |  | ||||||
| impl IDs { |  | ||||||
|     pub fn new() -> Self { |  | ||||||
|         Self {} |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl ID for IDs { |  | ||||||
|     fn next(&self) -> String { |  | ||||||
|         thread_rng().sample_iter(&Alphanumeric).take(64).collect() |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct MoreThanText { | pub struct MoreThanText { | ||||||
|     session: Vec<String>, |     session: Vec<String>, | ||||||
|  |     channel: Sender<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl MoreThanText { | impl MoreThanText { | ||||||
|     pub async fn new<P>(dir: P) -> Result<Self, DBError> |     async fn get_entry(&self, id: String) { | ||||||
|  |         self.channel.send(id).await.unwrap(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub async fn start_db<P>(dir: P) -> Result<MoreThanText, MTTError> | ||||||
| where | where | ||||||
|     P: Into<PathBuf>, |     P: Into<PathBuf>, | ||||||
| { | { | ||||||
|         let pathbuf = dir.into(); |     let data_dir = dir.into(); | ||||||
|         let entry = pathbuf.as_path().join(ENTRY); |     let (s, r) = unbounded(); | ||||||
|         match Entry::get(entry.clone()).await { |     spawn(async move { | ||||||
|             Ok(_) => (), |         loop { | ||||||
|             Err(_) => { |             r.recv().await.unwrap(); | ||||||
|                 let store = DataType::new("store").unwrap(); |  | ||||||
|                 match Entry::new(entry, store).await { |  | ||||||
|                     Ok(_) => (), |  | ||||||
|                     Err(err) => { |  | ||||||
|                         let mut error = DBError::from_code(ErrorCode::CacheReadWrite); |  | ||||||
|                         error.add_source(err); |  | ||||||
|                         return Err(error); |  | ||||||
|         } |         } | ||||||
|                 } |     }); | ||||||
|             } |     Ok(MoreThanText {  | ||||||
|         } |  | ||||||
|         Ok(Self { |  | ||||||
|         session: [ENTRY.to_string()].to_vec(), |         session: [ENTRY.to_string()].to_vec(), | ||||||
|  |         channel: s,  | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
|     fn set_session(&mut self, sess: Vec<String>) { |  | ||||||
|         self.session = sess; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn new_entry(&self, _name: &str) -> Self { |  | ||||||
|         self.clone() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod datatype { | mod db_start_up { | ||||||
|     use super::*; |     use super::*; | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn bad_data_type() -> Result<(), DBError> { |  | ||||||
|         let dt = "bufcuss"; |  | ||||||
|         match DataType::new(dt) { |  | ||||||
|             Ok(_) => Err(DBError::new("should have produced an error")), |  | ||||||
|             Err(err) => match err.code { |  | ||||||
|                 ErrorCode::DataTypeIncorrect(value) => { |  | ||||||
|                     assert_eq!(value, dt, "Incorrect input value"); |  | ||||||
|                     Ok(()) |  | ||||||
|                 } |  | ||||||
|                 _ => { |  | ||||||
|                     let mut error = DBError::new("incorrect error"); |  | ||||||
|                     error.add_source(err); |  | ||||||
|                     Err(error) |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn create_store() -> Result<(), DBError> { |  | ||||||
|         match DataType::new("store") { |  | ||||||
|             Ok(dt) => match dt { |  | ||||||
|                 DataType::DBMap(_) => Ok(()), |  | ||||||
|                 _ => Err(DBError::new("incorrect data type")), |  | ||||||
|             }, |  | ||||||
|             Err(err) => Err(err), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn create_database() -> Result<(), DBError> { |  | ||||||
|         match DataType::new("database") { |  | ||||||
|             Ok(dt) => match dt { |  | ||||||
|                 DataType::TableMap(_) => Ok(()), |  | ||||||
|                 _ => Err(DBError::new("incorrect data type")), |  | ||||||
|             }, |  | ||||||
|             Err(err) => Err(err), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod datatype_sesssion { |  | ||||||
|     use super::*; |  | ||||||
|  |  | ||||||
|     #[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 new_database_bytes() { |  | ||||||
|         let db = DataType::new("database").unwrap(); |  | ||||||
|         let mut expected = "TableMap".as_bytes().to_vec(); |  | ||||||
|         expected.push(0); |  | ||||||
|         assert_eq!(db.to_bytes(), expected); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn read_empty_database() { |  | ||||||
|         let dt = DataType::new("database").unwrap(); |  | ||||||
|         let data = dt.to_bytes(); |  | ||||||
|         let mut feed = data.iter(); |  | ||||||
|         match DataType::from_bytes(&mut feed).unwrap() { |  | ||||||
|             DataType::TableMap(_) => (), |  | ||||||
|             _ => assert!(false, "Incorrect data type"), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[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 ids { |  | ||||||
|     use super::*; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn get_next() { |  | ||||||
|         let ids = IDs::new(); |  | ||||||
|         let mut holder: Vec<String> = Vec::new(); |  | ||||||
|         for _ in 0..10 { |  | ||||||
|             let id = ids.next(); |  | ||||||
|             assert!( |  | ||||||
|                 !holder.contains(&id), |  | ||||||
|                 "No duplicates: found {} in {:?}", |  | ||||||
|                 id, |  | ||||||
|                 holder |  | ||||||
|             ); |  | ||||||
|             holder.push(id); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod db { |  | ||||||
|     use super::*; |  | ||||||
|     use async_std::fs::write; |  | ||||||
|     use std::error::Error; |  | ||||||
|     use tempfile::tempdir; |     use tempfile::tempdir; | ||||||
|  |  | ||||||
|     #[async_std::test] |     #[async_std::test] | ||||||
|     async fn create() { |     async fn initial_session() { | ||||||
|         let dir = tempdir().unwrap(); |         let dir = tempdir().unwrap(); | ||||||
|         let mtt = MoreThanText::new(dir.path()).await.unwrap(); |         let mtt = start_db(dir.path()).await.unwrap(); | ||||||
|         let epoint = dir.path().join(ENTRY); |         assert_eq!(mtt.session, [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::<String>::new() |  | ||||||
|         ); |  | ||||||
|         let sess = [ENTRY]; |  | ||||||
|         assert_eq!(mtt.session, sess); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[async_std::test] |  | ||||||
|     async fn entry_failure() -> Result<(), DBError> { |  | ||||||
|         let dir = tempdir().unwrap(); |  | ||||||
|         let path = dir.path().join("bad").join("path"); |  | ||||||
|         match MoreThanText::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() -> Result<(), DBError> { |  | ||||||
|         let dir = tempdir().unwrap(); |  | ||||||
|         let data = DataType::new("store").unwrap(); |  | ||||||
|         Entry::new(dir.path().join(ENTRY), data.clone()) |  | ||||||
|             .await |  | ||||||
|             .unwrap(); |  | ||||||
|         match MoreThanText::new(dir.path()).await { |  | ||||||
|             Ok(_) => Ok(()), |  | ||||||
|             Err(err) => Err(err), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[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 MoreThanText::new(dir.path()).await { |  | ||||||
|             Ok(_) => Err(DBError::new("should have errored")), |  | ||||||
|             Err(_) => Ok(()), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[async_std::test] |  | ||||||
|     async fn set_session() { |  | ||||||
|         let dir = tempdir().unwrap(); |  | ||||||
|         let mut mtt = MoreThanText::new(dir.path()).await.unwrap(); |  | ||||||
|         let sess = ["different".to_string()]; |  | ||||||
|         mtt.set_session(sess.to_vec()); |  | ||||||
|         assert_eq!(mtt.session, sess); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[async_std::test] |  | ||||||
|     async fn add_a_database() { |  | ||||||
|         let dir = tempdir().unwrap(); |  | ||||||
|         let mtt = MoreThanText::new(dir.path()).await.unwrap(); |  | ||||||
|         mtt.new_entry("wilbur").await; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user