2023-04-04 09:59:29 -04:00
|
|
|
use async_std::{
|
2023-04-08 15:04:04 -04:00
|
|
|
channel::{unbounded, Receiver, Sender},
|
2023-04-04 09:59:29 -04:00
|
|
|
path::PathBuf,
|
|
|
|
task::spawn,
|
|
|
|
};
|
2023-04-08 15:04:04 -04:00
|
|
|
use std::{collections::HashMap, error::Error, fmt};
|
2023-04-04 09:59:29 -04:00
|
|
|
|
|
|
|
const ENTRY: &str = "EntryPoint";
|
|
|
|
|
2023-04-04 12:36:10 -04:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum ErrorCode {
|
|
|
|
Undefined(String),
|
|
|
|
IncorrectDataType(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for ErrorCode {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
ErrorCode::Undefined(msg) => write!(f, "{}", msg),
|
|
|
|
ErrorCode::IncorrectDataType(item) => write!(f, "data type '{}' does not exist", item),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 incorrect_data_type() {
|
|
|
|
for item in ITEMS {
|
|
|
|
let err = ErrorCode::IncorrectDataType(item.to_string());
|
|
|
|
assert_eq!(
|
|
|
|
err.to_string(),
|
|
|
|
format!("data type '{}' does not exist", item)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-04 09:59:29 -04:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MTTError {
|
2023-04-04 12:36:10 -04:00
|
|
|
code: ErrorCode,
|
2023-04-04 09:59:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MTTError {
|
2023-04-04 12:36:10 -04:00
|
|
|
fn new<S>(msg: S) -> Self
|
|
|
|
where
|
|
|
|
S: Into<String>,
|
|
|
|
{
|
2023-04-04 09:59:29 -04:00
|
|
|
let text = msg.into();
|
|
|
|
Self {
|
2023-04-04 12:36:10 -04:00
|
|
|
code: ErrorCode::Undefined(text),
|
2023-04-04 09:59:29 -04:00
|
|
|
}
|
|
|
|
}
|
2023-04-04 12:36:10 -04:00
|
|
|
|
|
|
|
fn from_code(code: ErrorCode) -> Self {
|
|
|
|
Self { code: code }
|
|
|
|
}
|
2023-04-04 09:59:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Error for MTTError {}
|
|
|
|
|
|
|
|
impl fmt::Display for MTTError {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2023-04-04 12:36:10 -04:00
|
|
|
write!(f, "{}", self.code)
|
2023-04-04 09:59:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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);
|
|
|
|
}
|
2023-04-04 12:36:10 -04:00
|
|
|
|
|
|
|
#[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_from_different_code() {
|
|
|
|
let code = ErrorCode::IncorrectDataType("nuts".to_string());
|
|
|
|
let err = MTTError::from_code(code);
|
|
|
|
match err.code {
|
|
|
|
ErrorCode::IncorrectDataType(_) => (),
|
|
|
|
_ => assert!(false, "{:?} is not incorrect data type", err.code),
|
|
|
|
}
|
|
|
|
}
|
2023-04-04 09:59:29 -04:00
|
|
|
}
|
|
|
|
|
2023-04-04 12:36:10 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2023-04-04 09:59:29 -04:00
|
|
|
struct Store;
|
|
|
|
|
2023-04-04 12:36:10 -04:00
|
|
|
impl Store {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod stores {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn create() {
|
|
|
|
Store::new();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-10 07:52:53 -04:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
struct Database;
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod databases {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn create() {
|
|
|
|
Database::new();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Database {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-04 12:36:10 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2023-04-04 09:59:29 -04:00
|
|
|
enum DataType {
|
|
|
|
DBMap(Store),
|
2023-04-10 07:52:53 -04:00
|
|
|
TableMap(Database),
|
2023-04-04 09:59:29 -04:00
|
|
|
}
|
|
|
|
|
2023-04-04 12:36:10 -04:00
|
|
|
impl DataType {
|
2023-04-10 07:52:53 -04:00
|
|
|
fn new(dtype: &str) -> DataType {
|
2023-04-04 12:36:10 -04:00
|
|
|
match dtype {
|
2023-04-10 07:52:53 -04:00
|
|
|
"store" => Self::DBMap(Store::new()),
|
|
|
|
"database" => Self::TableMap(Database::new()),
|
|
|
|
_ => unreachable!(),
|
2023-04-04 12:36:10 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod datatypes {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
2023-04-10 07:52:53 -04:00
|
|
|
fn create_store() {
|
|
|
|
let dtype = DataType::new("store");
|
|
|
|
match dtype {
|
|
|
|
DataType::DBMap(_) => (),
|
|
|
|
_ => assert!(false, "{:?} is not incorrect data type", dtype),
|
2023-04-04 12:36:10 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2023-04-10 07:52:53 -04:00
|
|
|
fn create_database() {
|
|
|
|
let dtype = DataType::new("database");
|
2023-04-04 12:36:10 -04:00
|
|
|
match dtype {
|
2023-04-10 07:52:53 -04:00
|
|
|
DataType::TableMap(_) => (),
|
2023-04-04 12:36:10 -04:00
|
|
|
_ => assert!(false, "{:?} is not incorrect data type", dtype),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-08 15:04:04 -04:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum FromCache {
|
2023-04-11 08:12:41 -04:00
|
|
|
Ok,
|
2023-04-08 15:04:04 -04:00
|
|
|
Data(HashMap<String, DataType>),
|
|
|
|
Error(MTTError),
|
|
|
|
}
|
|
|
|
|
|
|
|
struct CacheQuery {
|
|
|
|
ids: Vec<String>,
|
|
|
|
reply: Sender<FromCache>,
|
|
|
|
}
|
|
|
|
|
2023-04-11 08:12:41 -04:00
|
|
|
struct CacheCommit {
|
|
|
|
reply: Sender<FromCache>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CacheCommit {
|
|
|
|
fn new(channel: Sender<FromCache>) -> Self {
|
|
|
|
Self { reply: channel }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mod commits {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn create() {
|
|
|
|
let (s, _) = unbounded();
|
|
|
|
CacheCommit::new(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-08 15:04:04 -04:00
|
|
|
enum ToCache {
|
|
|
|
Query(CacheQuery),
|
2023-04-11 08:12:41 -04:00
|
|
|
Commit(CacheCommit),
|
2023-04-08 15:04:04 -04:00
|
|
|
}
|
|
|
|
|
2023-04-04 09:59:29 -04:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct MoreThanText {
|
|
|
|
session: Vec<String>,
|
2023-04-08 15:04:04 -04:00
|
|
|
cache: Sender<Vec<String>>,
|
2023-04-04 09:59:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MoreThanText {
|
2023-04-08 15:04:04 -04:00
|
|
|
async fn new(cache: Sender<Vec<String>>) -> Result<Self, MTTError> {
|
|
|
|
Ok(Self {
|
|
|
|
session: [ENTRY.to_string()].to_vec(),
|
|
|
|
cache: cache,
|
|
|
|
})
|
2023-04-04 09:59:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-04 12:36:10 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
mod mtt {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[async_std::test]
|
|
|
|
async fn create() {
|
2023-04-08 15:04:04 -04:00
|
|
|
let (s, _) = unbounded();
|
|
|
|
let mtt = MoreThanText::new(s).await.unwrap();
|
|
|
|
assert_eq!(mtt.session, [ENTRY]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Cache {
|
|
|
|
channel: Receiver<ToCache>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Cache {
|
|
|
|
async fn new<P>(_dir: P, channel: Receiver<ToCache>) -> Result<Self, MTTError>
|
|
|
|
where
|
|
|
|
P: Into<PathBuf>,
|
|
|
|
{
|
|
|
|
Ok(Self { channel: channel })
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn start(&self) {
|
|
|
|
loop {
|
|
|
|
match self.channel.recv().await.unwrap() {
|
|
|
|
ToCache::Query(data) => {
|
|
|
|
for id in data.ids {
|
|
|
|
if id == ENTRY {
|
|
|
|
let mut holder = HashMap::new();
|
2023-04-10 07:52:53 -04:00
|
|
|
holder.insert(ENTRY.to_string(), DataType::new("store"));
|
2023-04-08 15:04:04 -04:00
|
|
|
data.reply.send(FromCache::Data(holder)).await.unwrap();
|
|
|
|
} else {
|
|
|
|
data.reply
|
|
|
|
.send(FromCache::Error(MTTError::new("fred")))
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-04-11 08:12:41 -04:00
|
|
|
ToCache::Commit(data) => data.reply.send(FromCache::Ok).await.unwrap(),
|
2023-04-08 15:04:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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, r).await.unwrap();
|
|
|
|
cache.start().await;
|
|
|
|
});
|
|
|
|
s
|
|
|
|
}
|
|
|
|
|
2023-04-09 09:45:39 -04:00
|
|
|
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();
|
2023-04-11 08:12:41 -04:00
|
|
|
let msg = ToCache::Query(CacheQuery { ids: ids, reply: s });
|
2023-04-09 09:45:39 -04:00
|
|
|
channel.send(msg).await.unwrap();
|
|
|
|
r.recv().await.unwrap()
|
|
|
|
}
|
|
|
|
|
2023-04-08 15:04:04 -04:00
|
|
|
#[async_std::test]
|
|
|
|
async fn create() {
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
let s_cache = start_cache(dir.path()).await;
|
2023-04-09 09:45:39 -04:00
|
|
|
let result = send_request(vec![ENTRY], s_cache).await;
|
2023-04-08 15:04:04 -04:00
|
|
|
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;
|
2023-04-09 09:45:39 -04:00
|
|
|
let result = send_request(vec!["bad_id"], s_cache).await;
|
2023-04-08 15:04:04 -04:00
|
|
|
match result {
|
|
|
|
FromCache::Error(_) => (),
|
|
|
|
_ => assert!(false, "{:?} should have been an error.", result),
|
|
|
|
}
|
2023-04-04 12:36:10 -04:00
|
|
|
}
|
2023-04-11 08:12:41 -04:00
|
|
|
|
|
|
|
#[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(s));
|
|
|
|
s_cache.send(msg).await.unwrap();
|
|
|
|
let result = r.recv().await.unwrap();
|
|
|
|
match result {
|
|
|
|
FromCache::Ok => (),
|
|
|
|
_ => assert!(false, "{:?} should have been an error.", result),
|
|
|
|
}
|
|
|
|
}
|
2023-04-04 12:36:10 -04:00
|
|
|
}
|
|
|
|
|
2023-04-08 15:04:04 -04:00
|
|
|
pub async fn start_db<P>(_dir: P) -> Result<MoreThanText, MTTError>
|
2023-04-04 09:59:29 -04:00
|
|
|
where
|
|
|
|
P: Into<PathBuf>,
|
|
|
|
{
|
|
|
|
let (s, r) = unbounded();
|
|
|
|
spawn(async move {
|
|
|
|
loop {
|
|
|
|
r.recv().await.unwrap();
|
|
|
|
}
|
|
|
|
});
|
2023-04-08 15:04:04 -04:00
|
|
|
Ok(MoreThanText::new(s).await.unwrap())
|
2023-04-04 09:59:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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]);
|
|
|
|
}
|
|
|
|
}
|