rust_keyvault/
error.rs

1//! Error types for rust-keyvault
2
3use std::fmt;
4use thiserror::Error;
5
6/// Custom `Result` type
7pub type Result<T> = std::result::Result<T, Error>;
8
9/// Error codes for programmatic handling
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ErrorCode {
12    /// Key not found (404-equivalent)
13    KeyNotFound,
14    /// Key expired (410-equivalent)
15    KeyExpired,
16    /// Invalid key state for operation
17    InvalidKeyState,
18    /// Cryptographic operation failed
19    CryptoFailure,
20    /// Storage backend error
21    StorageFailure,
22    /// Insufficient entropy
23    InsufficientEntropy,
24    /// Key rotation failed
25    RotationFailure,
26    /// Serialization/deserialization failed
27    SerializationFailure,
28    /// I/O error
29    IoFailure,
30    /// Authentication failed
31    AuthenticationFailure,
32    /// Configuration error
33    ConfigurationError,
34}
35
36impl fmt::Display for ErrorCode {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        match self {
39            Self::KeyNotFound => write!(f, "KEY_NOT_FOUND"),
40            Self::KeyExpired => write!(f, "KEY_EXPIRED"),
41            Self::InvalidKeyState => write!(f, "INVALID_KEY_STATE"),
42            Self::CryptoFailure => write!(f, "CRYPTO_FAILURE"),
43            Self::StorageFailure => write!(f, "STORAGE_FAILURE"),
44            Self::InsufficientEntropy => write!(f, "INSUFFICIENT_ENTROPY"),
45            Self::RotationFailure => write!(f, "ROTATION_FAILURE"),
46            Self::SerializationFailure => write!(f, "SERIALIZATION_FAILURE"),
47            Self::IoFailure => write!(f, "IO_FAILURE"),
48            Self::AuthenticationFailure => write!(f, "AUTHENTICATION_FAILURE"),
49            Self::ConfigurationError => write!(f, "CONFIGURATION_ERROR"),
50        }
51    }
52}
53
54/// Context about what operation was being performed when error occurred
55#[derive(Debug, Clone)]
56pub struct ErrorContext {
57    /// The operation that was being performed
58    pub operation: String,
59    /// Optional key ID involved
60    pub key_id: Option<String>,
61    /// Optional additional details
62    pub details: Option<String>,
63}
64
65impl ErrorContext {
66    /// Create a new error context
67    pub fn new<S: Into<String>>(operation: S) -> Self {
68        Self {
69            operation: operation.into(),
70            key_id: None,
71            details: None,
72        }
73    }
74
75    /// Add key ID to context
76    pub fn with_key_id<S: Into<String>>(mut self, key_id: S) -> Self {
77        self.key_id = Some(key_id.into());
78        self
79    }
80
81    /// Add details to context
82    pub fn with_details<S: Into<String>>(mut self, details: S) -> Self {
83        self.details = Some(details.into());
84        self
85    }
86}
87
88/// `rust-keyvault` error type with enhanced context
89#[derive(Debug, Error)]
90pub enum Error {
91    /// Key not found in storage
92    #[error("key not found: {key_id} (operation: {operation})")]
93    KeyNotFound {
94        /// The ID of the key that was not found
95        key_id: String,
96        /// The operation that was attempting to access the key
97        operation: String,
98    },
99
100    /// Key has expired
101    #[error("key has expired: {key_id} (expired at: {expired_at:?})")]
102    KeyExpired {
103        /// The ID of the expired key
104        key_id: String,
105        /// When the key expired
106        expired_at: std::time::SystemTime,
107    },
108
109    /// Key is in the wrong state for requested operation
110    #[error("invalid key state: {state} for operation '{operation}' on key {key_id}")]
111    InvalidKeyState {
112        /// The ID of the key with invalid state
113        key_id: String,
114        /// The current state of the key
115        state: String,
116        /// The operation that was attempted
117        operation: String,
118    },
119
120    /// Cryptographic operation failed
121    #[error("cryptographic error during {operation}: {message}")]
122    CryptoError {
123        /// The operation that was being performed when the error occurred
124        operation: String,
125        /// Detailed error message
126        message: String,
127        /// Optional key ID involved in the operation
128        key_id: Option<String>,
129    },
130
131    /// Storage backend failed
132    #[error("storage error during {operation}: {message}")]
133    StorageError {
134        /// The operation that was being performed when the error occurred
135        operation: String,
136        /// Detailed error message
137        message: String,
138        /// Optional filesystem path involved in the operation
139        path: Option<String>,
140    },
141
142    /// Insufficient entropy available
143    #[error("insufficient entropy for operation: {operation}")]
144    InsufficientEntropy {
145        /// The operation that required entropy
146        operation: String,
147    },
148
149    /// Key rotation failed
150    #[error("rotation failed for key {key_id}: {reason}")]
151    RotationFailed {
152        /// The ID of the key that failed to rotate
153        key_id: String,
154        /// The reason rotation failed
155        reason: String,
156    },
157
158    /// Serialization/deserialization error
159    #[error("serialization error during {operation}: {message}")]
160    SerializationError {
161        /// The serialization operation that failed
162        operation: String,
163        /// Detailed error message
164        message: String,
165    },
166
167    /// Generic I/O error with context
168    #[error("I/O error during {operation}: {source}")]
169    IoError {
170        /// The I/O operation that failed
171        operation: String,
172        /// The underlying I/O error
173        #[source]
174        source: std::io::Error,
175    },
176
177    /// Authentication failed
178    #[error("authentication failed: {reason}")]
179    AuthenticationFailed {
180        /// The reason authentication failed
181        reason: String,
182        /// Number of failed attempts, if tracked
183        attempts: Option<u32>,
184    },
185
186    /// Configuration error
187    #[error("configuration error: {message}")]
188    ConfigurationError {
189        /// Description of the configuration problem
190        message: String,
191    },
192}
193
194impl Error {
195    /// Get the error code for programmatic handling
196    pub fn code(&self) -> ErrorCode {
197        match self {
198            Self::KeyNotFound { .. } => ErrorCode::KeyNotFound,
199            Self::KeyExpired { .. } => ErrorCode::KeyExpired,
200            Self::InvalidKeyState { .. } => ErrorCode::InvalidKeyState,
201            Self::CryptoError { .. } => ErrorCode::CryptoFailure,
202            Self::StorageError { .. } => ErrorCode::StorageFailure,
203            Self::InsufficientEntropy { .. } => ErrorCode::InsufficientEntropy,
204            Self::RotationFailed { .. } => ErrorCode::RotationFailure,
205            Self::SerializationError { .. } => ErrorCode::SerializationFailure,
206            Self::IoError { .. } => ErrorCode::IoFailure,
207            Self::AuthenticationFailed { .. } => ErrorCode::AuthenticationFailure,
208            Self::ConfigurationError { .. } => ErrorCode::ConfigurationError,
209        }
210    }
211
212    /// Create a crypto error with context
213    pub fn crypto<S: Into<String>>(operation: S, message: S) -> Self {
214        Self::CryptoError {
215            operation: operation.into(),
216            message: message.into(),
217            key_id: None,
218        }
219    }
220
221    /// Create a crypto error with key context
222    pub fn crypto_with_key<S: Into<String>>(operation: S, message: S, key_id: S) -> Self {
223        Self::CryptoError {
224            operation: operation.into(),
225            message: message.into(),
226            key_id: Some(key_id.into()),
227        }
228    }
229
230    /// Create a storage error with context
231    pub fn storage<S: Into<String>>(operation: S, message: S) -> Self {
232        Self::StorageError {
233            operation: operation.into(),
234            message: message.into(),
235            path: None,
236        }
237    }
238
239    /// Create a storage error with path context
240    pub fn storage_with_path<S: Into<String>>(operation: S, message: S, path: S) -> Self {
241        Self::StorageError {
242            operation: operation.into(),
243            message: message.into(),
244            path: Some(path.into()),
245        }
246    }
247
248    /// Check if error is retryable
249    pub fn is_retryable(&self) -> bool {
250        matches!(self, Self::StorageError { .. } | Self::IoError { .. })
251    }
252
253    /// Check if error is a authentication failure
254    pub fn is_auth_failure(&self) -> bool {
255        matches!(self, Self::AuthenticationFailed { .. })
256    }
257}
258
259// Implement From for io::Error with context helper
260impl From<std::io::Error> for Error {
261    fn from(err: std::io::Error) -> Self {
262        Self::IoError {
263            operation: "unknown".to_string(),
264            source: err,
265        }
266    }
267}