Skip to main content

backbone_storage/
error.rs

1//! Storage module error types
2
3use thiserror::Error;
4
5/// Storage module result type
6pub type StorageResult<T> = Result<T, StorageError>;
7
8/// Storage error types
9#[derive(Error, Debug)]
10pub enum StorageError {
11    /// Configuration error
12    #[error("Configuration error: {0}")]
13    InvalidConfiguration(String),
14
15    /// Authentication error
16    #[error("Authentication error: {0}")]
17    AuthenticationError(String),
18
19    /// Permission error
20    #[error("Permission denied: {0}")]
21    PermissionDenied(String),
22
23    /// File not found
24    #[error("File not found: {0}")]
25    FileNotFound(String),
26
27    /// File already exists
28    #[error("File already exists: {0}")]
29    FileAlreadyExists(String),
30
31    /// Network connection error
32    #[error("Network error: {0}")]
33    NetworkError(String),
34
35    
36    /// Local filesystem error
37    #[error("Filesystem error: {0}")]
38    FilesystemError(#[from] std::io::Error),
39
40    /// URL parsing error
41    #[error("Invalid URL: {0}")]
42    InvalidUrl(#[from] url::ParseError),
43
44    /// Serialization/deserialization error
45    #[error("Serialization error: {0}")]
46    SerializationError(#[from] serde_json::Error),
47
48    /// Base64 encoding/decoding error
49    #[error("Base64 error: {0}")]
50    Base64Error(#[from] base64::DecodeError),
51
52    /// Invalid file format
53    #[error("Invalid file format: {0}")]
54    InvalidFileFormat(String),
55
56    /// File too large
57    #[error("File too large: {size} bytes, maximum allowed: {max_size} bytes")]
58    FileTooLarge {
59        size: u64,
60        max_size: u64,
61    },
62
63    /// Checksum mismatch
64    #[error("Checksum mismatch: expected {expected}, got {actual}")]
65    ChecksumMismatch {
66        expected: String,
67        actual: String,
68    },
69
70    /// Upload interrupted
71    #[error("Upload interrupted: {0}")]
72    UploadInterrupted(String),
73
74    /// Download interrupted
75    #[error("Download interrupted: {0}")]
76    DownloadInterrupted(String),
77
78    /// Multipart upload error
79    #[error("Multipart upload error: {0}")]
80    MultipartUploadError(String),
81
82    /// Presigned URL generation error
83    #[error("Presigned URL generation failed: {0}")]
84    PresignedUrlError(String),
85
86    /// Temporary storage error
87    #[error("Temporary storage error: {0}")]
88    TemporaryStorageError(String),
89
90    /// Storage quota exceeded
91    #[error("Storage quota exceeded: {used}/{limit} bytes")]
92    QuotaExceeded {
93        used: u64,
94        limit: u64,
95    },
96
97    /// Rate limited
98    #[error("Rate limited: try again in {retry_after} seconds")]
99    RateLimited {
100        retry_after: u64,
101    },
102
103    /// Invalid range request
104    #[error("Invalid range request: {0}")]
105    InvalidRange(String),
106
107    /// Timeout error
108    #[error("Operation timed out after {timeout} seconds")]
109    Timeout {
110        timeout: u64,
111    },
112
113    /// Generic operation error
114    #[error("Operation failed: {operation} - {message}")]
115    OperationFailed {
116        operation: String,
117        message: String,
118    },
119
120    /// Unsupported operation for backend
121    #[error("Unsupported operation '{operation}' for backend '{backend}'")]
122    UnsupportedOperation {
123        operation: String,
124        backend: String,
125    },
126
127    /// S3 operation error
128    #[error("S3 operation failed: {message}")]
129    S3OperationFailed {
130        message: String,
131        request_id: Option<String>,
132    },
133
134    /// GCS operation error
135    #[error("GCS error: {0}")]
136    GcsError(String),
137
138    /// MinIO specific errors
139    #[error("MinIO error: {0}")]
140    MinIOError(String),
141
142    /// HTTP client error
143    #[error("HTTP error: {0}")]
144    HttpError(#[from] reqwest::Error),
145
146    /// Security-related error
147    #[error("Security error: {operation} - {message}")]
148    SecurityError {
149        operation: String,
150        message: String,
151    },
152
153    /// Generic error
154    #[error("{0}")]
155    Other(String),
156}
157
158impl StorageError {
159    /// Create a configuration error
160    pub fn config_error(message: impl Into<String>) -> Self {
161        Self::InvalidConfiguration(message.into())
162    }
163
164    /// Create an authentication error
165    pub fn auth_error(message: impl Into<String>) -> Self {
166        Self::AuthenticationError(message.into())
167    }
168
169    /// Create a file not found error
170    pub fn not_found(file_path: impl Into<String>) -> Self {
171        Self::FileNotFound(file_path.into())
172    }
173
174    /// Create an operation failed error
175    pub fn operation_failed(operation: impl Into<String>, message: impl Into<String>) -> Self {
176        Self::OperationFailed {
177            operation: operation.into(),
178            message: message.into(),
179        }
180    }
181
182    
183    /// Check if this is a retryable error
184    pub fn is_retryable(&self) -> bool {
185        matches!(self,
186            Self::NetworkError(_)
187            | Self::HttpError(_)
188            | Self::Timeout { .. }
189            | Self::RateLimited { .. }
190        )
191    }
192
193    /// Check if this is a client error (4xx)
194    pub fn is_client_error(&self) -> bool {
195        match self {
196            Self::AuthenticationError(_)
197            | Self::PermissionDenied(_)
198            | Self::FileNotFound(_)
199            | Self::FileAlreadyExists(_)
200            | Self::InvalidUrl(_)
201            | Self::InvalidFileFormat(_)
202            | Self::FileTooLarge { .. }
203            | Self::ChecksumMismatch { .. }
204            | Self::InvalidRange(_)
205            | Self::UnsupportedOperation { .. } => true,
206            Self::HttpError(e) => e.status().is_some_and(|s| s.is_client_error()),
207            _ => false,
208        }
209    }
210
211    /// Check if this is a server error (5xx)
212    pub fn is_server_error(&self) -> bool {
213        match self {
214            Self::NetworkError(_)
215            | Self::TemporaryStorageError(_) => true,
216            Self::HttpError(e) => e.status().is_some_and(|s| s.is_server_error()),
217            _ => false,
218        }
219    }
220
221    /// Get error category for logging/metrics
222    pub fn category(&self) -> &'static str {
223        match self {
224            Self::InvalidConfiguration(_) => "configuration",
225            Self::AuthenticationError(_) | Self::PermissionDenied(_) => "authentication",
226            Self::FileNotFound(_) | Self::FileAlreadyExists(_) => "file_system",
227            Self::NetworkError(_) | Self::HttpError(_) => "network",
228            Self::FilesystemError(_) => "filesystem",
229            Self::SerializationError(_) | Self::Base64Error(_) => "serialization",
230            Self::FileTooLarge { .. } | Self::QuotaExceeded { .. } => "quota",
231            Self::RateLimited { .. } => "rate_limit",
232            Self::Timeout { .. } => "timeout",
233            _ => "other",
234        }
235    }
236}
237
238/// Convert HTTP status codes to storage errors
239impl From<reqwest::Response> for StorageError {
240    fn from(response: reqwest::Response) -> Self {
241        let status = response.status();
242        let url = response.url().clone();
243
244        match status.as_u16() {
245            400 => Self::InvalidConfiguration(format!("Bad request to {}", url)),
246            401 => Self::AuthenticationError("Unauthorized".to_string()),
247            403 => Self::PermissionDenied(format!("Access denied to {}", url)),
248            404 => Self::FileNotFound(format!("Resource not found at {}", url)),
249            409 => Self::FileAlreadyExists(format!("Resource already exists at {}", url)),
250            413 => Self::FileTooLarge { size: 0, max_size: 0 },
251            416 => Self::InvalidRange("Invalid range request".to_string()),
252            429 => Self::RateLimited { retry_after: 60 },
253            500..=599 => Self::NetworkError(format!("Server error {} from {}", status, url)),
254            _ => Self::Other(format!("HTTP {} from {}", status, url)),
255        }
256    }
257}