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    /// Operation failed (intentionally vague for security)
15    ///
16    /// This could be:
17    /// - Key generation failure
18    /// - Hashing failure
19    /// - Verification failure
20    ///
21    /// Use `{:?}` formatting to see details in logs.
22    #[error("Operation failed")]
23    OperationFailed(
24        #[source]
25        #[from]
26        OperationError,
27    ),
28}
29
30/// Configuration errors with specific variants
31#[derive(Debug, Error)]
32pub enum ConfigError {
33    #[error("Prefix must be between 1 and 20 characters")]
34    InvalidPrefixLength,
35
36    #[error("Prefix must contain only alphanumeric characters or underscores")]
37    InvalidPrefixCharacters,
38
39    #[error("Prefix must not contain {0} substring")]
40    InvalidPrefixSubstring(String),
41
42    #[error("Prefix cannot look like a version number (e.g., 'v1', 'v2', 'v42')")]
43    InvalidPrefixVersionLike,
44
45    #[error("String must not be empty")]
46    EmptyString,
47
48    #[error("Entropy must be at least 16 bytes (128 bits)")]
49    EntropyTooLow,
50
51    #[error("Entropy cannot exceed 64 bytes (512 bits)")]
52    EntropyTooHigh,
53
54    #[error("Invalid Argon2 parameters")]
55    InvalidHashParams,
56
57    #[error("Invalid Argon2 hash. Please raise an issue at https://github.com/gpmcp/api-keys-simplified/issues/new"
58    )]
59    InvalidArgon2Hash,
60
61    #[error("Minium checksum length should be 32 bits")]
62    ChecksumLenTooSmall,
63
64    #[error("Checksum length should be at MOST 128 bits")]
65    ChecksumLenTooLarge,
66}
67
68/// Detailed operation errors for debugging (use {:?} to see these)
69#[derive(Debug, Error)]
70pub enum OperationError {
71    #[error("Key generation failed: {0}")]
72    Generation(String),
73
74    #[error("Hashing failed: {0}")]
75    Hashing(String),
76
77    #[error("Verification failed: {0}")]
78    Verification(String),
79}
80
81pub type Result<T> = std::result::Result<T, Error>;
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test_display_is_generic() {
89        let err =
90            Error::OperationFailed(OperationError::Hashing("detailed salt error".to_string()));
91        // Display is generic (safe for clients)
92        assert_eq!(err.to_string(), "Operation failed");
93
94        // Debug contains details (for logging)
95        let debug_str = format!("{:?}", err);
96        assert!(debug_str.contains("Hashing"));
97        assert!(debug_str.contains("salt"));
98    }
99
100    #[test]
101    fn test_error_chaining() {
102        let err = Error::OperationFailed(OperationError::Verification(
103            "argon2 param error".to_string(),
104        ));
105        // Can access source for logging
106        if let Error::OperationFailed(source) = err {
107            assert!(source.to_string().contains("argon2"));
108        }
109    }
110
111    #[test]
112    fn test_format_errors_are_generic() {
113        let err = Error::InvalidFormat;
114        assert_eq!(err.to_string(), "Invalid input");
115    }
116}