From 45b4b005ca6a5a2019a975453733262b27fb59fc Mon Sep 17 00:00:00 2001 From: giuseppe-g-gelardi Date: Fri, 29 Nov 2024 23:38:03 -0500 Subject: [PATCH 1/9] start add async features --- Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index c2e1dae..44e6d86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,9 @@ tracing = "0.1" tracing-subscriber = "0.3" tracing-test = "0.2.5" +[features] +default = ["sync", "logging"] # default features +sync = [] # synchronous features only +async = ["tokio"] # asynchronous features only +logging = ["tracing"] # logging via tracing +full = ["sync", "async", "logging"] # all features From 0c90500ffd7c40f66db265075095ce343889d1c7 Mon Sep 17 00:00:00 2001 From: giuseppe-g-gelardi Date: Fri, 29 Nov 2024 23:41:49 -0500 Subject: [PATCH 2/9] update cargotoml with wishful todos --- Cargo.toml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 44e6d86..a7fc9f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,13 @@ default = ["sync", "logging"] # default features sync = [] # synchronous features only async = ["tokio"] # asynchronous features only logging = ["tracing"] # logging via tracing -full = ["sync", "async", "logging"] # all features +# TODO encryption = ["???"] +# TODO compression = ["???"] +full = [ +"sync", +"async", +"logging" +# "encryption", +# "compression" +] # all features + From c4fdb5d89786171eb7cb8f1399b7d86fa70a3f01 Mon Sep 17 00:00:00 2001 From: giuseppe-g-gelardi Date: Sat, 30 Nov 2024 11:58:03 -0500 Subject: [PATCH 3/9] there is so much i dont know... moved tracing sub out of lib? to simulate end user setting it up, start messing around using 'features' and my brain hurts. --- Cargo.toml | 20 ++++++++++---------- src/cargobase/util.rs | 23 ++++++++++++----------- src/lib.rs | 2 +- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a7fc9f5..0ea1847 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,18 +17,18 @@ tracing = "0.1" tracing-subscriber = "0.3" tracing-test = "0.2.5" -[features] -default = ["sync", "logging"] # default features -sync = [] # synchronous features only -async = ["tokio"] # asynchronous features only -logging = ["tracing"] # logging via tracing +# [features] +# default = ["sync", "logging"] # default features +# sync = [] # synchronous features only +# async = ["tokio"] # asynchronous features only +# logging = ["tracing"] # logging via tracing # TODO encryption = ["???"] # TODO compression = ["???"] -full = [ -"sync", -"async", -"logging" +# full = [ +# "sync", +# "async", +# "logging" # "encryption", # "compression" -] # all features +# ] # all features diff --git a/src/cargobase/util.rs b/src/cargobase/util.rs index b4435ca..aa3b61f 100644 --- a/src/cargobase/util.rs +++ b/src/cargobase/util.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; use tempfile::NamedTempFile; -use tracing_subscriber::fmt; +// use tracing_subscriber::fmt::Subscriber; use super::{Columns, Database, Table}; @@ -25,13 +25,14 @@ pub fn setup_temp_db() -> Database { db } -pub fn init_tracing() { - let subscriber = fmt::Subscriber::builder() - .with_max_level(tracing::Level::WARN) - .finish(); - /* - example implementation: - info!(target: "cargobase", "Database `{name}` already exists, loading..."); - */ - tracing::subscriber::set_global_default(subscriber).expect("Failed to set subscriber"); -} +// pub fn init_tracing() { +// // let subscriber = fmt::Subscriber::builder() +// let subscriber = Subscriber::builder() +// .with_max_level(tracing::Level::WARN) +// .finish(); +// /* +// example implementation: +// info!(target: "cargobase", "Database `{name}` already exists, loading..."); +// */ +// tracing::subscriber::set_global_default(subscriber).expect("Failed to set subscriber"); +// } diff --git a/src/lib.rs b/src/lib.rs index f42444a..22cd8dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,3 @@ pub mod cargobase; -pub use cargobase::{Columns, Column, Row, Database, Table, util::init_tracing}; +pub use cargobase::{Column, Columns, Database, Row, Table}; From dc7bda367fc416236cea67ef40fc1264f71fb341 Mon Sep 17 00:00:00 2001 From: giuseppe-g-gelardi Date: Sat, 30 Nov 2024 12:00:52 -0500 Subject: [PATCH 4/9] update package toml to specify tokio is optional and start specify async features. also change version to 0.1.1, is this how semantic versioning works??? --- Cargo.toml | 9 ++++-- TestUpdateAndDelete.json | 65 ---------------------------------------- 2 files changed, 6 insertions(+), 68 deletions(-) delete mode 100644 TestUpdateAndDelete.json diff --git a/Cargo.toml b/Cargo.toml index 0ea1847..4886bba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargobase" -version = "0.1.0" +version = "0.1.1" edition = "2021" [dependencies] @@ -8,7 +8,7 @@ serde = { version = "1.0.215", features = ["derive"] } serde_json = { version = "1.0.132", features = ["raw_value"] } serde_derive = "1.0.188" base64 = "0.22.1" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1", features = ["full"], optional = true} uuid = {version ="1.11.0", features = ["v4"] } thiserror = "2.0.3" tempfile = "3.14.0" @@ -17,8 +17,11 @@ tracing = "0.1" tracing-subscriber = "0.3" tracing-test = "0.2.5" -# [features] +[features] # default = ["sync", "logging"] # default features +default = ["sync"] +sync = [] # synchronous features only +async = ["tokio"] # asynchronous features only # sync = [] # synchronous features only # async = ["tokio"] # asynchronous features only # logging = ["tracing"] # logging via tracing diff --git a/TestUpdateAndDelete.json b/TestUpdateAndDelete.json deleted file mode 100644 index 72f498a..0000000 --- a/TestUpdateAndDelete.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "TestUpdateAndDelete", - "file_name": "TestUpdateAndDelete.json", - "tables": [ - { - "name": "TestTracingInfo", - "rows": [ - { - "_id": "6efca8a7-3ac5-473f-afa9-7add8365388e", - "data": { - "email": "jondoe@email.com", - "id": "58409c6a-2129-4528-a94f-71685fdfa3c3", - "name": "Jon Doe" - } - }, - { - "_id": "68403104-e228-46ee-9308-9a2013e6c0f8", - "data": { - "email": "janedoe@email.com", - "id": "4bf896a0-f807-47a3-b2fe-6a5453f21578", - "name": "Jane Doe" - } - }, - { - "_id": "8cdefe82-bd2f-4ea5-bacf-ce060df7f794", - "data": { - "email": null, - "id": "c0c0200d-7dc9-4881-9839-b83f6cf4f82a", - "name": "alice cooper" - } - }, - { - "_id": "a2c340db-6f29-42c5-be6c-460cb6877dbe", - "data": { - "email": "jondoe@email.com", - "id": "bf962a49-7a58-43e1-bd63-cd810050194c", - "name": "Jon Doe" - } - }, - { - "_id": "9d271fa4-6a11-43ff-87fb-b8bc0bc18a72", - "data": { - "email": "janedoe@email.com", - "id": "1c6af75d-124c-4db0-b0cf-8c199dedf246", - "name": "Jane Doe" - } - } - ], - "columns": [ - { - "name": "id", - "required": true - }, - { - "name": "name", - "required": true - }, - { - "name": "email", - "required": true - } - ] - } - ] -} \ No newline at end of file From 2ea671976b48f4ebebc06a70890be34276b15112 Mon Sep 17 00:00:00 2001 From: giuseppe-g-gelardi Date: Sat, 30 Nov 2024 19:18:10 -0500 Subject: [PATCH 5/9] add save_to and load_from async methods --- Cargo.toml | 2 -- src/cargobase/database.rs | 59 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4886bba..35bd784 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,6 @@ tracing-test = "0.2.5" default = ["sync"] sync = [] # synchronous features only async = ["tokio"] # asynchronous features only -# sync = [] # synchronous features only -# async = ["tokio"] # asynchronous features only # logging = ["tracing"] # logging via tracing # TODO encryption = ["???"] # TODO compression = ["???"] diff --git a/src/cargobase/database.rs b/src/cargobase/database.rs index 98c0c2d..ea9e395 100644 --- a/src/cargobase/database.rs +++ b/src/cargobase/database.rs @@ -98,6 +98,14 @@ impl Database { Ok(()) } + #[cfg(feature = "async")] + pub(crate) async fn save_to_file_async(&self) -> Result<(), std::io::Error> { + let json_data = serde_json::to_string_pretty(&self)?; + tokio::fs::write(&self.file_name, json_data).await?; + tracing::info!("Database saved to file: {}", self.file_name); + Ok(()) + } + pub(crate) fn load_from_file(file_name: &str) -> Result { let json_data = std::fs::read_to_string(file_name)?; let db: Database = serde_json::from_str(&json_data)?; @@ -105,6 +113,14 @@ impl Database { Ok(db) } + #[cfg(feature = "async")] + pub(crate) async fn load_from_file_async(file_name: &str) -> Result<(), std::io::Error> { + let json_data = tokio::fs::read_to_string(file_name).await?; + let db: Database = serde_json::from_str(&json_data)?; + tracing::info!("Database loaded from file: {}", file_name); // needed? + Ok(db) + } + pub(crate) fn get_table_mut(&mut self, table_name: &str) -> Option<&mut Table> { self.tables.iter_mut().find(|t| t.name == table_name) } @@ -271,4 +287,47 @@ mod tests { // Ensure no tables were removed assert_eq!(db.tables.len(), 1); } + + #[cfg(feature = "async")] + #[tokio::test] + async fn test_save_to_file_async() { + let db = Database { + name: "test_db".to_string(), + file_name: "test_db.json".to_string(), + tables: vec![], + }; + + // Ensure the async save works + db.save_to_file_async() + .await + .expect("Failed to save database"); + assert!(std::path::Path::new("test_db.json").exists()); + + // Cleanup + std::fs::remove_file("test_db.json").ok(); + } + + #[cfg(feature = "async")] + #[tokio::test] + async fn test_load_from_file_async() { + let db = Database { + name: "test_db".to_string(), + file_name: "test_db.json".to_string(), + tables: vec![], + }; + + // Save and then load + db.save_to_file_async() + .await + .expect("Failed to save database"); + let loaded_db = Database::load_from_file_async("test_db.json") + .await + .expect("Failed to load database"); + + // Assert that the loaded database matches the original + assert_eq!(db, loaded_db); + + // Cleanup + std::fs::remove_file("test_db.json").ok(); + } } From d9ce3e9e6aab555420e1eee7a450a65b8cd8bb6c Mon Sep 17 00:00:00 2001 From: giuseppe-g-gelardi Date: Sat, 30 Nov 2024 19:39:47 -0500 Subject: [PATCH 6/9] update both async functions to use database error, update test temp db unit test --- src/cargobase/database.rs | 33 ++++++++++++++------------ src/cargobase/errors/errors.rs | 7 ++++++ src/cargobase/util.rs | 42 ++++++++++++++++++++++++---------- 3 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/cargobase/database.rs b/src/cargobase/database.rs index ea9e395..1df26de 100644 --- a/src/cargobase/database.rs +++ b/src/cargobase/database.rs @@ -99,7 +99,8 @@ impl Database { } #[cfg(feature = "async")] - pub(crate) async fn save_to_file_async(&self) -> Result<(), std::io::Error> { + // pub(crate) async fn save_to_file_async(&self) -> Result<(), std::io::Error> { + pub(crate) async fn save_to_file_async(&self) -> Result<(), DatabaseError> { let json_data = serde_json::to_string_pretty(&self)?; tokio::fs::write(&self.file_name, json_data).await?; tracing::info!("Database saved to file: {}", self.file_name); @@ -114,7 +115,8 @@ impl Database { } #[cfg(feature = "async")] - pub(crate) async fn load_from_file_async(file_name: &str) -> Result<(), std::io::Error> { + // pub(crate) async fn load_from_file_async(file_name: &str) -> Result<(), std::io::Error> { + pub(crate) async fn load_from_file_async(file_name: &str) -> Result<(), DatabaseError> { let json_data = tokio::fs::read_to_string(file_name).await?; let db: Database = serde_json::from_str(&json_data)?; tracing::info!("Database loaded from file: {}", file_name); // needed? @@ -291,43 +293,46 @@ mod tests { #[cfg(feature = "async")] #[tokio::test] async fn test_save_to_file_async() { + use tempfile::NamedTempFile; + + let temp_file = NamedTempFile::new().expect("Failed to create a temporary file"); + let db_path = temp_file.path().to_str().unwrap().to_string(); + let db = Database { name: "test_db".to_string(), - file_name: "test_db.json".to_string(), + file_name: db_path.to_clone(), tables: vec![], }; - // Ensure the async save works db.save_to_file_async() .await .expect("Failed to save database"); - assert!(std::path::Path::new("test_db.json").exists()); - - // Cleanup - std::fs::remove_file("test_db.json").ok(); + let loaded_db = Database::load_from_file(&file_path).expect("Failed to load database"); + assert_eq!(db, loaded_db); } #[cfg(feature = "async")] #[tokio::test] async fn test_load_from_file_async() { + use tempfile::NamedTempFile; + + let temp_file = NamedTempFile::new().expect("Failed to create a temporary file"); + let db_path = temp_file.path().to_str().unwrap().to_string(); + let db = Database { name: "test_db".to_string(), - file_name: "test_db.json".to_string(), + file_name: db_path.to_string(), tables: vec![], }; - // Save and then load db.save_to_file_async() .await .expect("Failed to save database"); + let loaded_db = Database::load_from_file_async("test_db.json") .await .expect("Failed to load database"); - // Assert that the loaded database matches the original assert_eq!(db, loaded_db); - - // Cleanup - std::fs::remove_file("test_db.json").ok(); } } diff --git a/src/cargobase/errors/errors.rs b/src/cargobase/errors/errors.rs index b42c0e9..29cc9bf 100644 --- a/src/cargobase/errors/errors.rs +++ b/src/cargobase/errors/errors.rs @@ -32,4 +32,11 @@ pub enum DatabaseError { #[error("")] // could expand to specify serialization/deserialization error JSONError(#[from] serde_json::Error), + + #[error("IO error: `{0}`")] + Io(#[from] std::io::Error), + + #[cfg(feature = "async")] + #[error("Tokio IO error: `{0}`")] + TokioIo(#[from] tokio::io::Error), } diff --git a/src/cargobase/util.rs b/src/cargobase/util.rs index aa3b61f..6e72da6 100644 --- a/src/cargobase/util.rs +++ b/src/cargobase/util.rs @@ -1,6 +1,5 @@ use serde::{Deserialize, Serialize}; use tempfile::NamedTempFile; -// use tracing_subscriber::fmt::Subscriber; use super::{Columns, Database, Table}; @@ -25,14 +24,33 @@ pub fn setup_temp_db() -> Database { db } -// pub fn init_tracing() { -// // let subscriber = fmt::Subscriber::builder() -// let subscriber = Subscriber::builder() -// .with_max_level(tracing::Level::WARN) -// .finish(); -// /* -// example implementation: -// info!(target: "cargobase", "Database `{name}` already exists, loading..."); -// */ -// tracing::subscriber::set_global_default(subscriber).expect("Failed to set subscriber"); -// } +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + + #[test] + fn test_setup_temp_db() { + let db = setup_temp_db(); + assert_eq!(db.tables.len(), 1); + assert_eq!(db.tables[0].name, "TestTable"); + } + + #[test] + fn test_temp_file_cleanup() { + // Create a temporary database + let temp_file = NamedTempFile::new().expect("Failed to create a temporary file"); + let db_path = temp_file.path().to_str().unwrap().to_string(); + + // Drop the file explicitly by dropping the `NamedTempFile` instance + drop(temp_file); + + // Verify that the temporary file is removed + let file_exists = fs::metadata(&db_path).is_ok(); + assert!( + !file_exists, + "Temporary file `{}` should have been removed after being dropped", + db_path + ); + } +} From 7f97cd50a3279addb911faa895bce0ab9fa07141 Mon Sep 17 00:00:00 2001 From: giuseppe-g-gelardi Date: Sun, 1 Dec 2024 09:57:28 -0500 Subject: [PATCH 7/9] async tests passing. --- Cargo.toml | 13 +------- src/cargobase/database.rs | 55 ++++++++++++++++++++++++++-------- src/cargobase/errors/errors.rs | 12 ++++---- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 35bd784..c82834f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,18 +18,7 @@ tracing-subscriber = "0.3" tracing-test = "0.2.5" [features] -# default = ["sync", "logging"] # default features default = ["sync"] sync = [] # synchronous features only async = ["tokio"] # asynchronous features only -# logging = ["tracing"] # logging via tracing -# TODO encryption = ["???"] -# TODO compression = ["???"] -# full = [ -# "sync", -# "async", -# "logging" -# "encryption", -# "compression" -# ] # all features - +full = ["sync", "async"] # all features diff --git a/src/cargobase/database.rs b/src/cargobase/database.rs index 1df26de..83e6dd2 100644 --- a/src/cargobase/database.rs +++ b/src/cargobase/database.rs @@ -17,9 +17,6 @@ impl Database { let name = name.to_string(); let file_name = format!("{name}.json"); - // find a better way of logging this information for the end user - // -- they might not have tracing enabled - if std::path::Path::new(&file_name).exists() { tracing::info!("Database already exists: {name}, loading database"); @@ -99,8 +96,7 @@ impl Database { } #[cfg(feature = "async")] - // pub(crate) async fn save_to_file_async(&self) -> Result<(), std::io::Error> { - pub(crate) async fn save_to_file_async(&self) -> Result<(), DatabaseError> { + pub(crate) async fn save_to_file_async(&self) -> Result<(), tokio::io::Error> { let json_data = serde_json::to_string_pretty(&self)?; tokio::fs::write(&self.file_name, json_data).await?; tracing::info!("Database saved to file: {}", self.file_name); @@ -110,19 +106,37 @@ impl Database { pub(crate) fn load_from_file(file_name: &str) -> Result { let json_data = std::fs::read_to_string(file_name)?; let db: Database = serde_json::from_str(&json_data)?; - tracing::info!("Database loaded from file: {}", file_name); // needed? + tracing::info!("Database loaded from file: {}", file_name); Ok(db) } + // updated load from file method + // pub(crate) fn load_from_file_instance(&mut self) -> Result<(), std::io::Error> { + // let json_data = std::fs::read_to_string(&self.file_name)?; + // let db: Database = serde_json::from_str(&json_data)?; + // tracing::info!("Database loaded from file: {}", self.file_name); + // self.tables = db.tables; + // Ok(()) + // } + #[cfg(feature = "async")] - // pub(crate) async fn load_from_file_async(file_name: &str) -> Result<(), std::io::Error> { - pub(crate) async fn load_from_file_async(file_name: &str) -> Result<(), DatabaseError> { + pub(crate) async fn load_from_file_async(file_name: &str) -> Result { let json_data = tokio::fs::read_to_string(file_name).await?; let db: Database = serde_json::from_str(&json_data)?; - tracing::info!("Database loaded from file: {}", file_name); // needed? + tracing::info!("Database loaded from file: {}", file_name); Ok(db) } + // async version of the load from file instance method + // #[cfg(feature = "async")] + // pub(crate) async fn load_from_file_instance_async(&mut self) -> Result<(), DatabaseError> { + // let json_data = tokio::fs::read_to_string(&self.file_name).await?; + // let db: Database = serde_json::from_str(&json_data)?; + // tracing::info!("Database loaded from file: {}", self.file_name); + // self.tables = db.tables; + // Ok(()) + // } + pub(crate) fn get_table_mut(&mut self, table_name: &str) -> Option<&mut Table> { self.tables.iter_mut().find(|t| t.name == table_name) } @@ -290,6 +304,23 @@ mod tests { assert_eq!(db.tables.len(), 1); } + #[test] + fn test_save_to_file() { + let db = setup_temp_db(); + let result = db.save_to_file(); + + assert!(result.is_ok()); + assert!(std::path::Path::new(&db.file_name).exists()); + } + + #[test] + fn test_load_from_file() { + let db = setup_temp_db(); + let loaded_db = Database::load_from_file(&db.file_name).expect("Failed to load database"); + + assert_eq!(db, loaded_db); + } + #[cfg(feature = "async")] #[tokio::test] async fn test_save_to_file_async() { @@ -300,14 +331,14 @@ mod tests { let db = Database { name: "test_db".to_string(), - file_name: db_path.to_clone(), + file_name: db_path.clone(), tables: vec![], }; db.save_to_file_async() .await .expect("Failed to save database"); - let loaded_db = Database::load_from_file(&file_path).expect("Failed to load database"); + let loaded_db = Database::load_from_file(&db_path).expect("Failed to load database"); assert_eq!(db, loaded_db); } @@ -329,7 +360,7 @@ mod tests { .await .expect("Failed to save database"); - let loaded_db = Database::load_from_file_async("test_db.json") + let loaded_db = Database::load_from_file_async(&db_path) .await .expect("Failed to load database"); diff --git a/src/cargobase/errors/errors.rs b/src/cargobase/errors/errors.rs index 29cc9bf..9155863 100644 --- a/src/cargobase/errors/errors.rs +++ b/src/cargobase/errors/errors.rs @@ -33,10 +33,10 @@ pub enum DatabaseError { #[error("")] // could expand to specify serialization/deserialization error JSONError(#[from] serde_json::Error), - #[error("IO error: `{0}`")] - Io(#[from] std::io::Error), - - #[cfg(feature = "async")] - #[error("Tokio IO error: `{0}`")] - TokioIo(#[from] tokio::io::Error), + // #[error("IO error: `{0}`")] + // Io(#[from] std::io::Error), + // + // #[cfg(feature = "async")] + // #[error("Tokio IO error: `{0}`")] + // TokioIo(#[from] tokio::io::Error), } From 754e7e7afeb196486ef6aee1ed592fc35ebd07c8 Mon Sep 17 00:00:00 2001 From: giuseppe-g-gelardi Date: Sun, 1 Dec 2024 20:13:42 -0500 Subject: [PATCH 8/9] delete commented code --- src/cargobase/database.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/cargobase/database.rs b/src/cargobase/database.rs index 83e6dd2..4d45ff4 100644 --- a/src/cargobase/database.rs +++ b/src/cargobase/database.rs @@ -47,9 +47,6 @@ impl Database { "{}", DatabaseError::DeleteError("Failed to delete database file".to_string()) ); - - // should we crash the program? - // return Err(DatabaseError::DeleteError("Failed to delete database file".to_string(),)); } tracing::info!("Database `{}` dropped successfully", self.name); @@ -110,15 +107,6 @@ impl Database { Ok(db) } - // updated load from file method - // pub(crate) fn load_from_file_instance(&mut self) -> Result<(), std::io::Error> { - // let json_data = std::fs::read_to_string(&self.file_name)?; - // let db: Database = serde_json::from_str(&json_data)?; - // tracing::info!("Database loaded from file: {}", self.file_name); - // self.tables = db.tables; - // Ok(()) - // } - #[cfg(feature = "async")] pub(crate) async fn load_from_file_async(file_name: &str) -> Result { let json_data = tokio::fs::read_to_string(file_name).await?; @@ -127,16 +115,6 @@ impl Database { Ok(db) } - // async version of the load from file instance method - // #[cfg(feature = "async")] - // pub(crate) async fn load_from_file_instance_async(&mut self) -> Result<(), DatabaseError> { - // let json_data = tokio::fs::read_to_string(&self.file_name).await?; - // let db: Database = serde_json::from_str(&json_data)?; - // tracing::info!("Database loaded from file: {}", self.file_name); - // self.tables = db.tables; - // Ok(()) - // } - pub(crate) fn get_table_mut(&mut self, table_name: &str) -> Option<&mut Table> { self.tables.iter_mut().find(|t| t.name == table_name) } From 783aa5fd32344611f173539ee5fd294fab3606c7 Mon Sep 17 00:00:00 2001 From: giuseppe-g-gelardi Date: Sun, 1 Dec 2024 20:42:14 -0500 Subject: [PATCH 9/9] update cargo.toml for development other wise async is pain. prepare to setup for separate crates to reduce confusion --- Cargo.toml | 3 ++- src/cargobase/database.rs | 46 +++++++++++++++++++++++++++++++++ src/cargobase/mod.rs | 7 +++-- src/cargobase/util.rs | 54 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c82834f..53950ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,8 @@ tracing-subscriber = "0.3" tracing-test = "0.2.5" [features] -default = ["sync"] +default = ["sync", "async"] # for development +# default = ["sync"] sync = [] # synchronous features only async = ["tokio"] # asynchronous features only full = ["sync", "async"] # all features diff --git a/src/cargobase/database.rs b/src/cargobase/database.rs index 4d45ff4..b6f8dd0 100644 --- a/src/cargobase/database.rs +++ b/src/cargobase/database.rs @@ -41,6 +41,36 @@ impl Database { } } + #[cfg(feature = "async")] + pub async fn new_async(name: &str) -> Self { + let name = name.to_string(); + let file_name = format!("{name}.json"); + + if tokio::fs::metadata(&file_name).await.is_ok() { + tracing::info!("Database already exists: {name}, loading database"); + + // Load the database from the file + match Database::load_from_file_async(&file_name).await { + Ok(db) => return db, + Err(e) => { + tracing::error!("Failed to load database from file: {file_name}, error: {e}"); + } + } + } else { + tracing::info!("Creating new database: {file_name}"); + // Create an empty JSON file for the new database + if let Err(e) = tokio::fs::write(&file_name, "{}").await { + tracing::error!("Failed to create database file: {e}"); + } + } + + Database { + name, + file_name, + tables: Vec::new(), + } + } + pub fn drop_database(&self) -> Result<(), DatabaseError> { if std::fs::remove_file(&self.file_name).is_err() { tracing::error!( @@ -188,6 +218,9 @@ mod tests { use crate::cargobase::setup_temp_db; use crate::{Columns, Table}; + #[cfg(feature = "async")] + use crate::cargobase::setup_temp_db_async; + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] struct TestData { id: String, @@ -206,6 +239,19 @@ mod tests { assert_eq!(db.tables.len(), 1); // the setup_temp_db function adds a table } + #[cfg(feature = "async")] + #[tokio::test] + async fn test_database_new_async() { + let db = setup_temp_db_async().await; + + let db_name = &db.name.to_string(); + let fnn = format!("{db_name}.json"); + + assert_eq!(db.name, db_name.to_string()); + assert_eq!(db.file_name, fnn); + assert_eq!(db.tables.len(), 1); // the setup_temp_db function adds a table + } + #[test] fn test_drop_database() { let db = setup_temp_db(); diff --git a/src/cargobase/mod.rs b/src/cargobase/mod.rs index eeb1d03..edf3740 100644 --- a/src/cargobase/mod.rs +++ b/src/cargobase/mod.rs @@ -1,17 +1,20 @@ pub mod columns; pub mod database; +pub mod errors; pub mod query; pub mod row; pub mod table; pub mod util; -pub mod errors; pub mod view; pub use columns::{Column, Columns}; pub use database::Database; +pub use errors::errors::DatabaseError; pub use query::Query; pub use row::Row; pub use table::Table; pub use util::setup_temp_db; -pub use errors::errors::DatabaseError; pub use view::View; + +#[cfg(feature = "async")] +pub use util::setup_temp_db_async; diff --git a/src/cargobase/util.rs b/src/cargobase/util.rs index 6e72da6..2bb63f2 100644 --- a/src/cargobase/util.rs +++ b/src/cargobase/util.rs @@ -3,6 +3,9 @@ use tempfile::NamedTempFile; use super::{Columns, Database, Table}; +// #[cfg(feature = "async")] +// use tokio::fs; + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] struct TestData { id: String, @@ -24,6 +27,25 @@ pub fn setup_temp_db() -> Database { db } +#[cfg(feature = "async")] +pub async fn setup_temp_db_async() -> Database { + let temp_file = NamedTempFile::new().expect("Failed to create a temporary file"); + let db_path = temp_file.path().to_str().unwrap().to_string(); + + // Initialize the test database + let mut db = Database::new_async(&db_path).await; + let test_columns = Columns::from_struct::(true); + + let mut table = Table::new("TestTable".to_string(), test_columns); + db.add_table(&mut table).unwrap(); + + db.save_to_file_async() + .await + .expect("Failed to save database"); + + db +} + #[cfg(test)] mod tests { use super::*; @@ -53,4 +75,36 @@ mod tests { db_path ); } + + #[cfg(feature = "async")] + #[tokio::test] + async fn test_setup_temp_db_async() { + let db = setup_temp_db_async().await; + assert_eq!(db.tables.len(), 1); + assert_eq!(db.tables[0].name, "TestTable"); + } + + #[cfg(feature = "async")] + #[tokio::test] + async fn test_temp_file_cleanup_async() { + // Create a temporary database + let temp_file = tempfile::Builder::new() + .prefix("test_db") + .suffix(".json") + .tempfile() + .expect("Failed to create a temporary file"); + + let db_path = temp_file.path().to_str().unwrap().to_string(); + + // Drop the file explicitly by dropping the `NamedTempFile` instance + drop(temp_file); + + // Verify that the temporary file is removed + let file_exists = fs::metadata(&db_path).is_ok(); + assert!( + !file_exists, + "Temporary file `{}` should have been removed after being dropped", + db_path + ); + } }