// libmarlin/src/error.rs use std::fmt; use std::io; // Ensure these are present if Error enum variants use them directly // use rusqlite; // use notify; pub type Result = std::result::Result; #[derive(Debug)] pub enum Error { Io(io::Error), Database(rusqlite::Error), Watch(notify::Error), InvalidState(String), NotFound(String), Config(String), Other(String), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Io(err) => write!(f, "IO error: {}", err), Self::Database(err) => write!(f, "Database error: {}", err), Self::Watch(err) => write!(f, "Watch error: {}", err), Self::InvalidState(msg) => write!(f, "Invalid state: {}", msg), Self::NotFound(path) => write!(f, "Not found: {}", path), Self::Config(msg) => write!(f, "Configuration error: {}", msg), Self::Other(msg) => write!(f, "Error: {}", msg), } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Io(err) => Some(err), Self::Database(err) => Some(err), Self::Watch(err) => Some(err), Self::InvalidState(_) | Self::NotFound(_) | Self::Config(_) | Self::Other(_) => None, } } } impl From for Error { fn from(err: io::Error) -> Self { Self::Io(err) } } impl From for Error { fn from(err: rusqlite::Error) -> Self { Self::Database(err) } } impl From for Error { fn from(err: notify::Error) -> Self { Self::Watch(err) } } #[cfg(test)] mod tests { use super::*; use std::error::Error as StdError; #[test] fn test_error_display_and_from() { // Test Io variant let io_err_inner_for_source_check = io::Error::new(io::ErrorKind::NotFound, "test io error"); let io_err_marlin = Error::from(io::Error::new(io::ErrorKind::NotFound, "test io error")); assert_eq!(io_err_marlin.to_string(), "IO error: test io error"); let source = io_err_marlin.source(); assert!(source.is_some(), "Io error should have a source"); if let Some(s) = source { // Compare details of the source if necessary, or just its string representation assert_eq!(s.to_string(), io_err_inner_for_source_check.to_string()); } // Test Database variant let rusqlite_err_inner_for_source_check = rusqlite::Error::SqliteFailure( rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_ERROR), Some("test db error".to_string()), ); // We need to create the error again for the From conversion if we want to compare the source let db_err_marlin = Error::from(rusqlite::Error::SqliteFailure( rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_ERROR), Some("test db error".to_string()), )); assert!(db_err_marlin .to_string() .contains("Database error: test db error")); let source = db_err_marlin.source(); assert!(source.is_some(), "Database error should have a source"); if let Some(s) = source { assert_eq!( s.to_string(), rusqlite_err_inner_for_source_check.to_string() ); } // Test Watch variant let notify_raw_err_inner_for_source_check = notify::Error::new(notify::ErrorKind::Generic("test watch error".to_string())); let watch_err_marlin = Error::from(notify::Error::new(notify::ErrorKind::Generic( "test watch error".to_string(), ))); assert!(watch_err_marlin .to_string() .contains("Watch error: test watch error")); let source = watch_err_marlin.source(); assert!(source.is_some(), "Watch error should have a source"); if let Some(s) = source { assert_eq!( s.to_string(), notify_raw_err_inner_for_source_check.to_string() ); } let invalid_state_err = Error::InvalidState("bad state".to_string()); assert_eq!(invalid_state_err.to_string(), "Invalid state: bad state"); assert!(invalid_state_err.source().is_none()); let not_found_err = Error::NotFound("missing_file.txt".to_string()); assert_eq!(not_found_err.to_string(), "Not found: missing_file.txt"); assert!(not_found_err.source().is_none()); let config_err = Error::Config("bad config".to_string()); assert_eq!(config_err.to_string(), "Configuration error: bad config"); assert!(config_err.source().is_none()); let other_err = Error::Other("some other issue".to_string()); assert_eq!(other_err.to_string(), "Error: some other issue"); assert!(other_err.source().is_none()); } #[test] fn test_rusqlite_error_without_message() { let sqlite_busy_error = rusqlite::Error::SqliteFailure( rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_BUSY), None, ); let db_err_no_msg = Error::from(sqlite_busy_error); let expected_rusqlite_msg = rusqlite::Error::SqliteFailure( rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_BUSY), None, ) .to_string(); let expected_marlin_msg = format!("Database error: {}", expected_rusqlite_msg); // Verify the string matches the expected format assert_eq!(db_err_no_msg.to_string(), expected_marlin_msg); // Check the error code directly instead of the string if let Error::Database(rusqlite::Error::SqliteFailure(err, _)) = &db_err_no_msg { assert_eq!(err.code, rusqlite::ffi::ErrorCode::DatabaseBusy); } else { panic!("Expected Error::Database variant"); } // Verify the source exists assert!(db_err_no_msg.source().is_some()); } }