morethantext-web/src/morethantext/mod.rs

187 lines
4.6 KiB
Rust

mod cache;
mod database;
mod error;
mod store;
mod table;
use async_std::{
channel::{unbounded, Sender},
path::PathBuf,
task::spawn,
};
use cache::Cache;
use database::Database;
use error::{ErrorCode, MTTError};
use store::Store;
use table::Table;
const ENTRY: &str = "EntryPoint";
#[derive(Debug)]
pub struct ToCacheMsg<D> {
data: D,
result: Sender<FromCache>,
}
#[derive(Debug)]
pub enum ToCache {
Get(ToCacheMsg<String>),
Commit(ToCacheMsg<Store>),
}
#[derive(Clone, Debug)]
pub enum FromCache {
Ok,
Str(Store),
DB(Database),
Error(MTTError),
}
#[derive(Clone, Debug)]
pub struct Data<D> {
id: Option<String>,
data: Option<D>,
}
impl<D> Data<D> {
fn from_id<S>(id: S) -> Self
where
S: Into<String>,
{
Self {
id: Some(id.into()),
data: None,
}
}
fn from_data(data: D) -> Self {
Self {
id: None,
data: Some(data),
}
}
}
#[derive(Clone)]
pub struct MoreThanText {
to_cache: Sender<ToCache>,
entry: Data<Store>,
}
impl MoreThanText {
fn new(to_cache: Sender<ToCache>) -> Self {
Self {
to_cache: to_cache,
entry: Data::from_id(ENTRY),
}
}
async fn session(&self) -> Result<Store, MTTError> {
let (s, r) = unbounded();
let msg = ToCacheMsg {
data: ENTRY.to_string(),
result: s,
};
self.to_cache.send(ToCache::Get(msg)).await.unwrap();
match r.recv().await.unwrap() {
FromCache::Str(store) => Ok(store),
FromCache::Error(err) => Err(err),
_ => unreachable!(),
}
}
async fn commit(&self, store: Store) -> Result<(), MTTError> {
let (s, r) = unbounded();
let msg = ToCacheMsg {
data: store,
result: s,
};
self.to_cache.send(ToCache::Commit(msg)).await.unwrap();
match r.recv().await.unwrap() {
FromCache::Ok => Ok(()),
FromCache::Error(err) => Err(err),
_ => unreachable!(),
}
}
}
#[cfg(test)]
mod mtt {
use super::*;
use tempfile::tempdir;
#[async_std::test]
async fn create_new() {
let dir = tempdir().unwrap();
let mtt = start_db(dir.path()).await.unwrap();
assert_eq!(mtt.entry.id, Some(ENTRY.to_string()));
assert!(mtt.entry.data.is_none());
let store = mtt.session().await.unwrap();
let expected: Vec<String> = Vec::new();
assert_eq!(store.list(), expected);
}
#[async_std::test]
async fn commit_db() {
let dir = tempdir().unwrap();
let db = "fred";
let mtt = start_db(dir.path()).await.unwrap();
let mut store = mtt.session().await.unwrap();
store.add(db).unwrap();
mtt.commit(store).await.unwrap();
let store2 = mtt.session().await.unwrap();
assert_eq!(store2.list(), [db]);
}
#[async_std::test]
async fn commit_from_multiple_sources() {
let dir = tempdir().unwrap();
let mtt1 = start_db(dir.path()).await.unwrap();
let mtt2 = mtt1.clone();
let db1 = "first";
let db2 = "second";
let mut store1 = mtt1.session().await.unwrap();
let mut store2 = mtt2.session().await.unwrap();
store1.add(db1).unwrap();
store2.add(db2).unwrap();
mtt1.commit(store1).await.unwrap();
mtt2.commit(store2).await.unwrap();
let output = mtt1.session().await.unwrap();
assert_eq!(output.list(), [db1, db2]);
}
#[async_std::test]
async fn fail_on_duplicates() {
let dir = tempdir().unwrap();
let mtt1 = start_db(dir.path()).await.unwrap();
let mtt2 = mtt1.clone();
let name = "unique_only";
let mut store1 = mtt1.session().await.unwrap();
let mut store2 = mtt2.session().await.unwrap();
store1.add(name).unwrap();
store2.add(name).unwrap();
mtt1.commit(store1).await.unwrap();
let output = mtt2.commit(store2).await;
match output {
Ok(_) => assert!(false, "Should have returned an error"),
Err(err) => match err.code {
ErrorCode::DuplicateDatabase(_) => (),
_ => assert!(false, "{:?} is not ErrorCode::DuplicateDatabase", err.code),
},
}
}
}
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 mut cache = Cache::new(path).await;
cache.listen(r).await;
});
Ok(MoreThanText::new(s))
}