umi_memory/storage/
error.rs

1//! Storage Errors
2//!
3//! TigerStyle: Explicit error types with context.
4
5use thiserror::Error;
6
7/// Errors from storage operations.
8#[derive(Debug, Clone, Error)]
9pub enum StorageError {
10    /// Entity not found
11    #[error("entity not found: {id}")]
12    NotFound {
13        /// Entity ID that was not found
14        id: String,
15    },
16
17    /// Entity already exists (for insert-only operations)
18    #[error("entity already exists: {id}")]
19    AlreadyExists {
20        /// Entity ID that already exists
21        id: String,
22    },
23
24    /// Validation error
25    #[error("validation error: {message}")]
26    Validation {
27        /// Validation error message
28        message: String,
29    },
30
31    /// Connection error
32    #[error("connection error: {message}")]
33    Connection {
34        /// Connection error message
35        message: String,
36    },
37
38    /// Query error
39    #[error("query error: {message}")]
40    Query {
41        /// Query error message
42        message: String,
43    },
44
45    /// Timeout error
46    #[error("timeout after {duration_ms}ms")]
47    Timeout {
48        /// Duration in milliseconds
49        duration_ms: u64,
50    },
51
52    /// Simulated fault (for DST)
53    #[error("simulated fault: {fault_type}")]
54    SimulatedFault {
55        /// Type of simulated fault
56        fault_type: String,
57    },
58
59    /// Internal error
60    #[error("internal error: {message}")]
61    Internal {
62        /// Error message
63        message: String,
64    },
65
66    /// Connection failed
67    #[error("connection failed: {0}")]
68    ConnectionFailed(String),
69
70    /// Write failed
71    #[error("write failed: {0}")]
72    WriteFailed(String),
73
74    /// Read failed
75    #[error("read failed: {0}")]
76    ReadFailed(String),
77
78    /// Serialization error
79    #[error("serialization error: {0}")]
80    SerializationError(String),
81
82    /// Deserialization error
83    #[error("deserialization error: {0}")]
84    DeserializationError(String),
85}
86
87impl StorageError {
88    /// Create a not found error.
89    #[must_use]
90    pub fn not_found(id: impl Into<String>) -> Self {
91        Self::NotFound { id: id.into() }
92    }
93
94    /// Create an already exists error.
95    #[must_use]
96    pub fn already_exists(id: impl Into<String>) -> Self {
97        Self::AlreadyExists { id: id.into() }
98    }
99
100    /// Create a validation error.
101    #[must_use]
102    pub fn validation(message: impl Into<String>) -> Self {
103        Self::Validation {
104            message: message.into(),
105        }
106    }
107
108    /// Create a connection error.
109    #[must_use]
110    pub fn connection(message: impl Into<String>) -> Self {
111        Self::Connection {
112            message: message.into(),
113        }
114    }
115
116    /// Create a query error.
117    #[must_use]
118    pub fn query(message: impl Into<String>) -> Self {
119        Self::Query {
120            message: message.into(),
121        }
122    }
123
124    /// Create a timeout error.
125    #[must_use]
126    pub fn timeout(duration_ms: u64) -> Self {
127        Self::Timeout { duration_ms }
128    }
129
130    /// Create a simulated fault error.
131    #[must_use]
132    pub fn simulated_fault(fault_type: impl Into<String>) -> Self {
133        Self::SimulatedFault {
134            fault_type: fault_type.into(),
135        }
136    }
137
138    /// Create an internal error.
139    #[must_use]
140    pub fn internal(message: impl Into<String>) -> Self {
141        Self::Internal {
142            message: message.into(),
143        }
144    }
145
146    /// Create a read error (wraps query error for reads).
147    #[must_use]
148    pub fn read(message: impl Into<String>) -> Self {
149        Self::Query {
150            message: format!("read: {}", message.into()),
151        }
152    }
153
154    /// Create a write error (wraps query error for writes).
155    #[must_use]
156    pub fn write(message: impl Into<String>) -> Self {
157        Self::Query {
158            message: format!("write: {}", message.into()),
159        }
160    }
161
162    /// Check if this is a transient error (can be retried).
163    #[must_use]
164    pub fn is_transient(&self) -> bool {
165        matches!(
166            self,
167            Self::Connection { .. } | Self::Timeout { .. } | Self::SimulatedFault { .. }
168        )
169    }
170}
171
172/// Result type for storage operations.
173pub type StorageResult<T> = Result<T, StorageError>;
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_error_constructors() {
181        let err = StorageError::not_found("test-id");
182        assert!(matches!(err, StorageError::NotFound { id } if id == "test-id"));
183
184        let err = StorageError::validation("invalid content");
185        assert!(
186            matches!(err, StorageError::Validation { message } if message == "invalid content")
187        );
188    }
189
190    #[test]
191    fn test_is_transient() {
192        assert!(StorageError::connection("failed").is_transient());
193        assert!(StorageError::timeout(1000).is_transient());
194        assert!(StorageError::simulated_fault("network").is_transient());
195
196        assert!(!StorageError::not_found("id").is_transient());
197        assert!(!StorageError::validation("bad").is_transient());
198    }
199}