From a3d8feb37aa4c3eee826fc649ac2d831b238f40b Mon Sep 17 00:00:00 2001 From: Jeff Baskin Date: Tue, 14 Mar 2023 11:32:37 -0400 Subject: [PATCH] Added cache initialization. --- src/morethantext/cache.rs | 230 ++++++++++++++++++++++---------------- src/morethantext/error.rs | 177 ++++++++++++++++++++++++++++- src/morethantext/mod.rs | 2 +- 3 files changed, 307 insertions(+), 102 deletions(-) diff --git a/src/morethantext/cache.rs b/src/morethantext/cache.rs index 184811f..1ee76c0 100644 --- a/src/morethantext/cache.rs +++ b/src/morethantext/cache.rs @@ -1,4 +1,4 @@ -use super::{DBError, FileData, SessionData, Store}; +use super::{DBError, ErrorCode, FileData, SessionData, Store}; use async_std::{ fs::{read, remove_file, write}, path::{Path, PathBuf}, @@ -21,7 +21,9 @@ impl DataType { fn new(data_type: &str) -> Result { match data_type { "store" => Ok(DataType::DBMap(Store::new())), - _ => Err(DBError::new("invalid data type")), + _ => Err(DBError::from_code(ErrorCode::DataTypeIncorrect( + data_type.to_string(), + ))), } } } @@ -74,14 +76,14 @@ impl FileData for DataType { } let header = match str::from_utf8(&header) { Ok(item) => item, - Err(_) => return Err(DBError::new("file corruption")), + 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::new("file corruption")), + _ => Err(DBError::from_code(ErrorCode::CorruptFile)), } } } @@ -99,13 +101,12 @@ impl Entry { { let pathbuf = filename.into(); if pathbuf.as_path().exists().await { - // if Path::new(&filename).exists().await { - return Err(DBError::new("entry already exists")); + return Err(DBError::from_code(ErrorCode::EntryExists(pathbuf))); } else { match write(&pathbuf, data.to_bytes()).await { Ok(_) => (), Err(err) => { - let mut error = DBError::new("failed to write"); + let mut error = DBError::from_code(ErrorCode::EntryWriteFailure(pathbuf)); error.add_source(err); return Err(error); } @@ -126,7 +127,7 @@ impl Entry { let content = match read(&pathbuf).await { Ok(text) => text, Err(err) => { - let mut error = DBError::new("read error"); + let mut error = DBError::from_code(ErrorCode::EntryReadFailure(pathbuf)); error.add_source(err); return Err(error); } @@ -134,7 +135,7 @@ impl Entry { let data = match DataType::from_bytes(&mut content.iter()) { Ok(raw) => raw, Err(err) => { - let mut error = DBError::new("read error"); + let mut error = DBError::from_code(ErrorCode::EntryReadFailure(pathbuf)); error.add_source(err); return Err(error); } @@ -160,7 +161,7 @@ impl Entry { match write(&self.filename, data.to_bytes()).await { Ok(_) => (), Err(err) => { - let mut error = DBError::new("write error"); + let mut error = DBError::from_code(ErrorCode::EntryWriteFailure(self.filename.clone())); error.add_source(err); return Err(error); } @@ -173,7 +174,7 @@ impl Entry { match remove_file(&self.filename).await { Ok(_) => Ok(()), Err(err) => { - let mut error = DBError::new("cannot remove"); + let mut error = DBError::from_code(ErrorCode::EntryDeleteFailure(self.filename.clone())); error.add_source(err); Err(error) } @@ -190,14 +191,19 @@ impl Cache { { let pathbuf = dir.into(); let entry = pathbuf.as_path().join(ENTRY); - let store = DataType::new("store").unwrap(); - match Entry::new(entry, store).await { + match Entry::get(entry.clone()).await { Ok(_) => Ok(Self {}), - Err(err) => { - let mut error = DBError::new("initialization failure"); - error.add_source(err); - Err(error) - } + 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) + }, + } + }, } } } @@ -210,10 +216,10 @@ mod datatype_sesssion { fn invalid_cache_type() -> Result<(), DBError> { match DataType::new("dkhgdl") { Ok(_) => Err(DBError::new("invalid data type should raise an error")), - Err(err) => { - assert_eq!(err.to_string(), "invalid data type"); - Ok(()) - } + Err(err) => match err.code { + ErrorCode::DataTypeIncorrect(_) => Ok(()), + _ => Err(DBError::new("Invalid error code")), + }, } } @@ -291,10 +297,10 @@ mod datatype_file { let mut feed = data.iter(); match DataType::from_bytes(&mut feed) { Ok(_) => Err(DBError::new("should have raised an error")), - Err(err) => { - assert_eq!(err.to_string(), "file corruption"); - Ok(()) - } + Err(err) => match err.code { + ErrorCode::CorruptFile => Ok(()), + _ => Err(DBError::new("incorrect error")), + }, } } @@ -371,16 +377,18 @@ mod entry { 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) => { - assert_eq!(err.to_string(), "failed to write"); - assert!(err.source().is_some(), "Must include the source error."); - assert!(err - .source() - .unwrap() - .to_string() - .contains("could not write to file")); - Ok(()) - } + 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")), + }, } } @@ -398,10 +406,10 @@ mod entry { "Should produce an error for an existing Entry", )) } - Err(err) => { - assert_eq!(err.to_string(), "entry already exists"); - Ok(()) - } + Err(err) => match err.code { + ErrorCode::EntryExists(_) => Ok(()), + _ => Err(DBError::new("incorrect error code")), + }, } } @@ -459,15 +467,17 @@ mod entry { drop(dir); match item.update(data).await { Ok(_) => Err(DBError::new("file writes should return an error")), - Err(err) => { - assert_eq!(err.to_string(), "write error"); - assert!(err.source().is_some(), "Must include the source error."); - assert!(err - .source() - .unwrap() - .to_string() - .contains("could not write to file")); - Ok(()) + 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")), } } } @@ -501,18 +511,20 @@ mod entry { let filename = filepath.to_str().unwrap(); match Entry::get(filename).await { Ok(_) => Err(DBError::new("should have returned an error")), - Err(err) => { - assert_eq!(err.to_string(), "read error"); - 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(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")), } } } @@ -525,18 +537,17 @@ mod entry { write(&filepath, b"jhsdfghlsdf").await.unwrap(); match Entry::get(filename).await { Ok(_) => Err(DBError::new("should have returned an error")), - Err(err) => { - assert_eq!(err.to_string(), "read error"); - assert!(err.source().is_some(), "Error should have a source."); - assert!( - err.source() - .unwrap() - .to_string() - .contains("file corruption"), - "Source Error Message: {}", - err.source().unwrap().to_string() - ); - Ok(()) + 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")), } } } @@ -566,18 +577,20 @@ mod entry { remove_file(filename).await.unwrap(); match item.remove().await { Ok(_) => Err(DBError::new("should have produced an error")), - Err(err) => { - assert_eq!(err.to_string(), "cannot remove"); - 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(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")), } } } @@ -607,23 +620,46 @@ mod cache { } #[async_std::test] - async fn entry_failure() { + async fn entry_failure() -> Result<(), DBError> { let dir = tempdir().unwrap(); let path = dir.path().join("bad").join("path"); match Cache::new(path).await { - Ok(_) => assert!(false, "Should have produced an error."), + Ok(_) => Err(DBError::new("Should have produced an error.")), Err(err) => { - assert_eq!(err.to_string(), "initialization failure"); - assert!(err.source().is_some(), "Error should have a source."); - assert!( - err.source() - .unwrap() - .to_string() - .contains("failed to write"), - "Source Error Message: {}", - err.source().unwrap().to_string() - ); + 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(()), + } + } } diff --git a/src/morethantext/error.rs b/src/morethantext/error.rs index af80adf..46dcad7 100644 --- a/src/morethantext/error.rs +++ b/src/morethantext/error.rs @@ -1,8 +1,56 @@ +use async_std::path::PathBuf; use std::{error::Error, fmt}; +#[derive(Debug)] +pub enum ErrorCode { + 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"), + } + } +} + #[derive(Debug)] pub struct DBError { - msg: String, + pub code: ErrorCode, src: Option>, } @@ -12,7 +60,14 @@ impl DBError { S: Into, { Self { - msg: msg.into(), + code: ErrorCode::Undefined(msg.into()), + src: None, + } + } + + pub fn from_code(code: ErrorCode) -> Self { + Self { + code: code, src: None, } } @@ -36,12 +91,12 @@ impl Error for DBError { impl fmt::Display for DBError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.msg) + write!(f, "{}", self.code) } } #[cfg(test)] -mod create { +mod errors { use super::*; #[test] @@ -76,6 +131,15 @@ mod create { ); } + #[test] + fn using_error_code() { + let msg = "utter failure"; + let code = ErrorCode::Undefined(msg.to_string()); + let err = DBError::from_code(code); + 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"; @@ -88,3 +152,108 @@ mod create { 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"); + } +} diff --git a/src/morethantext/mod.rs b/src/morethantext/mod.rs index b2a4f9b..2f8c807 100644 --- a/src/morethantext/mod.rs +++ b/src/morethantext/mod.rs @@ -8,7 +8,7 @@ use async_std::{ sync::{Arc, Mutex}, task::{sleep, spawn}, }; -use error::DBError; +use error::{DBError, ErrorCode}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use std::{ collections::HashMap,