1use std::fmt;
8
9#[derive(Debug)]
15pub enum StorageError {
16 ResourceNotFound {
18 tenant_id: String,
19 resource_type: String,
20 id: String,
21 },
22
23 ResourceAlreadyExists {
25 tenant_id: String,
26 resource_type: String,
27 id: String,
28 },
29
30 InvalidData {
32 message: String,
33 cause: Option<String>,
34 },
35
36 TenantNotFound { tenant_id: String },
38
39 InvalidQuery {
41 message: String,
42 attribute: Option<String>,
43 value: Option<String>,
44 },
45
46 CapacityExceeded {
48 message: String,
49 current_count: Option<usize>,
50 limit: Option<usize>,
51 },
52
53 ConcurrentModification {
55 tenant_id: String,
56 resource_type: String,
57 id: String,
58 expected_version: Option<String>,
59 actual_version: Option<String>,
60 },
61
62 Unavailable {
64 message: String,
65 retry_after: Option<std::time::Duration>,
66 },
67
68 PermissionDenied { operation: String, resource: String },
70
71 Timeout {
73 operation: String,
74 duration: std::time::Duration,
75 },
76
77 DataCorruption {
79 tenant_id: String,
80 resource_type: String,
81 id: Option<String>,
82 details: String,
83 },
84
85 Configuration {
87 message: String,
88 parameter: Option<String>,
89 },
90
91 Network {
93 message: String,
94 endpoint: Option<String>,
95 },
96
97 Serialization {
99 message: String,
100 data_type: Option<String>,
101 },
102
103 Internal {
105 message: String,
106 source: Option<Box<dyn std::error::Error + Send + Sync>>,
107 },
108}
109
110impl fmt::Display for StorageError {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 StorageError::ResourceNotFound {
114 tenant_id,
115 resource_type,
116 id,
117 } => {
118 write!(
119 f,
120 "Resource not found: {}/{}/{}",
121 tenant_id, resource_type, id
122 )
123 }
124 StorageError::ResourceAlreadyExists {
125 tenant_id,
126 resource_type,
127 id,
128 } => {
129 write!(
130 f,
131 "Resource already exists: {}/{}/{}",
132 tenant_id, resource_type, id
133 )
134 }
135 StorageError::InvalidData { message, cause } => {
136 if let Some(cause) = cause {
137 write!(f, "Invalid data: {} (cause: {})", message, cause)
138 } else {
139 write!(f, "Invalid data: {}", message)
140 }
141 }
142 StorageError::TenantNotFound { tenant_id } => {
143 write!(f, "Tenant not found: {}", tenant_id)
144 }
145 StorageError::InvalidQuery {
146 message,
147 attribute,
148 value,
149 } => match (attribute, value) {
150 (Some(attr), Some(val)) => {
151 write!(
152 f,
153 "Invalid query: {} (attribute: {}, value: {})",
154 message, attr, val
155 )
156 }
157 (Some(attr), None) => {
158 write!(f, "Invalid query: {} (attribute: {})", message, attr)
159 }
160 _ => write!(f, "Invalid query: {}", message),
161 },
162 StorageError::CapacityExceeded {
163 message,
164 current_count,
165 limit,
166 } => match (current_count, limit) {
167 (Some(current), Some(max)) => {
168 write!(f, "Capacity exceeded: {} ({}/{})", message, current, max)
169 }
170 _ => write!(f, "Capacity exceeded: {}", message),
171 },
172 StorageError::ConcurrentModification {
173 tenant_id,
174 resource_type,
175 id,
176 expected_version,
177 actual_version,
178 } => match (expected_version, actual_version) {
179 (Some(expected), Some(actual)) => {
180 write!(
181 f,
182 "Concurrent modification detected for {}/{}/{}: expected version {}, found {}",
183 tenant_id, resource_type, id, expected, actual
184 )
185 }
186 _ => {
187 write!(
188 f,
189 "Concurrent modification detected for {}/{}/{}",
190 tenant_id, resource_type, id
191 )
192 }
193 },
194 StorageError::Unavailable {
195 message,
196 retry_after,
197 } => {
198 if let Some(duration) = retry_after {
199 write!(
200 f,
201 "Storage unavailable: {} (retry after {:?})",
202 message, duration
203 )
204 } else {
205 write!(f, "Storage unavailable: {}", message)
206 }
207 }
208 StorageError::PermissionDenied {
209 operation,
210 resource,
211 } => {
212 write!(f, "Permission denied: {} on {}", operation, resource)
213 }
214 StorageError::Timeout {
215 operation,
216 duration,
217 } => {
218 write!(f, "Timeout during {} after {:?}", operation, duration)
219 }
220 StorageError::DataCorruption {
221 tenant_id,
222 resource_type,
223 id,
224 details,
225 } => {
226 if let Some(resource_id) = id {
227 write!(
228 f,
229 "Data corruption in {}/{}/{}: {}",
230 tenant_id, resource_type, resource_id, details
231 )
232 } else {
233 write!(
234 f,
235 "Data corruption in {}/{}: {}",
236 tenant_id, resource_type, details
237 )
238 }
239 }
240 StorageError::Configuration { message, parameter } => {
241 if let Some(param) = parameter {
242 write!(f, "Configuration error: {} (parameter: {})", message, param)
243 } else {
244 write!(f, "Configuration error: {}", message)
245 }
246 }
247 StorageError::Network { message, endpoint } => {
248 if let Some(ep) = endpoint {
249 write!(f, "Network error: {} (endpoint: {})", message, ep)
250 } else {
251 write!(f, "Network error: {}", message)
252 }
253 }
254 StorageError::Serialization { message, data_type } => {
255 if let Some(dtype) = data_type {
256 write!(f, "Serialization error: {} (type: {})", message, dtype)
257 } else {
258 write!(f, "Serialization error: {}", message)
259 }
260 }
261 StorageError::Internal { message, .. } => {
262 write!(f, "Internal storage error: {}", message)
263 }
264 }
265 }
266}
267
268impl std::error::Error for StorageError {
269 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
270 match self {
271 StorageError::Internal { source, .. } => source
272 .as_ref()
273 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)),
274 _ => None,
275 }
276 }
277}
278
279impl StorageError {
280 pub fn resource_not_found(
282 tenant_id: impl Into<String>,
283 resource_type: impl Into<String>,
284 id: impl Into<String>,
285 ) -> Self {
286 Self::ResourceNotFound {
287 tenant_id: tenant_id.into(),
288 resource_type: resource_type.into(),
289 id: id.into(),
290 }
291 }
292
293 pub fn resource_already_exists(
295 tenant_id: impl Into<String>,
296 resource_type: impl Into<String>,
297 id: impl Into<String>,
298 ) -> Self {
299 Self::ResourceAlreadyExists {
300 tenant_id: tenant_id.into(),
301 resource_type: resource_type.into(),
302 id: id.into(),
303 }
304 }
305
306 pub fn invalid_data(message: impl Into<String>) -> Self {
308 Self::InvalidData {
309 message: message.into(),
310 cause: None,
311 }
312 }
313
314 pub fn invalid_data_with_cause(message: impl Into<String>, cause: impl Into<String>) -> Self {
316 Self::InvalidData {
317 message: message.into(),
318 cause: Some(cause.into()),
319 }
320 }
321
322 pub fn tenant_not_found(tenant_id: impl Into<String>) -> Self {
324 Self::TenantNotFound {
325 tenant_id: tenant_id.into(),
326 }
327 }
328
329 pub fn invalid_query(message: impl Into<String>) -> Self {
331 Self::InvalidQuery {
332 message: message.into(),
333 attribute: None,
334 value: None,
335 }
336 }
337
338 pub fn capacity_exceeded(message: impl Into<String>) -> Self {
340 Self::CapacityExceeded {
341 message: message.into(),
342 current_count: None,
343 limit: None,
344 }
345 }
346
347 pub fn concurrent_modification(
349 tenant_id: impl Into<String>,
350 resource_type: impl Into<String>,
351 id: impl Into<String>,
352 ) -> Self {
353 Self::ConcurrentModification {
354 tenant_id: tenant_id.into(),
355 resource_type: resource_type.into(),
356 id: id.into(),
357 expected_version: None,
358 actual_version: None,
359 }
360 }
361
362 pub fn unavailable(message: impl Into<String>) -> Self {
364 Self::Unavailable {
365 message: message.into(),
366 retry_after: None,
367 }
368 }
369
370 pub fn permission_denied(operation: impl Into<String>, resource: impl Into<String>) -> Self {
372 Self::PermissionDenied {
373 operation: operation.into(),
374 resource: resource.into(),
375 }
376 }
377
378 pub fn timeout(operation: impl Into<String>, duration: std::time::Duration) -> Self {
380 Self::Timeout {
381 operation: operation.into(),
382 duration,
383 }
384 }
385
386 pub fn data_corruption(
388 tenant_id: impl Into<String>,
389 resource_type: impl Into<String>,
390 details: impl Into<String>,
391 ) -> Self {
392 Self::DataCorruption {
393 tenant_id: tenant_id.into(),
394 resource_type: resource_type.into(),
395 id: None,
396 details: details.into(),
397 }
398 }
399
400 pub fn configuration(message: impl Into<String>) -> Self {
402 Self::Configuration {
403 message: message.into(),
404 parameter: None,
405 }
406 }
407
408 pub fn network(message: impl Into<String>) -> Self {
410 Self::Network {
411 message: message.into(),
412 endpoint: None,
413 }
414 }
415
416 pub fn serialization(message: impl Into<String>) -> Self {
418 Self::Serialization {
419 message: message.into(),
420 data_type: None,
421 }
422 }
423
424 pub fn internal(message: impl Into<String>) -> Self {
426 Self::Internal {
427 message: message.into(),
428 source: None,
429 }
430 }
431
432 pub fn internal_with_source(
434 message: impl Into<String>,
435 source: Box<dyn std::error::Error + Send + Sync>,
436 ) -> Self {
437 Self::Internal {
438 message: message.into(),
439 source: Some(source),
440 }
441 }
442
443 pub fn is_not_found(&self) -> bool {
445 matches!(self, StorageError::ResourceNotFound { .. })
446 }
447
448 pub fn is_conflict(&self) -> bool {
450 matches!(
451 self,
452 StorageError::ResourceAlreadyExists { .. }
453 | StorageError::ConcurrentModification { .. }
454 )
455 }
456
457 pub fn is_temporary(&self) -> bool {
459 matches!(
460 self,
461 StorageError::Unavailable { .. }
462 | StorageError::Timeout { .. }
463 | StorageError::Network { .. }
464 )
465 }
466
467 pub fn is_invalid_input(&self) -> bool {
469 matches!(
470 self,
471 StorageError::InvalidData { .. } | StorageError::InvalidQuery { .. }
472 )
473 }
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479
480 #[test]
481 fn test_storage_error_display() {
482 let error = StorageError::resource_not_found("tenant1", "User", "123");
483 assert_eq!(error.to_string(), "Resource not found: tenant1/User/123");
484
485 let error = StorageError::invalid_data("malformed JSON");
486 assert_eq!(error.to_string(), "Invalid data: malformed JSON");
487
488 let error = StorageError::capacity_exceeded("disk full");
489 assert_eq!(error.to_string(), "Capacity exceeded: disk full");
490 }
491
492 #[test]
493 fn test_storage_error_type_checks() {
494 let not_found = StorageError::resource_not_found("tenant1", "User", "123");
495 assert!(not_found.is_not_found());
496 assert!(!not_found.is_conflict());
497 assert!(!not_found.is_temporary());
498
499 let conflict = StorageError::resource_already_exists("tenant1", "User", "123");
500 assert!(!conflict.is_not_found());
501 assert!(conflict.is_conflict());
502 assert!(!conflict.is_temporary());
503
504 let timeout = StorageError::timeout("query", std::time::Duration::from_secs(30));
505 assert!(!timeout.is_not_found());
506 assert!(!timeout.is_conflict());
507 assert!(timeout.is_temporary());
508
509 let invalid = StorageError::invalid_data("bad format");
510 assert!(invalid.is_invalid_input());
511 }
512
513 #[test]
514 fn test_storage_error_constructors() {
515 let error = StorageError::invalid_data_with_cause("parse error", "unexpected token");
516 if let StorageError::InvalidData { message, cause } = error {
517 assert_eq!(message, "parse error");
518 assert_eq!(cause, Some("unexpected token".to_string()));
519 } else {
520 panic!("Expected InvalidData error");
521 }
522
523 let error = StorageError::concurrent_modification("tenant1", "User", "123");
524 if let StorageError::ConcurrentModification {
525 tenant_id,
526 resource_type,
527 id,
528 ..
529 } = error
530 {
531 assert_eq!(tenant_id, "tenant1");
532 assert_eq!(resource_type, "User");
533 assert_eq!(id, "123");
534 } else {
535 panic!("Expected ConcurrentModification error");
536 }
537 }
538}