(rust) sqlx tests for armasuisse

This commit is contained in:
scbj
2025-05-08 15:07:47 +02:00
parent d1297d6e83
commit 3dc3d46a21
5 changed files with 253 additions and 251 deletions
+155
View File
@@ -0,0 +1,155 @@
use chrono::{DateTime, Local};
use sqlx::{
migrate::MigrateDatabase,
query,
query_as,
Row,
sqlite::{
SqlitePool,
SqlitePoolOptions,
},
Sqlite,
};
const URL_DATABASE: &str = "log.db";
const TABLE_TESTS: &str = "tests";
const TABLE_LOGS: &str = "logs";
const TEST_NONE: &str = "none";
#[derive(Debug, Clone, thiserror::Error)]
pub enum Error {
#[error("Error in crate `sqlx`: {0}")]
Internal(String),
}
#[derive(Clone, Debug, sqlx::FromRow)]
pub struct LogRecord {
log_id: i32,
/// timestamp of the logs creation
log_time: DateTime<Local>,
/// logs affiliated test
test_name: String,
/// log message
log_message: String,
}
#[derive(Clone, Debug, sqlx::FromRow)]
pub struct TestRecord {
/// name of test
test_name: String,
/// time test is scheduled to start
test_time: DateTime<Local>,
}
#[derive(Clone, Debug)]
pub struct Logger {
database: SqlitePool,
}
impl Logger {
pub async fn setup() -> Result<Self, Error> {
match Sqlite::database_exists(URL_DATABASE).await {
Ok(true) => {}
Ok(false) => {
println!("created `{}`", URL_DATABASE);
_ = Sqlite::create_database(URL_DATABASE).await;
}
Err(error) => {
return Err(Error::Internal(error.to_string()));
}
}
let db = match SqlitePool::connect(URL_DATABASE).await {
Ok(value) => value,
Err(error) => return Err(Error::Internal(error.to_string())),
};
match 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} (
log_id INTEGER NOT NULL UNIQUE,
log_time INTEGER DEFAULT CURRENT_TIMESTAMP,
test_name VARCHAR(255) NOT NULL,
log_message VARCHAR(2047),
PRIMARY KEY (log_id),
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())); }
};
Ok(Self{database: db})
}
pub async fn query(&self, _query_: &str) -> Result<(), Error> {
match query(_query_).execute(&self.database).await {
Err(error) => return Err(Error::Internal(error.to_string())),
_ => Ok(()),
}
}
pub async fn query_for_test(&self, _query_: &str) -> Result<Vec<TestRecord>, Error> {
match query_as(_query_).fetch_all(&self.database).await {
Ok(records) => Ok(records),
Err(error) => return Err(Error::Internal(error.to_string())),
}
}
pub async fn query_for_log(&self, query: &str) -> Result<Vec<LogRecord>, Error> {
match query_as(query).fetch_all(&self.database).await {
Ok(records) => Ok(records),
Err(error) => return Err(Error::Internal(error.to_string())),
}
}
pub async fn get_test(&self, name: Option<&str>) -> Result<Vec<TestRecord>, Error> {
match name {
Some(value) => {
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
}
}
}
// 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 remove_test(&self, name: &str) -> Result<(), Error> {
self.query(&format!("DELETE FROM {TABLE_TESTS} WHERE test_name = '{name}';")).await
}
pub async fn get_logs_by_testname(&self, name: String) -> Result<Vec<LogRecord>, Error> {
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<usize>) -> Result<Vec<LogRecord>, 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
}
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
}
}
+90 -58
View File
@@ -1,70 +1,102 @@
#![allow(dead_code, unused)]
use std::{
io::{stdin, stdout, Error, Result, Write},
sync::{
atomic::{AtomicBool, Ordering}, Arc
},
mod log_handler;
mod log_structures;
use chrono::{
DateTime, Local,
};
use clap::{Args, Parser, Subcommand};
use sqlx::{
database, migrate::MigrateDatabase, query, query_as, sqlite::{
SqlitePool,
SqlitePoolOptions,
}, Row, Sqlite
};
fn main() -> Result<()> {
let running = Arc::new(AtomicBool::new(true));
let signal_running = running.clone();
let mut input: String = String::new();
let mut length: usize;
let mut tokens: Vec<&str>;
use log_handler::Logger;
ctrlc::set_handler(move || {
signal_running.swap(false, Ordering::SeqCst);
});
const DATABASE_URL: &str = "./test.db";
const NAME_LOG_TABLE: &str = "logs";
const NAME_TEST_TABLE: &str = "tests";
while running.load(Ordering::SeqCst) {
print!("> ");
_ = stdout().flush();
length = match stdin().read_line(&mut input) {
Ok(value) => value,
Err(error) => {
println!("\nError: {:#?}\n", error);
continue;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about=None)]
struct Arguments {
/// subcommand
#[command(subcommand)]
pub action: Action,
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Subcommand)]
pub enum Action {
/// perform action on logs
#[command(subcommand)]
log(SubAction),
// /// perform action on tests
// test(SubAction),
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Subcommand)]
pub enum SubAction {
add(AddArgs),
get(GetArgs),
remove(RemoveArgs),
show(ShowArgs),
}
#[derive(Debug, Clone, Args)]
pub struct AddArgs {
test: String,
message: String,
}
#[derive(Debug, Clone, Args)]
pub struct GetArgs {
id: usize,
}
#[derive(Debug, Clone, Args)]
pub struct RemoveArgs {
range_start: usize,
range_stop: usize,
}
#[derive(Debug, Clone, Args)]
pub struct ShowArgs {
range_start: Option<usize>,
range_stop: Option<usize>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Arguments::try_parse()?;
let logger = Logger::setup().await.unwrap();
let read_logger = logger.clone();
match args.action {
Action::log(subaction) => {
match subaction {
SubAction::add(subargs) => {
logger.add_log(&subargs.test, &subargs.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();
println!("record: {:#?}", record);
}
SubAction::remove(subargs) => {
// remove_log(&db, subargs.range_start, subargs.range_stop).await?;
}
SubAction::show(subargs) => {
let records = read_logger.query_for_log(&format!("SELECT * FROM logs;")).await.unwrap();
println!("records: {:#?}", records);
}
}
};
if 0 == length {
continue;
}
input = input.trim_matches('\n').to_owned();
tokens = input.splitn(2,[' ']).collect();
if tokens.len() != 2 {
continue;
}
let cmd = tokens.remove(0);
let data = tokens.remove(0);
if cmd == "raw" {
println!("{:02x?}", data.as_bytes());
} else if cmd == "str" {
println!("{}", data);
}
input.clear();
}
Ok(())
}
// NOTE: not required, but might be fun to implement at some point
// fn tokenise(input: &str, separator: Vec<&str>) -> Vec<String> {
// let separator = separator.join("");
// let mut tokens: Vec<String> = Vec::new();
// let mut quoted = false;
//
// for char in input.chars() {
// input.len
// if separator.contains(char) {
// }
// }
//
// tokens
// }