Skip to main content

mediagit_storage/
error.rs

1// MediaGit - Git for Media Files
2// Copyright (C) 2025 MediaGit Contributors
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13
14//! Storage error types and utilities
15
16use std::io;
17use thiserror::Error;
18
19/// Result type alias for storage operations
20pub type StorageResult<T> = Result<T, StorageError>;
21
22/// Errors that can occur during storage operations
23#[derive(Error, Debug)]
24pub enum StorageError {
25    /// Object not found in storage
26    #[error("object not found: {0}")]
27    NotFound(String),
28
29    /// Permission denied for the requested operation
30    #[error("permission denied: {0}")]
31    PermissionDenied(String),
32
33    /// I/O error occurred
34    #[error("I/O error: {0}")]
35    Io(#[from] io::Error),
36
37    /// Invalid key format (empty, contains invalid characters, etc.)
38    #[error("invalid key: {0}")]
39    InvalidKey(String),
40
41    /// Storage backend not available or misconfigured
42    #[error("storage backend error: {0}")]
43    Backend(String),
44
45    /// Operation timed out
46    #[error("operation timed out: {0}")]
47    Timeout(String),
48
49    /// Transparent error delegation for wrapped error types
50    ///
51    /// This variant allows wrapping other error types (like anyhow::Error)
52    /// while forwarding their Display and source implementations transparently.
53    /// Useful for catch-all error handling and opaque error types.
54    #[error(transparent)]
55    Other(#[from] anyhow::Error),
56}
57
58impl StorageError {
59    /// Create a NotFound error with the given key
60    pub fn not_found<S: Into<String>>(key: S) -> Self {
61        StorageError::NotFound(key.into())
62    }
63
64    /// Create a PermissionDenied error with context
65    pub fn permission_denied<S: Into<String>>(msg: S) -> Self {
66        StorageError::PermissionDenied(msg.into())
67    }
68
69    /// Create an InvalidKey error with context
70    pub fn invalid_key<S: Into<String>>(msg: S) -> Self {
71        StorageError::InvalidKey(msg.into())
72    }
73
74    /// Create a Backend error with context
75    pub fn backend<S: Into<String>>(msg: S) -> Self {
76        StorageError::Backend(msg.into())
77    }
78
79    /// Create a Timeout error with context
80    pub fn timeout<S: Into<String>>(msg: S) -> Self {
81        StorageError::Timeout(msg.into())
82    }
83
84    /// Create a generic error from any error type that can convert to anyhow::Error
85    pub fn other<E: Into<anyhow::Error>>(error: E) -> Self {
86        StorageError::Other(error.into())
87    }
88
89    /// Check if this is a NotFound error
90    pub fn is_not_found(&self) -> bool {
91        matches!(self, StorageError::NotFound(_))
92    }
93
94    /// Check if this is a PermissionDenied error
95    pub fn is_permission_denied(&self) -> bool {
96        matches!(self, StorageError::PermissionDenied(_))
97    }
98
99    /// Check if this is an InvalidKey error
100    pub fn is_invalid_key(&self) -> bool {
101        matches!(self, StorageError::InvalidKey(_))
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_error_creation() {
111        let err = StorageError::not_found("test_key");
112        assert!(err.is_not_found());
113        assert_eq!(err.to_string(), "object not found: test_key");
114    }
115
116    #[test]
117    fn test_permission_denied_error() {
118        let err = StorageError::permission_denied("bucket locked");
119        assert!(err.is_permission_denied());
120    }
121
122    #[test]
123    fn test_invalid_key_error() {
124        let err = StorageError::invalid_key("empty key");
125        assert!(err.is_invalid_key());
126    }
127
128    #[test]
129    fn test_io_error_conversion() {
130        let io_err = io::Error::other("read failed");
131        let storage_err = StorageError::from(io_err);
132        assert!(matches!(storage_err, StorageError::Io(_)));
133    }
134}