pub mod error; use async_std::sync::{Arc, RwLock}; use error::DBError; use pest::Parser; use std::collections::HashMap; enum Ast { Script, Command, Action, Object, Name, } #[derive(Parser)] #[grammar = "morethantext/mttsql.pest"] struct MTTSQL; #[derive(Clone)] pub struct MoreThanText { databases: Arc>>, } impl MoreThanText { pub async fn new() -> Self { Self { databases: Arc::new(RwLock::new(HashMap::new())), } } pub async fn execute(&self, script: &str) -> Result<(), DBError> { match MTTSQL::parse(Rule::file, script) { Ok(mut commands) => { let pair = commands.next().unwrap(); match pair.as_rule() { Rule::script => Ast::Script, Rule::command => Ast::Command, Rule::action => Ast::Action, Rule::object => Ast::Object, Rule::name => Ast::Name, Rule::char | Rule::whitespace | Rule::file | Rule::EOI => unreachable!(), }; Ok(()) } Err(err) => { let mut error = DBError::new("script parsing error"); error.add_source(err); Err(error) } } } async fn create_database(&self, name: &str) -> Result<(), DBError> { let mut databases = self.databases.write().await; match databases.get(name) { Some(_) => Err(DBError::new("duplicate database name")), None => { let db = Database::new().await; databases.insert(name.to_string(), db); Ok(()) } } } async fn use_database(&self, name: &str) -> Result<(), DBError> { let databases = self.databases.read().await; match databases.get(name) { Some(_) => Ok(()), None => Err(DBError::new("database name not found")), } } } struct Database; impl Database { async fn new() -> Self { Self {} } } #[cfg(test)] mod engine_functions { use super::*; #[async_std::test] async fn create_database() { let mtt = MoreThanText::new().await; mtt.create_database("smith").await.unwrap(); } #[async_std::test] async fn database_names_must_be_unique() -> Result<(), DBError> { let mtt = MoreThanText::new().await; let msg = "duplicate database name"; mtt.create_database("john").await.unwrap(); match mtt.create_database("john").await { Ok(_) => Err(DBError::new("Duplicate names should cause error")), Err(err) => { if err.to_string() == msg { Ok(()) } else { Err(DBError::new(format!( "incorrect err message: got: '{}' want: '{}'", err.to_string(), msg ))) } } } } #[async_std::test] async fn use_database() -> Result<(), DBError> { let mtt = MoreThanText::new().await; let dbname = "Johnson"; mtt.create_database(dbname).await.unwrap(); mtt.use_database(dbname).await.unwrap(); Ok(()) } #[async_std::test] async fn use_missing_database() -> Result<(), DBError> { let error = "database name not found"; let mtt = MoreThanText::new().await; match mtt.use_database("ssmith").await { Ok(_) => Err(DBError::new("Should raise database missing error")), Err(err) => { if err.to_string() == error { Ok(()) } else { Err(DBError::new(format!( "Incorrect error message: Got '{}' Want '{}'", err.to_string(), error ))) } } } } } #[cfg(test)] mod database_functions { use super::*; #[async_std::test] async fn new_database() { Database::new().await; } } #[cfg(test)] mod mtt_commands { use super::*; #[async_std::test] async fn create_database() { let mtt = MoreThanText::new().await; mtt.execute("create database fred;").await.unwrap(); } #[async_std::test] async fn unsuccessful_parse() -> Result<(), DBError> { let msg = "script parsing error"; let mtt = MoreThanText::new().await; match mtt.execute("#$%^&").await { Ok(_) => Err(DBError::new("Should show a parse failure.")), Err(err) => { if err.to_string() == msg { Ok(()) } else { Err(DBError::new(format!( "Error message is incorrect: Got: '{}' Want: '{}'", err.to_string(), msg ))) } } } } }