//! Core error types and structured error handling. use thiserror::Error; /// Core error type for the application. /// /// Provides structured error handling with context preservation and source chaining. #[derive(Debug, Error)] pub enum CoreError { /// Database error with optional source for error chaining. #[error("Database error: {message}")] Database { message: String, #[source] source: Option>, }, /// Resource not found with type and identifier context. #[error("Not found: {resource} with id {id}")] NotFound { resource: &'static str, id: String, }, /// Validation error with field and message context. #[error("Validation error: {field} - {message}")] Validation { field: &'static str, message: String, }, /// Parse error for malformed input. #[error("Parse error: {0}")] Parse(String), /// Bad request (generic invalid input). #[error("Bad request: {0}")] BadRequest(String), /// Internal server error. #[error("Internal error: {0}")] Internal(String), /// Authentication error. #[error("Authentication error: {0}")] Auth(String), /// Remote sync operation failed (push, pull, device registration). #[error("Sync error: {0}")] Sync(String), } impl CoreError { /// Creates a database error from any error type that implements Error + Send + Sync. pub fn database(err: impl std::error::Error + Send + Sync + 'static) -> Self { CoreError::Database { message: err.to_string(), source: Some(Box::new(err)), } } /// Creates a database error from a string message. pub fn database_msg(msg: impl Into) -> Self { CoreError::Database { message: msg.into(), source: None, } } /// Creates a not-found error with resource type and identifier. pub fn not_found(resource: &'static str, id: impl ToString) -> Self { CoreError::NotFound { resource, id: id.to_string(), } } /// Creates a validation error for a specific field. pub fn validation(field: &'static str, message: impl Into) -> Self { CoreError::Validation { field, message: message.into(), } } /// Creates a parse error. pub fn parse(msg: impl Into) -> Self { CoreError::Parse(msg.into()) } /// Creates a bad request error. pub fn bad_request(msg: impl Into) -> Self { CoreError::BadRequest(msg.into()) } /// Creates an internal error. pub fn internal(msg: impl Into) -> Self { CoreError::Internal(msg.into()) } /// Creates an authentication error. pub fn auth(msg: impl Into) -> Self { CoreError::Auth(msg.into()) } /// Creates a sync error. pub fn sync(msg: impl Into) -> Self { CoreError::Sync(msg.into()) } /// Returns true if this is a not-found error. pub fn is_not_found(&self) -> bool { matches!(self, CoreError::NotFound { .. }) } /// Returns true if this is a validation error. pub fn is_validation(&self) -> bool { matches!(self, CoreError::Validation { .. }) } /// Returns true if this is a database error. pub fn is_database(&self) -> bool { matches!(self, CoreError::Database { .. }) } } #[cfg(test)] mod tests { use super::*; #[test] fn database_constructor_preserves_message() { let err = CoreError::database_msg("connection refused"); assert!(err.to_string().contains("connection refused")); assert!(err.is_database()); } #[test] fn not_found_constructor_formats_resource_and_id() { let err = CoreError::not_found("Task", "abc-123"); let msg = err.to_string(); assert!(msg.contains("Task")); assert!(msg.contains("abc-123")); assert!(err.is_not_found()); } #[test] fn validation_constructor_includes_field_and_message() { let err = CoreError::validation("name", "too long"); let msg = err.to_string(); assert!(msg.contains("name")); assert!(msg.contains("too long")); assert!(err.is_validation()); } #[test] fn is_not_found_false_for_other_variants() { assert!(!CoreError::database_msg("fail").is_not_found()); assert!(!CoreError::parse("bad").is_not_found()); assert!(!CoreError::internal("oops").is_not_found()); } #[test] fn is_validation_false_for_other_variants() { assert!(!CoreError::not_found("Task", "1").is_validation()); assert!(!CoreError::bad_request("nope").is_validation()); assert!(!CoreError::auth("denied").is_validation()); } #[test] fn is_database_false_for_other_variants() { assert!(!CoreError::not_found("Task", "1").is_database()); assert!(!CoreError::validation("x", "y").is_database()); assert!(!CoreError::internal("fail").is_database()); } }