//! Service error trait for standardized error handling across all services //! //! This module provides a common trait that all service-specific errors implement, //! enabling consistent error handling, metrics, and retry logic across the codebase. use super::{CommonError, ErrorCategory, ErrorSeverity, RetryStrategy}; /// Common trait for service-specific error types /// /// Implement this trait to provide standardized error handling for your service. /// This enables consistent metrics collection, retry logic, and error categorization. /// /// # Example /// /// ```rust /// use common::error::{ServiceErrorExt, CommonError, ErrorCategory, ErrorSeverity, RetryStrategy}; /// /// #[derive(Debug, thiserror::Error)] /// pub enum MyServiceError { /// #[error("Common error: {0}")] /// Common(#[from] CommonError), /// /// #[error("Custom error: {0}")] /// Custom(String), /// } /// /// impl ServiceErrorExt for MyServiceError { /// fn error_code(&self) -> &'static str { /// match self { /// Self::Common(_) => "MY_COMMON_ERROR", /// Self::Custom(_) => "MY_CUSTOM_ERROR", /// } /// } /// /// fn category(&self) -> ErrorCategory { /// match self { /// Self::Common(err) => err.category(), /// Self::Custom(_) => ErrorCategory::System, /// } /// } /// /// fn severity(&self) -> ErrorSeverity { /// match self { /// Self::Common(err) => err.severity(), /// Self::Custom(_) => ErrorSeverity::Error, /// } /// } /// /// fn retry_strategy(&self) -> RetryStrategy { /// match self { /// Self::Common(err) => err.retry_strategy(), /// Self::Custom(_) => RetryStrategy::Linear { base_delay_ms: 1000 }, /// } /// } /// /// fn to_common_error(self) -> CommonError { /// match self { /// Self::Common(err) => err, /// Self::Custom(msg) => CommonError::internal(msg), /// } /// } /// } /// ``` pub trait ServiceErrorExt: std::error::Error + Send + Sync { /// Get unique error code for monitoring and alerting /// /// Error codes should be: /// - Uppercase with underscores (e.g., "ML_MODEL_TRAINING_ERROR") /// - Service-prefixed (e.g., "ML_", "RISK_", "DATA_", "TLI_") /// - Descriptive and unique within the service fn error_code(&self) -> &'static str; /// Get error category for classification and metrics fn category(&self) -> ErrorCategory; /// Get error severity level for prioritization and alerting fn severity(&self) -> ErrorSeverity; /// Get retry strategy for automatic error recovery fn retry_strategy(&self) -> RetryStrategy; /// Convert service-specific error to CommonError for interoperability /// /// This enables seamless integration with common error handling infrastructure /// and allows service errors to be used in generic contexts. fn to_common_error(self) -> CommonError; /// Check if error is retryable based on retry strategy fn is_retryable(&self) -> bool { !matches!(self.retry_strategy(), RetryStrategy::NoRetry) } } /// Extension trait for Result types using service errors pub trait ServiceResultExt { /// Map service error to CommonError fn map_to_common(self) -> Result; /// Extract error code if Result is Err fn error_code(&self) -> Option<&'static str>; /// Check if error is retryable fn is_retryable(&self) -> bool; } impl ServiceResultExt for Result { fn map_to_common(self) -> Result { self.map_err(ServiceErrorExt::to_common_error) } fn error_code(&self) -> Option<&'static str> { self.as_ref().err().map(ServiceErrorExt::error_code) } fn is_retryable(&self) -> bool { self.as_ref().err().is_some_and(ServiceErrorExt::is_retryable) } } #[cfg(test)] mod tests { use super::*; #[derive(Debug, thiserror::Error)] enum TestServiceError { #[error("Common error: {0}")] Common(#[from] CommonError), #[error("Test error: {0}")] Test(String), } impl ServiceErrorExt for TestServiceError { fn error_code(&self) -> &'static str { match self { Self::Common(_) => "TEST_COMMON_ERROR", Self::Test(_) => "TEST_CUSTOM_ERROR", } } fn category(&self) -> ErrorCategory { match self { Self::Common(err) => err.category(), Self::Test(_) => ErrorCategory::System, } } fn severity(&self) -> ErrorSeverity { match self { Self::Common(err) => err.severity(), Self::Test(_) => ErrorSeverity::Error, } } fn retry_strategy(&self) -> RetryStrategy { match self { Self::Common(err) => err.retry_strategy(), Self::Test(_) => RetryStrategy::NoRetry, } } fn to_common_error(self) -> CommonError { match self { Self::Common(err) => err, Self::Test(msg) => CommonError::internal(msg), } } } #[test] fn test_service_error_trait() { let error = TestServiceError::Test("test error".to_string()); assert_eq!(error.error_code(), "TEST_CUSTOM_ERROR"); assert_eq!(error.category(), ErrorCategory::System); assert_eq!(error.severity(), ErrorSeverity::Error); assert!(!error.is_retryable()); } #[test] fn test_common_error_delegation() { let common_err = CommonError::config("test config error"); let error = TestServiceError::Common(common_err); assert_eq!(error.error_code(), "TEST_COMMON_ERROR"); assert_eq!(error.category(), ErrorCategory::Configuration); assert_eq!(error.severity(), ErrorSeverity::Critical); assert!(!error.is_retryable()); } #[test] fn test_result_extensions() { let ok_result: Result = Ok(42); assert!(ok_result.error_code().is_none()); assert!(!ok_result.is_retryable()); let err_result: Result = Err(TestServiceError::Test("error".to_string())); assert_eq!(err_result.error_code(), Some("TEST_CUSTOM_ERROR")); assert!(!err_result.is_retryable()); } }