diff --git a/rust/src/log_handler.rs b/rust/src/log_handler.rs index 8f4cf2c..3d1fbd1 100644 --- a/rust/src/log_handler.rs +++ b/rust/src/log_handler.rs @@ -1,25 +1,27 @@ use chrono::{DateTime, Local}; use sqlx::{ migrate::MigrateDatabase, - query, - query_as, - Row, - sqlite::{ - SqlitePool, - SqlitePoolOptions, - }, - Sqlite, + query, query_as, + sqlite::{SqlitePool, SqlitePoolOptions}, + Row, Sqlite, }; const URL_DATABASE: &str = "log.db"; -const TABLE_TESTS: &str = "tests"; -const TABLE_LOGS: &str = "logs"; -const TEST_NONE: &str = "none"; +pub const TABLE_TESTS: &str = "tests"; +pub const TABLE_TYPES: &str = "log_types"; +pub const TABLE_LOGS: &str = "logs"; +pub const TEST_NONE: &str = "none"; +const TYPE_INFORMATION: &str = "information"; +const TYPE_WARNING: &str = "warning"; +const TYPE_ERROR: &str = "error"; #[derive(Debug, Clone, thiserror::Error)] pub enum Error { #[error("Error in crate `sqlx`: {0}")] Internal(String), + + #[error("failed to convert `{0}` to LogType")] + LogTypeConversion(String), } #[derive(Clone, Debug, sqlx::FromRow)] @@ -27,6 +29,8 @@ pub struct LogRecord { log_id: i32, /// timestamp of the logs creation log_time: DateTime, + /// type of log (information, error..) + log_type: LogType, /// logs affiliated test test_name: String, /// log message @@ -62,37 +66,63 @@ impl Logger { Ok(value) => value, Err(error) => return Err(Error::Internal(error.to_string())), }; - match query(&format!( - "CREATE TABLE IF NOT EXISTS {TABLE_TESTS} ( + let logger = Self { database: db }; + + logger + .query(&format!( + "CREATE TABLE IF NOT EXISTS {TABLE_TESTS} ( test_name VARCHAR(255) NOT NULL UNIQUE, test_time INTEGER DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (test_name) )" - )).execute(&db).await { - Ok(_) => {} - Err(error) => { return Err(Error::Internal(error.to_string())); } - }; - match query(&format!( - "CREATE TABLE IF NOT EXISTS {TABLE_LOGS} ( + )) + .await?; + logger + .query(&format!( + "INSERT OR IGNORE INTO {TABLE_TESTS} (test_name) VALUES('{TEST_NONE}');" + )) + .await?; + + logger + .query(&format!( + "CREATE TABLE IF NOT EXISTS {TABLE_TYPES} ( + log_type VARCHAR(255) NOT NULL UNIQUE, + PRIMARY KEY (log_type) + )" + )) + .await?; + logger + .query(&format!( + "INSERT OR IGNORE INTO {TABLE_TYPES} (log_type) VALUES('{TYPE_INFORMATION}');" + )) + .await?; + logger + .query(&format!( + "INSERT OR IGNORE INTO {TABLE_TYPES} (log_type) VALUES('{TYPE_WARNING}');" + )) + .await?; + logger + .query(&format!( + "INSERT OR IGNORE INTO {TABLE_TYPES} (log_type) VALUES('{TYPE_ERROR}');" + )) + .await?; + + logger + .query(&format!( + "CREATE TABLE IF NOT EXISTS {TABLE_LOGS} ( log_id INTEGER NOT NULL UNIQUE, log_time INTEGER DEFAULT CURRENT_TIMESTAMP, + log_type VARCHAR(255) NOT NULL, test_name VARCHAR(255) NOT NULL, log_message VARCHAR(2047), PRIMARY KEY (log_id), + FOREIGN KEY (log_type) REFERENCES {TABLE_TYPES}(log_type), FOREIGN KEY (test_name) REFERENCES {TABLE_TESTS}(test_name) );" - )).execute(&db).await { - Ok(_) => {} - Err(error) => { return Err(Error::Internal(error.to_string())); } - }; - match query(&format!( - "INSERT OR IGNORE INTO {TABLE_TESTS} (test_name) VALUES('{TEST_NONE}');" - )).execute(&db).await { - Ok(_) => {} - Err(error) => { return Err(Error::Internal(error.to_string())); } - }; + )) + .await?; - Ok(Self{database: db}) + Ok(logger) } pub async fn query(&self, _query_: &str) -> Result<(), Error> { @@ -116,40 +146,89 @@ impl Logger { } } - pub async fn get_test(&self, name: Option<&str>) -> Result, Error> { + pub async fn get_tests(&self, name: Option<&str>) -> Result, Error> { match name { Some(value) => { - self.query_for_test(&format!("SELECT * FROM {TABLE_TESTS} WHERE test_name = '{value}';")).await + self.query_for_test(&format!( + "SELECT * FROM {TABLE_TESTS} WHERE test_name = '{value}';" + )) + .await } None => { - self.query_for_test(&format!("SELECT * FROM {TABLE_TESTS};")).await + self.query_for_test(&format!("SELECT * FROM {TABLE_TESTS};")) + .await } } } - // TODO: figure out time datatype - pub async fn add_test(&self, name: &str, time: &str) -> Result<(), Error> { - self.query(&format!("INSERT INTO {TABLE_TESTS} (test_name, test_time) VALUES('{name}', '{time}');")).await + pub async fn add_test(&self, name: &str, time: DateTime) -> Result<(), Error> { + self.query(&format!( + "INSERT INTO {TABLE_TESTS} (test_name, test_time) VALUES('{name}', '{}');", time.timestamp() + )) + .await } pub async fn remove_test(&self, name: &str) -> Result<(), Error> { - self.query(&format!("DELETE FROM {TABLE_TESTS} WHERE test_name = '{name}';")).await + self.query(&format!( + "DELETE FROM {TABLE_TESTS} WHERE test_name = '{name}';" + )) + .await } pub async fn get_logs_by_testname(&self, name: String) -> Result, Error> { - self.query_for_log(&format!("SELECT * FROM {TABLE_LOGS} WHERE test_name = '{name}';")).await + self.query_for_log(&format!( + "SELECT * FROM {TABLE_LOGS} WHERE test_name = '{name}';" + )) + .await } - pub async fn get_logs_by_id(&self, start: usize, stop: Option) -> Result, Error> { + pub async fn get_logs_by_id( + &self, + start: usize, + stop: Option, + ) -> Result, Error> { let end = match stop { Some(value) => value, None => start, }; - self.query_for_log(&format!("SELECT * FROM {TABLE_LOGS} WHERE log_id BETWEEN {start} AND {end};")).await + self.query_for_log(&format!( + "SELECT * FROM {TABLE_LOGS} WHERE log_id BETWEEN {start} AND {end};" + )) + .await } - pub async fn add_log(&self, test: &str, message: &str) -> Result<(), Error> { - println!("parameters: {test}, {message}"); - self.query(&format!("INSERT INTO {TABLE_LOGS} (test_name, log_message) VALUES('{test}', '{message}');")).await + pub async fn add_log(&self, log_type: LogType, test: &str, message: &str) -> Result<(), Error> { + let log_type = log_type.as_str(); + self.query(&format!( + "INSERT INTO {TABLE_LOGS} (log_type, test_name, log_message) VALUES('{log_type}', '{test}', '{message}');" + )) + .await + } +} + +#[derive(Clone, Debug, PartialEq, sqlx::Type)] +#[sqlx(rename_all = "lowercase")] +pub enum LogType { + Information, + Warning, + Error, +} + +impl LogType { + pub fn as_str(&self) -> &'static str { + match self { + Self::Information => TYPE_INFORMATION, + Self::Warning => TYPE_WARNING, + Self::Error => TYPE_ERROR, + } + } + + pub fn from_str(input: &str) -> Result { + match input.to_lowercase().as_str() { + TYPE_INFORMATION => Ok(Self::Information), + TYPE_WARNING => Ok(Self::Warning), + TYPE_ERROR => Ok(Self::Error), + unrecognised => Err(Error::LogTypeConversion(unrecognised.to_owned())), + } } } diff --git a/rust/src/main.rs b/rust/src/main.rs index 1bffef6..e025cd3 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -1,7 +1,6 @@ #![allow(dead_code, unused)] mod log_handler; -mod log_structures; use chrono::{ DateTime, Local, @@ -14,7 +13,7 @@ use sqlx::{ }, Row, Sqlite }; -use log_handler::Logger; +use log_handler::{LogType, Logger}; const DATABASE_URL: &str = "./test.db"; const NAME_LOG_TABLE: &str = "logs"; @@ -33,44 +32,59 @@ struct Arguments { pub enum Action { /// perform action on logs #[command(subcommand)] - log(SubAction), + log(LogAction), - // /// perform action on tests - // test(SubAction), + /// perform action on tests + #[command(subcommand)] + test(TestAction), } #[allow(non_camel_case_types)] #[derive(Debug, Clone, Subcommand)] -pub enum SubAction { - add(AddArgs), - get(GetArgs), - remove(RemoveArgs), - show(ShowArgs), +pub enum LogAction { + add(LogAddArgs), + get(LogGetArgs), + remove(LogRemoveArgs), + show(LogShowArgs), +} + +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Subcommand)] +pub enum TestAction { + add(TestArgs), + remove(TestArgs), + show, } #[derive(Debug, Clone, Args)] -pub struct AddArgs { - test: String, - message: String, +pub struct LogAddArgs { + log_type: String, + log_test: String, + log_message: String, } #[derive(Debug, Clone, Args)] -pub struct GetArgs { +pub struct LogGetArgs { id: usize, } #[derive(Debug, Clone, Args)] -pub struct RemoveArgs { +pub struct LogRemoveArgs { range_start: usize, range_stop: usize, } #[derive(Debug, Clone, Args)] -pub struct ShowArgs { +pub struct LogShowArgs { range_start: Option, range_stop: Option, } +#[derive(Clone, Debug, Args)] +pub struct TestArgs { + test_name: String, +} + #[tokio::main] async fn main() -> anyhow::Result<()> { let args = Arguments::try_parse()?; @@ -80,22 +94,37 @@ async fn main() -> anyhow::Result<()> { match args.action { Action::log(subaction) => { match subaction { - SubAction::add(subargs) => { - logger.add_log(&subargs.test, &subargs.message).await.unwrap(); + LogAction::add(args) => { + logger.add_log(LogType::from_str(&args.log_type.as_str())?, &args.log_test, &args.log_message).await.unwrap(); } - SubAction::get(subargs) => { - let record = logger.query_for_log(&format!("DELETE FROM logs WHERE log_id = {} RETURNING log_id, log_time, test_name, log_message;", subargs.id)).await.unwrap(); + LogAction::get(args) => { + let record = logger.query_for_log(&format!("DELETE FROM logs WHERE log_id = {} RETURNING log_id, log_time, test_name, log_message;", args.id)).await.unwrap(); println!("record: {:#?}", record); } - SubAction::remove(subargs) => { - // remove_log(&db, subargs.range_start, subargs.range_stop).await?; + LogAction::remove(args) => { + _ = logger.query(&format!("DELETE FROM logs WHERE log_id BETWEEN {} AND {};", args.range_start, args.range_stop)).await; } - SubAction::show(subargs) => { + LogAction::show(args) => { + // let records = read_logger.query_for_log(&format!("SELECT log_id, log_time, log_type, test_name, log_message FROM logs;")).await.unwrap(); let records = read_logger.query_for_log(&format!("SELECT * FROM logs;")).await.unwrap(); println!("records: {:#?}", records); } } } + Action::test(subaction) => { + match subaction { + TestAction::add(args) => { + logger.add_test(args.test_name.as_str(), Local::now()).await?; + } + TestAction::remove(args) => { + logger.remove_test(args.test_name.as_str()).await?; + } + TestAction::show => { + let tests = logger.get_tests(None).await?; + println!("tests: {:#?}", tests); + } + } + } } Ok(())