//! Error types for storage operations use common::error::{CommonError, ErrorCategory}; use thiserror::Error; /// Result type for storage operations pub type StorageResult = Result; /// Storage-related errors #[derive(Error, Debug)] pub enum StorageError { /// IO error during file operations #[error("IO error: {message}")] IoError { /// Error message describing the IO failure message: String, }, /// Network error during remote storage operations #[error("Network error: {message}")] NetworkError { /// Error message describing the network failure message: String, }, /// Authentication/authorization error #[error("Authentication error: {message}")] AuthError { /// Error message describing the authentication failure message: String, }, /// Data not found at the specified path #[error("Data not found at path: {path}")] NotFound { /// The path where data was not found path: String, }, /// Permission denied for storage operation #[error("Permission denied for operation on: {path}")] PermissionDenied { /// The path where permission was denied path: String, }, /// Storage quota exceeded #[error("Storage quota exceeded (used: {used}, limit: {limit})")] QuotaExceeded { /// Current storage usage in bytes used: u64, /// Storage quota limit in bytes limit: u64, }, /// Data integrity check failed #[error("Data integrity check failed for: {path} (expected: {expected}, actual: {actual})")] IntegrityError { /// The path where integrity check failed path: String, /// Expected checksum or hash value expected: String, /// Actual checksum or hash value actual: String, }, /// Serialization/deserialization error #[error("Serialization error: {message}")] SerializationError { /// Error message describing the serialization failure message: String, }, /// Compression/decompression error #[error("Compression error: {message}")] CompressionError { /// Error message describing the compression failure message: String, }, /// Configuration error #[error("Configuration error: {message}")] ConfigError { /// Error message describing the configuration issue message: String, }, /// Operation failed with detailed context #[error("Operation '{operation}' failed on path '{path}': {source}")] OperationFailed { /// The operation that failed operation: String, /// The path where the operation failed path: String, /// The underlying error that caused the failure #[source] source: std::sync::Arc, }, /// Timeout during storage operation #[error("Operation timed out after {timeout_ms}ms")] Timeout { /// Timeout duration in milliseconds timeout_ms: u64, }, /// Rate limit exceeded #[error("Rate limit exceeded, retry after {retry_after_ms}ms")] RateLimited { /// Time to wait before retrying in milliseconds retry_after_ms: u64, }, /// Generic storage error #[error("Storage error: {message}")] Generic { /// Error message describing the generic failure message: String, }, /// S3-specific error #[cfg(feature = "s3")] #[error("S3 error: {message}")] S3Error { /// Error message describing the S3 failure message: String, }, } impl StorageError { /// Check if the error is retryable pub fn is_retryable(&self) -> bool { match self { StorageError::NetworkError { .. } => true, StorageError::Timeout { .. } => true, StorageError::RateLimited { .. } => true, StorageError::Generic { .. } => true, #[cfg(feature = "s3")] StorageError::S3Error { .. } => true, _ => false, } } /// Get retry delay in milliseconds for retryable errors pub fn retry_delay_ms(&self) -> Option { match self { StorageError::NetworkError { .. } => Some(100), StorageError::Timeout { .. } => Some(200), StorageError::RateLimited { retry_after_ms } => Some(*retry_after_ms), StorageError::Generic { .. } => Some(100), #[cfg(feature = "s3")] StorageError::S3Error { .. } => Some(100), _ => None, } } /// Check if error indicates a transient issue pub fn is_transient(&self) -> bool { matches!( self, StorageError::NetworkError { .. } | StorageError::Timeout { .. } | StorageError::RateLimited { .. } ) } /// Get error category for metrics and monitoring pub fn category(&self) -> &'static str { match self { StorageError::IoError { .. } => "io", StorageError::NetworkError { .. } => "network", StorageError::AuthError { .. } => "auth", StorageError::NotFound { .. } => "not_found", StorageError::PermissionDenied { .. } => "permission", StorageError::QuotaExceeded { .. } => "quota", StorageError::IntegrityError { .. } => "integrity", StorageError::SerializationError { .. } => "serialization", StorageError::CompressionError { .. } => "compression", StorageError::ConfigError { .. } => "config", StorageError::OperationFailed { .. } => "operation_failed", StorageError::Timeout { .. } => "timeout", StorageError::RateLimited { .. } => "rate_limit", StorageError::Generic { .. } => "generic", #[cfg(feature = "s3")] StorageError::S3Error { .. } => "s3", } } /// Create a sanitized error message safe for logging (removes sensitive info) pub fn safe_message(&self) -> String { match self { StorageError::AuthError { .. } => { "Authentication error (details masked for security)".to_string() }, _ => self.to_string(), } } } // Conversion implementations for common error types impl From for StorageError { fn from(err: std::io::Error) -> Self { use std::io::ErrorKind; match err.kind() { ErrorKind::NotFound => StorageError::NotFound { path: "unknown".to_string(), }, ErrorKind::PermissionDenied => StorageError::PermissionDenied { path: "unknown".to_string(), }, ErrorKind::TimedOut => StorageError::Timeout { timeout_ms: 5000 }, _ => StorageError::IoError { message: err.to_string(), }, } } } impl From for StorageError { fn from(err: serde_json::Error) -> Self { StorageError::SerializationError { message: err.to_string(), } } } impl From for StorageError { fn from(err: bincode::Error) -> Self { StorageError::SerializationError { message: err.to_string(), } } } // Conversion to CommonError for integration with common error system impl From for CommonError { fn from(err: StorageError) -> Self { let category = match &err { StorageError::IoError { .. } => ErrorCategory::System, StorageError::NetworkError { .. } => ErrorCategory::Network, StorageError::AuthError { .. } => ErrorCategory::Security, StorageError::NotFound { .. } => ErrorCategory::System, StorageError::PermissionDenied { .. } => ErrorCategory::Security, StorageError::QuotaExceeded { .. } => ErrorCategory::System, StorageError::IntegrityError { .. } => ErrorCategory::System, StorageError::SerializationError { .. } => ErrorCategory::System, StorageError::CompressionError { .. } => ErrorCategory::System, StorageError::ConfigError { .. } => ErrorCategory::Configuration, StorageError::OperationFailed { .. } => ErrorCategory::System, StorageError::Timeout { .. } => ErrorCategory::System, StorageError::RateLimited { .. } => ErrorCategory::System, StorageError::Generic { .. } => ErrorCategory::System, StorageError::S3Error { .. } => ErrorCategory::Network, }; CommonError::service(category, err.to_string()) } } // Note: AWS SDK error conversions removed - we use object_store now #[cfg(test)] mod tests { use super::*; #[test] fn test_error_retryability() { assert!(StorageError::NetworkError { message: "test".to_string() } .is_retryable()); assert!(!StorageError::NotFound { path: "test".to_string() } .is_retryable()); assert!(StorageError::Timeout { timeout_ms: 1000 }.is_retryable()); } #[test] fn test_error_categories() { assert_eq!( StorageError::IoError { message: "test".to_string() } .category(), "io" ); assert_eq!( StorageError::NetworkError { message: "test".to_string() } .category(), "network" ); assert_eq!( StorageError::NotFound { path: "test".to_string() } .category(), "not_found" ); } #[test] fn test_error_transient() { assert!(StorageError::NetworkError { message: "test".to_string() } .is_transient()); assert!(!StorageError::ConfigError { message: "test".to_string() } .is_transient()); } #[test] fn test_safe_message() { let auth_error = StorageError::AuthError { message: "sensitive auth details".to_string(), }; let safe_msg = auth_error.safe_message(); assert!(safe_msg.contains("masked")); assert!(!safe_msg.contains("sensitive")); } }