Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica

eidetica/auth/
errors.rs

1//! Authentication error types for the Eidetica library.
2//!
3//! This module defines structured error types for authentication-related operations,
4//! providing better error context and type safety compared to string-based errors.
5
6use thiserror::Error as ThisError;
7
8use crate::Error;
9use crate::entry::ID;
10
11/// Errors that can occur during authentication operations.
12///
13/// # Stability
14///
15/// - New variants may be added in minor versions (enum is `#[non_exhaustive]`)
16/// - Existing variants will not be removed in minor versions
17/// - Field additions/changes require a major version bump
18/// - Helper methods like `is_*()` provide stable APIs
19#[non_exhaustive]
20#[derive(Debug, ThisError)]
21pub enum AuthError {
22    /// A requested authentication key was not found in the configuration.
23    #[error("Key not found: {key_name}")]
24    KeyNotFound {
25        /// The name of the key that was not found
26        key_name: String,
27    },
28
29    /// Invalid key format or parsing error.
30    #[error("Invalid key format: {reason}")]
31    InvalidKeyFormat {
32        /// Description of why the key format is invalid
33        reason: String,
34    },
35
36    /// Key parsing failed due to cryptographic library error.
37    #[error("Key parsing failed: {reason}")]
38    KeyParsingFailed {
39        /// Description of the parsing failure
40        reason: String,
41    },
42
43    /// No authentication configuration was found.
44    #[error("No auth configuration found")]
45    NoAuthConfiguration,
46
47    /// The authentication configuration is invalid.
48    #[error("Invalid auth configuration: {reason}")]
49    InvalidAuthConfiguration {
50        /// Description of why the configuration is invalid
51        reason: String,
52    },
53
54    /// Delegation path is empty when it should contain at least one step.
55    #[error("Empty delegation path")]
56    EmptyDelegationPath,
57
58    /// Maximum delegation depth was exceeded to prevent infinite loops.
59    #[error("Maximum delegation depth ({depth}) exceeded")]
60    DelegationDepthExceeded {
61        /// The maximum depth that was exceeded
62        depth: usize,
63    },
64
65    /// A delegation step is invalid.
66    #[error("Invalid delegation step: {reason}")]
67    InvalidDelegationStep {
68        /// Description of why the delegation step is invalid
69        reason: String,
70    },
71
72    /// Failed to load a delegated tree.
73    #[error("Failed to load delegated tree {tree_id}")]
74    DelegatedTreeLoadFailed {
75        /// The ID of the tree that failed to load
76        tree_id: String,
77        /// The underlying error
78        #[source]
79        source: Box<Error>,
80    },
81
82    /// Delegation tips don't match the actual tree state.
83    #[error(
84        "Invalid delegation tips for tree {tree_id}: claimed tips {claimed_tips:?} don't match"
85    )]
86    InvalidDelegationTips {
87        /// The ID of the tree with invalid tips
88        tree_id: String,
89        /// The tips that were claimed but are invalid
90        claimed_tips: Vec<ID>,
91    },
92
93    /// A delegated tree reference was not found in the configuration.
94    #[error("Delegation not found for tree: {tree_id}")]
95    DelegationNotFound {
96        /// The root tree ID of the delegation that was not found
97        tree_id: String,
98    },
99
100    /// Attempted to revoke an entry that is not a key.
101    #[error("Cannot revoke non-key entry: {key_name}")]
102    CannotRevokeNonKey {
103        /// The name of the entry that is not a key
104        key_name: String,
105    },
106
107    /// Entry has malformed signature info (e.g., hint without signature).
108    #[error("Malformed entry: {reason}")]
109    MalformedEntry {
110        /// Description of why the entry is malformed
111        reason: &'static str,
112    },
113
114    /// Signature verification failed.
115    #[error("Invalid signature")]
116    InvalidSignature,
117
118    /// Signature verification failed with specific error.
119    #[error("Signature verification failed: {reason}")]
120    SignatureVerificationFailed {
121        /// Description of the verification failure
122        reason: String,
123    },
124
125    /// Database is required for the operation but not available.
126    #[error("Database required for {operation}")]
127    DatabaseRequired {
128        /// The operation that requires a database
129        operation: String,
130    },
131
132    /// Invalid permission string format.
133    #[error("Invalid permission string: {value}")]
134    InvalidPermissionString {
135        /// The invalid permission string
136        value: String,
137    },
138
139    /// Permission type requires a priority value.
140    #[error("{permission_type} permission requires priority")]
141    PermissionRequiresPriority {
142        /// The permission type that requires priority
143        permission_type: String,
144    },
145
146    /// Invalid priority value.
147    #[error("Invalid priority value: {value}")]
148    InvalidPriorityValue {
149        /// The invalid priority value
150        value: String,
151    },
152
153    /// Invalid key status string.
154    #[error("Invalid key status: {value}")]
155    InvalidKeyStatus {
156        /// The invalid status value
157        value: String,
158    },
159
160    /// Permission denied for an operation.
161    #[error("Permission denied: {reason}")]
162    PermissionDenied {
163        /// Description of why permission was denied
164        reason: String,
165    },
166
167    /// Attempted to add a key that already exists.
168    #[error("Key already exists: {key_name}")]
169    KeyAlreadyExists {
170        /// The name of the key that already exists
171        key_name: String,
172    },
173
174    /// Key name conflicts with existing key that has different public key.
175    #[error(
176        "Key name '{key_name}' conflicts: existing key has pubkey '{existing_pubkey}', new key has pubkey '{new_pubkey}'"
177    )]
178    KeyNameConflict {
179        /// The name of the conflicting key
180        key_name: String,
181        /// The public key of the existing key
182        existing_pubkey: String,
183        /// The public key of the new key
184        new_pubkey: String,
185    },
186
187    /// Signing key does not match the claimed identity in a DatabaseKey.
188    #[error("Signing key mismatch: {reason}")]
189    SigningKeyMismatch {
190        /// Description of the mismatch
191        reason: String,
192    },
193}
194
195impl AuthError {
196    /// Check if this error indicates a key or delegation was not found.
197    pub fn is_not_found(&self) -> bool {
198        matches!(
199            self,
200            AuthError::KeyNotFound { .. } | AuthError::DelegationNotFound { .. }
201        )
202    }
203
204    /// Check if this error indicates invalid signature.
205    pub fn is_invalid_signature(&self) -> bool {
206        matches!(
207            self,
208            AuthError::InvalidSignature | AuthError::SignatureVerificationFailed { .. }
209        )
210    }
211
212    /// Check if this error indicates permission was denied.
213    pub fn is_permission_denied(&self) -> bool {
214        matches!(self, AuthError::PermissionDenied { .. })
215    }
216
217    /// Check if this error indicates a key already exists.
218    pub fn is_key_already_exists(&self) -> bool {
219        matches!(self, AuthError::KeyAlreadyExists { .. })
220    }
221
222    /// Check if this error indicates a key name conflict.
223    pub fn is_key_name_conflict(&self) -> bool {
224        matches!(self, AuthError::KeyNameConflict { .. })
225    }
226
227    /// Check if this error indicates a configuration problem.
228    pub fn is_configuration_error(&self) -> bool {
229        matches!(
230            self,
231            AuthError::NoAuthConfiguration
232                | AuthError::InvalidAuthConfiguration { .. }
233                | AuthError::InvalidKeyFormat { .. }
234                | AuthError::KeyParsingFailed { .. }
235        )
236    }
237
238    /// Check if this error is related to delegation.
239    pub fn is_delegation_error(&self) -> bool {
240        matches!(
241            self,
242            AuthError::EmptyDelegationPath
243                | AuthError::DelegationDepthExceeded { .. }
244                | AuthError::InvalidDelegationStep { .. }
245                | AuthError::DelegatedTreeLoadFailed { .. }
246                | AuthError::InvalidDelegationTips { .. }
247                | AuthError::DelegationNotFound { .. }
248        )
249    }
250
251    /// Get the key name if this error is about a missing key.
252    pub fn key_name(&self) -> Option<&str> {
253        match self {
254            AuthError::KeyNotFound { key_name: id } => Some(id),
255            _ => None,
256        }
257    }
258}
259
260// Conversion from AuthError to the main Error type
261impl From<AuthError> for Error {
262    fn from(err: AuthError) -> Self {
263        // Use the new structured Auth variant
264        Error::Auth(err)
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271
272    #[test]
273    fn test_error_helpers() {
274        let err = AuthError::KeyNotFound {
275            key_name: "test-key".to_string(),
276        };
277        assert!(err.is_not_found());
278        assert_eq!(err.key_name(), Some("test-key"));
279
280        let err = AuthError::InvalidSignature;
281        assert!(err.is_invalid_signature());
282
283        let err = AuthError::PermissionDenied {
284            reason: "test".to_string(),
285        };
286        assert!(err.is_permission_denied());
287
288        let err = AuthError::NoAuthConfiguration;
289        assert!(err.is_configuration_error());
290
291        let err = AuthError::EmptyDelegationPath;
292        assert!(err.is_delegation_error());
293    }
294
295    #[test]
296    fn test_error_conversion() {
297        let auth_err = AuthError::KeyNotFound {
298            key_name: "test".to_string(),
299        };
300        let err: Error = auth_err.into();
301        match err {
302            Error::Auth(AuthError::KeyNotFound { key_name: id }) => assert_eq!(id, "test"),
303            _ => panic!("Unexpected error variant"),
304        }
305    }
306}