Files
foxhunt/storage/src/error.rs
jgrusewski 680646d6c3 🔧 Wave 30: Test Infrastructure + Critical Assessment (15 parallel agents)
## Summary
Mixed results: Test compilation improved 17% (145→120 errors), but warning
regression discovered (+141% from 136→328 warnings). Comprehensive production
readiness assessment completed.

## Achievements 
- **Test Compilation**: Reduced ML test errors 123→41 (66% improvement)
- **Test Infrastructure**: Fixed 16 risk compliance tests, 5 ML state tests
- **Service Warnings**: Fixed backtesting_service (11 files), ml-data (3 files)
- **Integration Tests**: Enhanced test_runner.rs with documentation
- **Test Helpers**: Added create_mock_features() and ML test utilities

## Critical Finding ⚠️
- **Warning Regression**: 136→328 warnings (+141% increase)
- **Root Cause**: Parallel agent chaos without coordination/quality gates
- **Impact**: Quality degradation blocks production readiness claim

## Files Modified (35 files)
- ML: selective_state.rs, lib.rs, benchmarks.rs, features.rs, test_common.rs
- Risk: compliance.rs (16 test fixes)
- Services: backtesting (11 files), ml-data (3 files)
- Storage/Config: Multiple warning fixes
- Tests: helpers.rs, test_runner.rs
- WAVE30_FINAL_ASSESSMENT.md: Comprehensive production analysis

## Test Compilation Status
- Production code:  0 errors (all services build)
- Test code: ⚠️ 120 errors (down from 145)
- ML crate: 80+ errors remain (types/imports)

## Production Assessment (70% Complete)
- Time to Ready: 2-3 weeks
- Blockers: Test suite, warning regression, S3 integration
- Estimated Work: 5-7 days warning cleanup, 2-3 days tests

## Wave 31 Roadmap
1. Fix warning regression (328→<50 target)
2. Complete test compilation fixes (120→0)
3. Add quality gates (pre-commit hooks, CI/CD)
4. Validate S3 model management
5. Performance validation (latency claims)

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-01 18:19:14 +02:00

333 lines
10 KiB
Rust

//! Error types for storage operations
use thiserror::Error;
use common::error::{CommonError, ErrorCategory};
/// Result type for storage operations
pub type StorageResult<T> = Result<T, StorageError>;
/// 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<CommonError>,
},
/// 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<u64> {
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<std::io::Error> 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<serde_json::Error> for StorageError {
fn from(err: serde_json::Error) -> Self {
StorageError::SerializationError {
message: err.to_string(),
}
}
}
impl From<bincode::Error> 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<StorageError> 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"));
}
}