api_keys_simplified/
error.rs

1use thiserror::Error;
2
3/// Error type for API key operations.
4///
5/// # Security Note
6/// Error messages are intentionally generic to prevent information leakage.
7/// For debugging, use `{:?}` formatting which includes additional context.
8#[derive(Debug, Error)]
9pub enum Error {
10    /// Invalid input format or parameters
11    #[error("Invalid input")]
12    InvalidFormat,
13
14    /// Configuration error with specific details
15    #[error(transparent)]
16    Config(#[from] ConfigError),
17
18    /// Operation failed (intentionally vague for security)
19    ///
20    /// This could be:
21    /// - Key generation failure
22    /// - Hashing failure
23    /// - Verification failure
24    ///
25    /// Use `{:?}` formatting to see details in logs.
26    #[error("Operation failed")]
27    OperationFailed(
28        #[source]
29        #[from]
30        OperationError,
31    ),
32}
33
34/// Configuration errors with specific variants
35#[derive(Debug, Error)]
36pub enum ConfigError {
37    #[error("Prefix must be between 1 and 10 characters")]
38    InvalidPrefixLength,
39
40    #[error("Prefix must contain only alphanumeric characters or underscores")]
41    InvalidPrefixCharacters,
42
43    #[error("Prefix must not contain {0} substring")]
44    InvalidPrefixSubstring(String),
45
46    #[error("String must not be empty")]
47    EmptyString,
48
49    #[error("Entropy must be at least 16 bytes (128 bits)")]
50    EntropyTooLow,
51
52    #[error("Entropy cannot exceed 64 bytes (512 bits)")]
53    EntropyTooHigh,
54
55    #[error("Invalid Argon2 parameters")]
56    InvalidHashParams,
57}
58
59/// Detailed operation errors for debugging (use {:?} to see these)
60#[derive(Debug, Error)]
61pub enum OperationError {
62    #[error("Key generation failed: {0}")]
63    Generation(String),
64
65    #[error("Hashing failed: {0}")]
66    Hashing(String),
67
68    #[error("Verification failed: {0}")]
69    Verification(String),
70}
71
72pub type Result<T> = std::result::Result<T, Error>;
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_display_is_generic() {
80        let err =
81            Error::OperationFailed(OperationError::Hashing("detailed salt error".to_string()));
82        // Display is generic (safe for clients)
83        assert_eq!(err.to_string(), "Operation failed");
84
85        // Debug contains details (for logging)
86        let debug_str = format!("{:?}", err);
87        assert!(debug_str.contains("Hashing"));
88        assert!(debug_str.contains("salt"));
89    }
90
91    #[test]
92    fn test_error_chaining() {
93        let err = Error::OperationFailed(OperationError::Verification(
94            "argon2 param error".to_string(),
95        ));
96        // Can access source for logging
97        if let Error::OperationFailed(source) = err {
98            assert!(source.to_string().contains("argon2"));
99        }
100    }
101
102    #[test]
103    fn test_format_errors_are_generic() {
104        let err = Error::InvalidFormat;
105        assert_eq!(err.to_string(), "Invalid input");
106    }
107
108    #[test]
109    fn test_config_errors_are_specific() {
110        let err = Error::Config(ConfigError::EntropyTooLow);
111        assert_eq!(
112            err.to_string(),
113            "Entropy must be at least 16 bytes (128 bits)"
114        );
115
116        let err = Error::Config(ConfigError::InvalidPrefixLength);
117        assert_eq!(
118            err.to_string(),
119            "Prefix must be between 1 and 10 characters"
120        );
121    }
122
123    #[test]
124    fn test_config_error_conversion() {
125        // Test automatic conversion from ConfigError to Error
126        let config_err = ConfigError::EmptyString;
127        let err: Error = config_err.into();
128        assert_eq!(err.to_string(), "String must not be empty");
129    }
130}