Move 17 library crates into crates/, CLI binary into bin/fxt, consolidate 10 test crates into testing/, split config crate from deployment config files. Root directory reduced from 38+ to ~17 directories. All Cargo.toml paths and build.rs proto refs updated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
202 lines
6.4 KiB
Rust
202 lines
6.4 KiB
Rust
//! 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<T, E: ServiceErrorExt> {
|
|
/// Map service error to CommonError
|
|
fn map_to_common(self) -> Result<T, CommonError>;
|
|
|
|
/// 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<T, E: ServiceErrorExt> ServiceResultExt<T, E> for Result<T, E> {
|
|
fn map_to_common(self) -> Result<T, CommonError> {
|
|
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<i32, TestServiceError> = Ok(42);
|
|
assert!(ok_result.error_code().is_none());
|
|
assert!(!ok_result.is_retryable());
|
|
|
|
let err_result: Result<i32, TestServiceError> = Err(TestServiceError::Test("error".to_string()));
|
|
assert_eq!(err_result.error_code(), Some("TEST_CUSTOM_ERROR"));
|
|
assert!(!err_result.is_retryable());
|
|
}
|
|
}
|