Skip to content

Commit

Permalink
Merge pull request #2 from giuseppe-g-gelardi/tracing
Browse files Browse the repository at this point in the history
Tracing
  • Loading branch information
giuseppe-g-gelardi authored Nov 30, 2024
2 parents aeaba4c + 3956e1c commit 6345997
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 105 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ tempfile = "3.14.0"
serde-reflection = "0.4.0"
tracing = "0.1"
tracing-subscriber = "0.3"
tracing-test = "0.2.5"

50 changes: 42 additions & 8 deletions src/cargobase/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_reflection::{ContainerFormat, Named, Tracer, TracerConfig};
use tracing;

use super::DatabaseError;

#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct Column {
Expand Down Expand Up @@ -56,30 +59,37 @@ impl Columns {
}

// validate the columns
pub fn validate(&self, row_data: Value) -> Result<(), String> {
pub fn validate(&self, row_data: Value) -> Result<(), DatabaseError> {
if let Value::Object(data) = row_data {
for column in &self.0 {
if column.required && !data.contains_key(&column.name) {
return Err(format!("Column '{}' is required.", column.name));
let error_message = format!("Column '{}' is required.", column.name);
tracing::error!("{}", error_message);
return Err(DatabaseError::ColumnRequiredError(error_message));
}
}

for key in data.keys() {
if !self.0.iter().any(|col| col.name == *key) {
return Err(format!("Column '{}' is not valid.", key));
let error_message = format!("Column '{}' is not valid.", key);
tracing::error!("{}", error_message);
return Err(DatabaseError::InvalidData(error_message));
}
}
Ok(())
} else {
Err("Invalid row data.".to_string())
tracing::error!("Invalid row data.");
Err(DatabaseError::InvalidData("Invalid row data.".to_string()))
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use tracing_test::traced_test;

use super::*;

#[test]
fn test_column_new() {
Expand Down Expand Up @@ -116,6 +126,7 @@ mod tests {
assert!(result.is_ok());
}

#[traced_test]
#[test]
fn test_validate_missing_required_column() {
let columns = Columns(vec![
Expand All @@ -130,10 +141,18 @@ mod tests {
});

let result = columns.validate(row_data);

// Assert that an error is returned
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Column 'name' is required.");

// Verify the specific log message
assert!(
logs_contain("Column 'name' is required."),
"Expected log message for missing required column not found."
);
}

#[traced_test]
#[test]
fn test_validate_invalid_column() {
let columns = Columns(vec![
Expand All @@ -149,10 +168,18 @@ mod tests {
});

let result = columns.validate(row_data);

// Assert that an error is returned
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Column 'phone' is not valid.");

// Verify the specific log message
assert!(
logs_contain("Column 'phone' is not valid."),
"Expected log message for invalid column not found."
);
}

#[traced_test]
#[test]
fn test_validate_invalid_row_type() {
let columns = Columns(vec![
Expand All @@ -170,8 +197,15 @@ mod tests {
]); // Invalid type (array instead of object)

let result = columns.validate(row_data);

// Assert that an error is returned
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Invalid row data.");

// Verify the specific log message
assert!(
logs_contain("Invalid row data."),
"Expected log message for invalid row data not found."
);
}

#[test]
Expand Down
89 changes: 41 additions & 48 deletions src/cargobase/database.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use tracing;

use super::view::View;
use super::DatabaseError;
Expand All @@ -20,18 +21,19 @@ impl Database {
// -- they might not have tracing enabled

if std::path::Path::new(&file_name).exists() {
println!("Database already exists: {name}, loading database");
tracing::info!("Database already exists: {name}, loading database");

// Load the database from the file
if let Ok(db) = Database::load_from_file(&file_name) {
return db;
} else {
eprintln!("Failed to load database from file: {file_name}");
tracing::error!("Failed to load database from file: {file_name}");
}
} else {
println!("Creating new database: {file_name}");
tracing::info!("Creating new database: {file_name}");
// Create an empty JSON file for the new database
if let Err(e) = std::fs::write(&file_name, "{}") {
eprintln!("Failed to create database file: {e}");
tracing::error!("Failed to create database file: {e}");
}
}

Expand All @@ -44,28 +46,26 @@ impl Database {

pub fn drop_database(&self) -> Result<(), DatabaseError> {
if std::fs::remove_file(&self.file_name).is_err() {
return Err(DatabaseError::DeleteError(
"Failed to delete database file".to_string(),
));
tracing::error!(
"{}",
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(),));
}

println!("Database `{}` dropped successfully", self.name);
tracing::info!("Database `{}` dropped successfully", self.name);
Ok(())
}

// TODO: update this:
/*
* if the table does not exist, add it to the Database
*
* if the table exists:
* -- do NOT add a duplicate to the db
* -- let the user know that the table already exists
* -- do NOT crash the program, just return and move on
*/
pub fn add_table(&mut self, table: &mut Table) -> Result<(), DatabaseError> {
// table.set_file_name(self.file_name.clone());
if self.tables.iter().any(|t| t.name == table.name) {
return Err(DatabaseError::TableAlreadyExists(table.name.clone()));
tracing::warn!(
"{}",
DatabaseError::TableAlreadyExists(table.name.to_string())
);
return Ok(());
}

self.tables.push(table.clone());
Expand All @@ -74,45 +74,34 @@ impl Database {
Ok(())
}

// TODO: update this:
/*
* IF the table does not exist:
* -- let the user know that the table does not exist
* -- do NOT crash the program, just return and move on
*
* IF the table exists:
* -- remove the table from the db
* -- save the db to file
* -- let the user know that the table was removed successfully
*/
pub fn drop_table(&mut self, table_name: &str) -> Result<(), DatabaseError> {
let mut db =
Database::load_from_file(&self.file_name).map_err(|e| DatabaseError::LoadError(e))?;

if let Some(index) = db.tables.iter().position(|t| t.name == table_name) {
let removed_table = db.tables.remove(index);
println!("Table `{}` dropped successfully", removed_table.name);
tracing::info!("Table `{}` dropped successfully", removed_table.name);
db.save_to_file().map_err(|e| DatabaseError::SaveError(e))?;

self.tables = db.tables;
Ok(())
} else {
// eprintln!("{}", DatabaseError::TableNotFound(table_name.to_string()));
Err(DatabaseError::TableNotFound(table_name.to_string()))
// Ok(())
tracing::error!("{}", DatabaseError::TableNotFound(table_name.to_string()));
Ok(())
}
}

pub(crate) fn save_to_file(&self) -> Result<(), std::io::Error> {
let json_data = serde_json::to_string_pretty(&self)?;
std::fs::write(&self.file_name, json_data)?;
println!("Database saved to file: {}", self.file_name);
tracing::info!("Database saved to file: {}", self.file_name);
Ok(())
}

pub(crate) fn load_from_file(file_name: &str) -> Result<Self, std::io::Error> {
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?
Ok(db)
}

Expand Down Expand Up @@ -183,10 +172,11 @@ impl Database {

#[cfg(test)]
mod tests {
use crate::cargobase::setup_temp_db;
use crate::{Columns, Table};
use tracing_test::traced_test;

use super::*;
use crate::cargobase::setup_temp_db;
use crate::{Columns, Table};

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
struct TestData {
Expand Down Expand Up @@ -235,23 +225,25 @@ mod tests {
std::fs::remove_file("test_db.json").ok();
}

#[traced_test]
#[test]
fn test_add_table_already_exists() {
let mut db = setup_temp_db();

// Create a duplicate table
let columns = Columns::from_struct::<TestData>(true);
let mut duplicate_table = Table::new("TestTable".to_string(), columns);
let result = db.add_table(&mut duplicate_table);

// Assert that an error is returned
assert!(matches!(result, Err(DatabaseError::TableAlreadyExists(_))));

if let Err(DatabaseError::TableAlreadyExists(name)) = result {
assert_eq!(name, "TestTable");
}
// Assert that the result is Ok(()) even when the table already exists
assert!(result.is_ok());

// Ensure no duplicate tables exist
assert_eq!(db.tables.len(), 1);

let db_error = DatabaseError::TableAlreadyExists("TestTable".to_string());
let logs = logs_contain(&format!("{}", db_error));
assert!(logs, "Expected warning log for existing table not found.");
}

#[test]
Expand All @@ -263,17 +255,18 @@ mod tests {
assert_eq!(db.tables.len(), 0);
}

#[traced_test]
#[test]
fn test_drop_table_not_found() {
let mut db = setup_temp_db();
let result = db.drop_table("NonExistentTable");

// Assert that an error is returned
assert!(matches!(result, Err(DatabaseError::TableNotFound(_))));
assert!(result.is_ok());

if let Err(DatabaseError::TableNotFound(name)) = result {
assert_eq!(name, "NonExistentTable");
}
// Assert that an error is returned
let db_error = DatabaseError::TableNotFound("NonExistentTable".to_string());
let logs = logs_contain(&format!("{}", db_error));
assert!(logs, "Expected error log for non-existent table not found.");

// Ensure no tables were removed
assert_eq!(db.tables.len(), 1);
Expand Down
18 changes: 12 additions & 6 deletions src/cargobase/errors/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,34 @@ use thiserror::Error;

#[derive(Error, Debug)]
pub enum DatabaseError {
#[error("Failed to load the batabase: {0}")]
#[error("Failed to load the batabase: `{0}`")]
LoadError(std::io::Error),

#[error("Failed to save the database: {0}")]
#[error("Failed to save the database: `{0}`")]
SaveError(std::io::Error),

#[error("Failed to drop database: {0}")]
#[error("Failed to drop database: `{0}`")]
// return Err(DatabaseError::DeleteError);
DeleteError(String),

#[error("Table `{0}` already exists")] // skipping creation
#[error("Table `{0}` already exists. skipping creation.")] // skipping creation
TableAlreadyExists(String),

#[error("Table {0} not found")]
#[error("Table `{0}` not found")]
TableNotFound(String),

#[error("Invalid data: {0}")]
#[error("Invalid data: `{0}`")]
InvalidData(String),

#[error("Row not found with {0} = {1}")]
RowNotFound(String, String),

#[error("Column `{0}` is missing from the row data")]
MissingColumn(String),

#[error("Column `{0}` is required")]
ColumnRequiredError(String),

#[error("")] // could expand to specify serialization/deserialization error
JSONError(#[from] serde_json::Error),
}
Loading

0 comments on commit 6345997

Please sign in to comment.