Setting up for data storage.
This commit is contained in:
parent
9f368b0b95
commit
50a4431316
30
src/morethantext/cache.rs
Normal file
30
src/morethantext/cache.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use async_std::{channel::Receiver, path::PathBuf};
|
||||||
|
|
||||||
|
pub struct Cache;
|
||||||
|
|
||||||
|
impl Cache {
|
||||||
|
pub async fn new<P>(_dir: P) -> Self
|
||||||
|
where
|
||||||
|
P: Into<PathBuf>,
|
||||||
|
{
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn listen(&self, listener: Receiver<String>) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,95 +1,57 @@
|
|||||||
use async_std::path::PathBuf;
|
|
||||||
use std::{error::Error, fmt};
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ErrorCode {
|
pub enum ErrorCode {
|
||||||
|
// General
|
||||||
Undefined(String),
|
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 {
|
impl fmt::Display for ErrorCode {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ErrorCode::Undefined(msg) => write!(f, "{}", msg),
|
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",
|
mod errorcodes {
|
||||||
path.file_name().unwrap().to_str().unwrap()
|
use super::*;
|
||||||
),
|
|
||||||
ErrorCode::EntryWriteFailure(path) => write!(
|
const ITEMS: [&str; 2] = ["one", "two"];
|
||||||
f,
|
|
||||||
"entry '{}' write failure",
|
#[test]
|
||||||
path.file_name().unwrap().to_str().unwrap()
|
fn undefined_display() {
|
||||||
),
|
for item in ITEMS {
|
||||||
ErrorCode::EntryReadFailure(path) => write!(
|
let err = ErrorCode::Undefined(item.to_string());
|
||||||
f,
|
assert_eq!(err.to_string(), item);
|
||||||
"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)]
|
#[derive(Debug)]
|
||||||
pub struct DBError {
|
pub struct MTTError {
|
||||||
pub code: ErrorCode,
|
code: ErrorCode,
|
||||||
src: Option<Box<dyn Error + 'static>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DBError {
|
impl MTTError {
|
||||||
pub fn new<S>(msg: S) -> Self
|
fn new<S>(msg: S) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
|
let text = msg.into();
|
||||||
Self {
|
Self {
|
||||||
code: ErrorCode::Undefined(msg.into()),
|
code: ErrorCode::Undefined(text),
|
||||||
src: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_code(code: ErrorCode) -> Self {
|
fn from_code(code: ErrorCode) -> Self {
|
||||||
Self {
|
Self { code: code }
|
||||||
code: code,
|
|
||||||
src: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_source<E>(&mut self, src: E)
|
impl Error for MTTError {}
|
||||||
where
|
|
||||||
E: Error + 'static,
|
|
||||||
{
|
|
||||||
self.src = Some(Box::new(src));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for DBError {
|
impl fmt::Display for MTTError {
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
||||||
match &self.src {
|
|
||||||
Some(err) => Some(err.as_ref()),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for DBError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.code)
|
write!(f, "{}", self.code)
|
||||||
}
|
}
|
||||||
@ -100,160 +62,28 @@ mod errors {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_str() {
|
fn create_with_str() {
|
||||||
let msg = "something happened";
|
let msgs = ["one", "two"];
|
||||||
let err = DBError::new(msg);
|
for msg in msgs {
|
||||||
assert!(
|
let err = MTTError::new(msg);
|
||||||
err.to_string() == msg,
|
|
||||||
"Got: {} -- Want: {}",
|
|
||||||
err.to_string(),
|
|
||||||
msg
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
err.source().is_none(),
|
|
||||||
"Error should initialize with no source."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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);
|
|
||||||
assert_eq!(err.to_string(), msg);
|
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<PathBuf> {
|
|
||||||
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]
|
#[test]
|
||||||
fn incorrect_data_type() {
|
fn create_with_string() {
|
||||||
for item in ITEMS {
|
let msg = "three";
|
||||||
let err = ErrorCode::DataTypeIncorrect(item.to_string());
|
let err = MTTError::new(msg.to_string());
|
||||||
assert_eq!(
|
assert_eq!(err.to_string(), msg);
|
||||||
err.to_string(),
|
|
||||||
format!("data type '{}' is not valid", item)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn corrupt_file() {
|
fn create_from_code() {
|
||||||
assert_eq!(ErrorCode::CorruptFile.to_string(), "corrupt file");
|
let code = ErrorCode::Undefined("oops".to_string());
|
||||||
}
|
let err = MTTError::from_code(code);
|
||||||
|
match err.code {
|
||||||
#[test]
|
ErrorCode::Undefined(_) => (),
|
||||||
fn entry_exists() {
|
_ => assert!(false, "{:?} is not undefined", err.code),
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
640
src/morethantext/mod-3.rs
Normal file
640
src/morethantext/mod-3.rs
Normal file
@ -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<S>(msg: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
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<D> {
|
||||||
|
id: Option<String>,
|
||||||
|
data: Option<D>,
|
||||||
|
// delete: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Storage<D> {
|
||||||
|
fn from_id<S>(id: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
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<String, Storage<Database>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
data: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_new<S>(&mut self, name: S) -> Result<(), MTTError>
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
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<Database>> {
|
||||||
|
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<String, DataType>),
|
||||||
|
Error(MTTError),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CacheQuery {
|
||||||
|
ids: Vec<String>,
|
||||||
|
reply: Sender<FromCache>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CacheCommit {
|
||||||
|
reply: Sender<FromCache>,
|
||||||
|
data: DataType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CacheCommit {
|
||||||
|
fn new(data: DataType, channel: Sender<FromCache>) -> Result<Self, MTTError> {
|
||||||
|
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<String>,
|
||||||
|
cache: Sender<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MoreThanText {
|
||||||
|
async fn new(cache: Sender<Vec<String>>) -> Result<Self, MTTError> {
|
||||||
|
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<P>(_dir: P) -> Result<Self, MTTError>
|
||||||
|
where
|
||||||
|
P: Into<PathBuf>,
|
||||||
|
{
|
||||||
|
Ok(Self {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn query(&self, qry: &Vec<String>) -> Result<HashMap<String, DataType>, 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<ToCache>) {
|
||||||
|
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<P>(dir: P) -> Sender<ToCache>
|
||||||
|
where
|
||||||
|
P: Into<PathBuf>,
|
||||||
|
{
|
||||||
|
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<ToCache>) -> 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<P>(_dir: P) -> Result<MoreThanText, MTTError>
|
||||||
|
where
|
||||||
|
P: Into<PathBuf>,
|
||||||
|
{
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,650 +1,55 @@
|
|||||||
|
mod cache;
|
||||||
|
mod error;
|
||||||
|
|
||||||
use async_std::{
|
use async_std::{
|
||||||
channel::{unbounded, Receiver, Sender},
|
channel::{unbounded, Sender},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
task::spawn,
|
task::spawn,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, error::Error, fmt};
|
use cache::Cache;
|
||||||
|
use error::{ErrorCode, MTTError};
|
||||||
const ENTRY: &str = "EntryPoint";
|
|
||||||
|
|
||||||
trait Requests {
|
|
||||||
fn add(kind: &str, key: &str, value: Storage) -> Result<(), MTTError> {
|
|
||||||
Err(MTTError::new("not supported"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get() -> Vec<Storage> {
|
|
||||||
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<S>(msg: S) -> Self
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
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)]
|
#[derive(Clone, Debug)]
|
||||||
struct Storage {
|
struct Data {
|
||||||
id: Option<String>,
|
id: String,
|
||||||
data: Option<DataType>,
|
|
||||||
// delete: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Storage {
|
|
||||||
fn from_id<S>(id: S) -> Self
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
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<String, Storage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Store {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
data: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_new<S>(&mut self, name: S) -> Result<(), MTTError>
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
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<String, DataType>),
|
|
||||||
Error(MTTError),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CacheQuery {
|
|
||||||
ids: Vec<String>,
|
|
||||||
reply: Sender<FromCache>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CacheCommit {
|
|
||||||
reply: Sender<FromCache>,
|
|
||||||
data: DataType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CacheCommit {
|
|
||||||
fn new(data: DataType, channel: Sender<FromCache>) -> Result<Self, MTTError> {
|
|
||||||
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)]
|
#[derive(Clone)]
|
||||||
pub struct MoreThanText {
|
pub struct MoreThanText {
|
||||||
session: Vec<String>,
|
to_cache: Sender<String>,
|
||||||
cache: Sender<Vec<String>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MoreThanText {
|
impl MoreThanText {
|
||||||
async fn new(cache: Sender<Vec<String>>) -> Result<Self, MTTError> {
|
fn new(to_cache: Sender<String>) -> Self {
|
||||||
Ok(Self {
|
Self { to_cache: to_cache }
|
||||||
session: [ENTRY.to_string()].to_vec(),
|
}
|
||||||
cache: cache,
|
|
||||||
})
|
async fn session(&self) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod mtt {
|
mod mtt {
|
||||||
use super::*;
|
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<P>(_dir: P) -> Result<Self, MTTError>
|
|
||||||
where
|
|
||||||
P: Into<PathBuf>,
|
|
||||||
{
|
|
||||||
Ok(Self {})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn query(&self, qry: &Vec<String>) -> Result<HashMap<String, DataType>, 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<ToCache>) {
|
|
||||||
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<P>(dir: P) -> Sender<ToCache>
|
|
||||||
where
|
|
||||||
P: Into<PathBuf>,
|
|
||||||
{
|
|
||||||
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<ToCache>) -> 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<P>(_dir: P) -> Result<MoreThanText, MTTError>
|
|
||||||
where
|
|
||||||
P: Into<PathBuf>,
|
|
||||||
{
|
|
||||||
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;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn initial_session() {
|
async fn create_new() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let mtt = start_db(dir.path()).await.unwrap();
|
let mtt = start_db(dir.path()).await.unwrap();
|
||||||
assert_eq!(mtt.session, [ENTRY]);
|
mtt.session().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn start_db<P>(dir: P) -> Result<MoreThanText, MTTError>
|
||||||
|
where
|
||||||
|
P: Into<PathBuf>,
|
||||||
|
{
|
||||||
|
let path = dir.into();
|
||||||
|
let (s, r) = unbounded();
|
||||||
|
spawn(async move {
|
||||||
|
let cache = Cache::new(path).await;
|
||||||
|
cache.listen(r).await;
|
||||||
|
});
|
||||||
|
Ok(MoreThanText::new(s))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user