Skip to main content

auth_framework/
errors.rs

1//! Comprehensive error types for the AuthFramework.
2//!
3//! This module defines all error types used throughout the authentication framework,
4//! providing detailed error information for debugging, logging, and user feedback.
5//! All errors implement standard Rust error traits and provide contextual information
6//! to help diagnose issues.
7//!
8//! # Error Categories
9//!
10//! - **Authentication Errors**: Credential validation and method failures
11//! - **Authorization Errors**: Permission and access control failures
12//! - **Token Errors**: JWT creation, validation, and lifecycle issues
13//! - **Configuration Errors**: Setup and configuration problems
14//! - **Storage Errors**: Database and persistence layer issues
15//! - **Network Errors**: External service communication failures
16//! - **Cryptographic Errors**: Security operation failures
17//!
18//! # Error Handling Patterns
19//!
20//! The framework uses structured error handling with:
21//! - Contextual error messages with relevant details
22//! - Error chaining to preserve root cause information
23//! - Categorized errors for appropriate response handling
24//! - Security-safe error messages that don't leak sensitive data
25//!
26//! # Example Error Handling
27//!
28//! ```rust,no_run
29//! use auth_framework::{AuthFramework, AuthError};
30//! use auth_framework::authentication::credentials::Credential;
31//! use auth_framework::authentication::CredentialMetadata;
32//!
33//! # #[tokio::main]
34//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
35//! # let auth_framework: AuthFramework = unimplemented!();
36//! # let credential: Credential = unimplemented!();
37//! # fn handle_success<T>(_: T) {}
38//! # fn respond_with_auth_failure() {}
39//! # fn respond_with_rate_limit(_: Option<u64>) {}
40//! # fn respond_with_system_error() {}
41//! match auth_framework.authenticate("password", credential).await {
42//!     Ok(result) => handle_success(result),
43//!     Err(AuthError::InvalidCredential { credential_type, message, .. }) => {
44//!         log::warn!("Invalid {} credential: {}", credential_type, message);
45//!         respond_with_auth_failure()
46//!     },
47//!     Err(AuthError::RateLimit { message, .. }) => {
48//!         respond_with_rate_limit(None)
49//!     },
50//!     Err(e) => {
51//!         log::error!("Authentication system error: {}", e);
52//!         respond_with_system_error()
53//!     }
54//! }
55//! # Ok(())
56//! # }
57//! ```
58//!
59//! # Security Considerations
60//!
61//! Error messages are designed to:
62//! - Provide useful debugging information for developers
63//! - Avoid exposing sensitive information to potential attackers
64//! - Enable proper security monitoring and alerting
65//! - Support compliance requirements for audit logging
66
67use thiserror::Error;
68
69/// Type alias for Results in the authentication framework.
70///
71/// This alias simplifies error handling throughout the framework by defaulting
72/// to `AuthError` as the error type while allowing flexibility for other error
73/// types when needed.
74pub type Result<T, E = AuthError> = std::result::Result<T, E>;
75
76/// Comprehensive error type covering all authentication and authorization failures.
77///
78/// `AuthError` provides detailed error information for all aspects of the authentication
79/// framework, from configuration issues to runtime failures. Each error variant includes
80/// contextual information to aid in debugging and provide appropriate user feedback.
81///
82/// This enhanced error type provides:
83/// - **Actionable error messages** with specific suggestions for fixes
84/// - **Documentation links** to relevant guides and troubleshooting
85/// - **Contextual help** that guides users to solutions
86/// - **Security-aware messaging** that doesn't leak sensitive information
87///
88/// # Error Categories
89///
90/// ## Configuration Errors
91/// Errors that occur during framework setup and configuration validation.
92///
93/// ## Authentication Errors
94/// Errors related to credential validation and authentication method execution.
95///
96/// ## Authorization Errors
97/// Errors related to permission checking and access control.
98///
99/// ## Token Errors
100/// JWT token creation, validation, expiration, and lifecycle issues.
101///
102/// ## Storage Errors
103/// Database connectivity, query failures, and data persistence issues.
104///
105/// ## Network Errors
106/// External service communication, timeouts, and connectivity problems.
107///
108/// ## Cryptographic Errors
109/// Encryption, decryption, signing, and other security operation failures.
110///
111/// # Enhanced Error Handling
112///
113/// ```rust,no_run
114/// use auth_framework::AuthError;
115///
116/// # let auth_result: auth_framework::errors::Result<()> = Ok(());
117/// // Enhanced error handling with contextual help
118/// match auth_result {
119///     Err(AuthError::Configuration { message, help, docs_url, .. }) => {
120///         eprintln!("Configuration Error: {}", message);
121///         if let Some(help) = help {
122///             eprintln!("Help: {}", help);
123///         }
124///         if let Some(docs) = docs_url {
125///             eprintln!("See: {}", docs);
126///         }
127///     },
128///     Err(AuthError::InvalidCredential { credential_type, message, .. }) => {
129///         eprintln!("Invalid {}: {}", credential_type, message);
130///     },
131///     // ... handle other error types
132///     _ => {}
133/// }
134/// ```
135///
136/// # Security Notes
137///
138/// Error messages are carefully crafted to:
139/// - Provide sufficient detail for debugging and monitoring
140/// - Avoid exposing sensitive information that could aid attackers
141/// - Enable security teams to identify potential threats
142/// - Support compliance and audit requirements
143/// - Guide users to secure solutions and best practices
144#[derive(Error, Debug)]
145pub enum AuthError {
146    /// Configuration validation and setup errors.
147    ///
148    /// These errors occur when the authentication framework is misconfigured
149    /// or when configuration validation fails during startup.
150    #[error("Configuration error: {message}")]
151    Configuration {
152        message: String,
153        #[source]
154        source: Option<Box<dyn std::error::Error + Send + Sync>>,
155        /// Helpful guidance for fixing the issue
156        help: Option<String>,
157        /// Link to relevant documentation
158        docs_url: Option<String>,
159        /// Specific fix suggestion with commands or code
160        suggested_fix: Option<String>,
161    },
162
163    /// Authentication method execution errors.
164    ///
165    /// These errors occur when a specific authentication method fails to
166    /// execute properly, such as OAuth provider communication failures.
167    #[error("Authentication method '{method}' error: {message}")]
168    AuthMethod {
169        method: String,
170        message: String,
171        /// Helpful guidance for fixing the issue
172        help: Option<String>,
173        /// Link to relevant documentation
174        docs_url: Option<String>,
175        /// Specific fix suggestion
176        suggested_fix: Option<String>,
177    },
178
179    /// Token-related errors
180    #[error("Token error: {0}")]
181    Token(#[from] TokenError),
182
183    /// Permission-related errors
184    #[error("Permission error: {0}")]
185    Permission(#[from] PermissionError),
186
187    /// Storage-related errors
188    #[error("Storage error: {0}")]
189    Storage(#[from] StorageError),
190
191    /// Network/HTTP errors
192    #[error("Network error: {0}")]
193    Network(#[from] reqwest::Error),
194
195    /// JSON parsing errors
196    #[error("JSON error: {0}")]
197    Json(#[from] serde_json::Error),
198
199    /// JWT errors
200    #[error("JWT error: {0}")]
201    Jwt(#[from] jsonwebtoken::errors::Error),
202
203    /// YAML parsing errors
204    #[error("YAML error: {0}")]
205    Yaml(#[from] serde_yaml::Error),
206
207    /// TOML parsing errors
208    #[error("TOML error: {0}")]
209    Toml(#[from] toml::ser::Error),
210
211    /// Prometheus metrics errors
212    #[cfg(feature = "prometheus")]
213    #[error("Metrics error: {0}")]
214    Metrics(#[from] prometheus::Error),
215
216    /// IO errors
217    #[error("IO error: {0}")]
218    Io(#[from] std::io::Error),
219
220    /// CLI interaction errors
221    #[error("CLI error: {0}")]
222    Cli(String),
223
224    /// System time errors
225    #[error("System time error: {0}")]
226    SystemTime(#[from] std::time::SystemTimeError),
227
228    /// Rate limiting errors
229    #[error("Rate limit exceeded: {message}")]
230    RateLimit { message: String },
231
232    /// Session-related errors
233    #[error(
234        "Too many concurrent sessions for user (limit reached). \
235             Revoke an existing session before creating a new one."
236    )]
237    TooManyConcurrentSessions,
238
239    /// MFA-related errors
240    #[error("MFA error: {0}")]
241    Mfa(#[from] MfaError),
242
243    /// Device flow errors
244    #[error("Device flow error: {0}")]
245    DeviceFlow(#[from] DeviceFlowError),
246
247    /// OAuth provider errors
248    #[error("OAuth provider error: {0}")]
249    OAuthProvider(#[from] OAuthProviderError),
250
251    /// The stored password hash does not match the supplied credential.
252    ///
253    /// This typically means the user entered the wrong password. Do **not**
254    /// reveal which field (username vs. password) was incorrect to the caller.
255    #[error("Password verification failed: {0}")]
256    PasswordVerification(String),
257
258    /// The password hashing algorithm (argon2/bcrypt) encountered an error.
259    #[error("Password hashing failed: {0}")]
260    PasswordHashing(String),
261
262    /// No user record exists for the given identifier.
263    #[error("User not found. The requested user ID does not exist in the store.")]
264    UserNotFound,
265
266    /// A request parameter failed validation (e.g. empty username, invalid email format).
267    #[error("Invalid input: {0}")]
268    InvalidInput(String),
269
270    /// A hardware security key (FIDO2 / WebAuthn) operation failed.
271    #[error("Hardware token error: {0}")]
272    HardwareToken(String),
273
274    /// A one-time backup code was rejected (already used, incorrect, or expired).
275    #[error("Backup code verification failed: {0}")]
276    BackupCodeVerification(String),
277
278    /// Failed to hash a backup code for secure storage.
279    #[error("Backup code hashing failed: {0}")]
280    BackupCodeHashing(String),
281
282    /// The TOTP or MFA secret is in an invalid format (e.g. not valid Base32).
283    #[error("Invalid secret format")]
284    InvalidSecret,
285
286    /// An error occurred while reading or updating a user profile.
287    #[error("User profile error: {message}")]
288    UserProfile { message: String },
289
290    /// Credential validation errors
291    #[error("Invalid credential: {credential_type} - {message}")]
292    InvalidCredential {
293        credential_type: String,
294        message: String,
295    },
296
297    /// An authentication operation did not complete within the allowed window.
298    #[error("Authentication timeout after {timeout_seconds} seconds")]
299    Timeout { timeout_seconds: u64 },
300
301    /// Provider configuration missing
302    #[error(
303        "Provider '{provider}' is not configured or supported. \
304             Add it via AuthConfig::method_config() or check available providers."
305    )]
306    ProviderNotConfigured { provider: String },
307
308    /// Cryptography errors
309    #[error("Cryptography error: {message}")]
310    Crypto { message: String },
311
312    /// Validation errors
313    #[error("Validation error: {message}")]
314    Validation { message: String },
315
316    /// Generic internal errors
317    #[error("Internal error: {message}")]
318    Internal { message: String },
319
320    /// Invalid request error
321    #[error("Invalid request: {0}")]
322    InvalidRequest(String),
323
324    /// Step-up authentication required
325    #[error(
326        "Step-up authentication required: current level '{current_level}', required level '{required_level}'"
327    )]
328    StepUpRequired {
329        current_level: String,
330        required_level: String,
331        step_up_url: String,
332    },
333
334    /// A session-layer error (e.g. session not found, session expired, store failure).
335    #[error("Session error: {0}")]
336    SessionError(String),
337
338    /// The caller lacks valid credentials or the credentials have been revoked.
339    #[error("Unauthorized: {0}")]
340    Unauthorized(String),
341
342    /// Token creation failed at the signing or encoding stage.
343    ///
344    /// Consider using [`AuthError::token`] for richer [`TokenError`] variants.
345    #[error("Token generation error: {0}")]
346    TokenGeneration(String),
347
348    /// Invalid token error.
349    ///
350    /// **Prefer** [`AuthError::token`] which routes through the structured
351    /// [`TokenError`] hierarchy for consistent token error handling.
352    #[deprecated(
353        since = "0.5.0",
354        note = "Use `AuthError::token(msg)` instead — it routes through the structured TokenError hierarchy"
355    )]
356    #[error("Invalid token: {0}")]
357    InvalidToken(String),
358
359    /// The named authentication provider is not compiled in or not recognized.
360    #[error("Unsupported provider: {0}")]
361    UnsupportedProvider(String),
362
363    /// Network error with custom message.
364    ///
365    /// **Prefer** [`AuthError::Internal`] with a descriptive message, or let
366    /// [`reqwest::Error`] convert automatically via the [`From`] impl on
367    /// [`AuthError::Network`].
368    #[deprecated(
369        since = "0.5.0",
370        note = "Use `AuthError::internal(msg)` or let `reqwest::Error` convert via `AuthError::Network` instead"
371    )]
372    #[error("Network error: {0}")]
373    NetworkError(String),
374
375    /// Parse error with custom message.
376    ///
377    /// **Prefer** [`AuthError::Internal`] with a descriptive message, or let
378    /// [`serde_json::Error`] convert automatically via the [`From`] impl on
379    /// [`AuthError::Json`].
380    #[deprecated(
381        since = "0.5.0",
382        note = "Use `AuthError::internal(msg)` or let serde errors convert via `AuthError::Json` instead"
383    )]
384    #[error("Parse error: {0}")]
385    ParseError(String),
386
387    /// Configuration error with custom message.
388    ///
389    /// **Prefer** [`AuthError::config`] or [`AuthError::config_with_help`] which
390    /// carry richer context (help text, docs URL, suggested fix).
391    #[deprecated(
392        since = "0.5.0",
393        note = "Use `AuthError::config(msg)` instead — it provides richer context fields"
394    )]
395    #[error("Configuration error: {0}")]
396    ConfigurationError(String),
397}
398
399/// Token-specific errors.
400///
401/// Covers the lifecycle of authentication tokens: creation, validation,
402/// refresh, and revocation.
403#[derive(Error, Debug)]
404pub enum TokenError {
405    /// The token's `exp` claim is in the past.
406    #[error("Token has expired")]
407    Expired,
408
409    /// The token failed signature verification or structural validation.
410    #[error("Token is invalid: {message}")]
411    Invalid { message: String },
412
413    /// No token matching the given identifier exists in the store.
414    #[error("Token not found")]
415    NotFound,
416
417    /// The request did not include a required token (e.g. missing `Authorization` header).
418    #[error("Token is missing")]
419    Missing,
420
421    /// A new token could not be issued (e.g. signing key unavailable).
422    #[error("Token creation failed: {message}")]
423    CreationFailed { message: String },
424
425    /// An existing token could not be refreshed (e.g. refresh token revoked).
426    #[error("Token refresh failed: {message}")]
427    RefreshFailed { message: String },
428
429    /// Token revocation did not complete (e.g. storage write failure).
430    #[error("Token revocation failed: {message}")]
431    RevocationFailed { message: String },
432}
433
434/// Permission and access-control errors.
435///
436/// Returned when an authenticated principal lacks the required permissions
437/// or roles to perform an operation.
438#[derive(Error, Debug)]
439pub enum PermissionError {
440    /// The principal does not hold the required permission for the target resource.
441    #[error("Access denied: missing permission '{permission}' for resource '{resource}'")]
442    AccessDenied {
443        permission: String,
444        resource: String,
445    },
446
447    /// The specified role does not exist in the role store.
448    #[error("Role '{role}' not found")]
449    RoleNotFound { role: String },
450
451    /// The specified permission identifier does not exist.
452    #[error("Permission '{permission}' not found")]
453    PermissionNotFound { permission: String },
454
455    /// A permission string could not be parsed (e.g. missing `resource:action` separator).
456    #[error("Invalid permission format: {message}")]
457    InvalidFormat { message: String },
458
459    /// General permission denial with a descriptive message.
460    #[error("Permission denied: {message}")]
461    Denied {
462        action: String,
463        resource: String,
464        message: String,
465    },
466}
467
468/// Storage backend errors.
469///
470/// Covers connectivity, query execution, and serialization issues for
471/// all storage backends (Memory, PostgreSQL, MySQL, Redis, SQLite).
472#[derive(Error, Debug)]
473pub enum StorageError {
474    /// Could not establish a connection to the storage backend.
475    #[error("Connection failed: {message}")]
476    ConnectionFailed { message: String },
477
478    /// A read or write operation against the store failed.
479    #[error("Operation failed: {message}")]
480    OperationFailed { message: String },
481
482    /// Data could not be serialized to or deserialized from the storage format.
483    #[error("Serialization error: {message}")]
484    Serialization { message: String },
485
486    /// The configured storage backend is unreachable or not initialized.
487    #[error("Storage backend not available")]
488    BackendUnavailable,
489}
490
491/// Multi-factor authentication errors.
492#[derive(Error, Debug)]
493pub enum MfaError {
494    /// The MFA challenge window has elapsed; the user must request a new challenge.
495    #[error("MFA challenge expired")]
496    ChallengeExpired,
497
498    /// The TOTP or one-time code provided by the user is incorrect.
499    #[error("Invalid MFA code")]
500    InvalidCode,
501
502    /// The requested MFA method (e.g. SMS, hardware key) is not enabled.
503    #[error("MFA method not supported: {method}")]
504    MethodNotSupported { method: String },
505
506    /// MFA has not been configured for this account yet.
507    #[error("MFA setup required")]
508    SetupRequired,
509
510    /// MFA verification failed for a reason described in `message`.
511    #[error("MFA verification failed: {message}")]
512    VerificationFailed { message: String },
513}
514
515/// Device Authorization Grant (RFC 8628) errors.
516///
517/// These map 1-to-1 to the error codes defined in RFC 8628 §3.5.
518#[derive(Error, Debug)]
519pub enum DeviceFlowError {
520    /// The user has not yet completed authorization on the verification URI.
521    #[error("Authorization pending - user has not yet completed authorization")]
522    AuthorizationPending,
523
524    /// The client is polling faster than the allowed `interval`.
525    #[error("Slow down - polling too frequently")]
526    SlowDown,
527
528    /// The device code has exceeded its `expires_in` window.
529    #[error("Device code expired")]
530    ExpiredToken,
531
532    /// The end user denied the authorization request.
533    #[error("Access denied by user")]
534    AccessDenied,
535
536    /// The device code presented by the client is not recognized.
537    #[error("Invalid device code")]
538    InvalidDeviceCode,
539
540    /// The grant type is not supported by this server.
541    #[error("Unsupported grant type")]
542    UnsupportedGrantType,
543}
544
545/// OAuth 2.0 provider interaction errors.
546///
547/// Covers issues encountered when communicating with upstream OAuth providers
548/// or when validating OAuth protocol messages.
549#[derive(Error, Debug)]
550pub enum OAuthProviderError {
551    /// The authorization code is expired, already consumed, or invalid.
552    #[error("Invalid authorization code")]
553    InvalidAuthorizationCode,
554
555    /// The `redirect_uri` does not match the registered value for the client.
556    #[error("Invalid redirect URI")]
557    InvalidRedirectUri,
558
559    /// The client ID or client secret is incorrect.
560    #[error("Invalid client credentials")]
561    InvalidClientCredentials,
562
563    /// The granted scopes do not satisfy the required scopes for the operation.
564    #[error("Insufficient scope: required '{required}', granted '{granted}'")]
565    InsufficientScope { required: String, granted: String },
566
567    /// The provider does not implement the requested feature.
568    #[error("Provider '{provider}' does not support '{feature}'")]
569    UnsupportedFeature { provider: String, feature: String },
570
571    /// The upstream provider returned a rate-limit response.
572    #[error("Rate limited by provider: {message}")]
573    RateLimited { message: String },
574}
575
576impl AuthError {
577    /// Create a new configuration error.
578    ///
579    /// # Example
580    ///
581    /// ```rust
582    /// use auth_framework::AuthError;
583    ///
584    /// let err = AuthError::config("Missing database URL");
585    /// assert!(err.to_string().contains("Missing database URL"));
586    /// ```
587    pub fn config(message: impl Into<String>) -> Self {
588        Self::Configuration {
589            message: message.into(),
590            source: None,
591            help: None,
592            docs_url: None,
593            suggested_fix: None,
594        }
595    }
596
597    /// Create a configuration error with helpful context.
598    ///
599    /// Includes a human-readable `help` string and an optional `suggested_fix`
600    /// that can be displayed by CLI tools or IDE integrations.
601    ///
602    /// # Example
603    ///
604    /// ```rust
605    /// use auth_framework::AuthError;
606    ///
607    /// let err = AuthError::config_with_help(
608    ///     "JWT secret not set",
609    ///     "Set the JWT_SECRET environment variable",
610    ///     Some("export JWT_SECRET=$(openssl rand -hex 32)".into()),
611    /// );
612    /// assert!(err.to_string().contains("JWT secret not set"));
613    /// ```
614    pub fn config_with_help(
615        message: impl Into<String>,
616        help: impl Into<String>,
617        suggested_fix: Option<String>,
618    ) -> Self {
619        Self::Configuration {
620            message: message.into(),
621            source: None,
622            help: Some(help.into()),
623            docs_url: Some(
624                "https://docs.rs/auth-framework/latest/auth_framework/config/".to_string(),
625            ),
626            suggested_fix,
627        }
628    }
629
630    /// Create a JWT secret validation error with helpful guidance.
631    ///
632    /// # Example
633    ///
634    /// ```rust
635    /// use auth_framework::AuthError;
636    ///
637    /// let err = AuthError::jwt_secret_too_short(16);
638    /// assert!(err.to_string().contains("16 characters"));
639    /// ```
640    pub fn jwt_secret_too_short(current_length: usize) -> Self {
641        Self::Configuration {
642            message: format!(
643                "JWT secret too short (got {} characters, need 32+ for security)",
644                current_length
645            ),
646            source: None,
647            help: Some("Use a cryptographically secure random string of at least 32 characters".to_string()),
648            docs_url: Some("https://docs.rs/auth-framework/latest/auth_framework/config/struct.SecurityConfig.html".to_string()),
649            suggested_fix: Some("Generate a secure secret: `openssl rand -hex 32`".to_string()),
650        }
651    }
652
653    /// Create a production environment error with guidance.
654    ///
655    /// # Example
656    ///
657    /// ```rust
658    /// use auth_framework::AuthError;
659    ///
660    /// let err = AuthError::production_memory_storage();
661    /// assert!(err.to_string().contains("Memory storage"));
662    /// ```
663    pub fn production_memory_storage() -> Self {
664        Self::Configuration {
665            message: "Memory storage is not suitable for production environments".to_string(),
666            source: None,
667            help: Some("Use a persistent storage backend like PostgreSQL or Redis".to_string()),
668            docs_url: Some("https://docs.rs/auth-framework/latest/auth_framework/storage/".to_string()),
669            suggested_fix: Some("Configure PostgreSQL: .with_postgres(\"postgresql://...\") or Redis: .with_redis(\"redis://...\")".to_string()),
670        }
671    }
672
673    /// Create a new auth method error.
674    ///
675    /// # Example
676    ///
677    /// ```rust
678    /// use auth_framework::AuthError;
679    ///
680    /// let err = AuthError::auth_method("oauth2", "token endpoint unreachable");
681    /// assert!(err.to_string().contains("oauth2"));
682    /// ```
683    pub fn auth_method(method: impl Into<String>, message: impl Into<String>) -> Self {
684        Self::AuthMethod {
685            method: method.into(),
686            message: message.into(),
687            help: None,
688            docs_url: None,
689            suggested_fix: None,
690        }
691    }
692
693    /// Create an auth method error with helpful context.
694    ///
695    /// # Example
696    ///
697    /// ```rust
698    /// use auth_framework::AuthError;
699    ///
700    /// let err = AuthError::auth_method_with_help(
701    ///     "saml",
702    ///     "certificate expired",
703    ///     "Renew the SAML signing certificate",
704    ///     Some("openssl x509 -req -in cert.csr -signkey key.pem -out cert.pem".into()),
705    /// );
706    /// assert!(err.to_string().contains("saml"));
707    /// ```
708    pub fn auth_method_with_help(
709        method: impl Into<String>,
710        message: impl Into<String>,
711        help: impl Into<String>,
712        suggested_fix: Option<String>,
713    ) -> Self {
714        Self::AuthMethod {
715            method: method.into(),
716            message: message.into(),
717            help: Some(help.into()),
718            docs_url: Some(
719                "https://docs.rs/auth-framework/latest/auth_framework/methods/".to_string(),
720            ),
721            suggested_fix,
722        }
723    }
724
725    /// Create a new rate limit error.
726    ///
727    /// # Example
728    ///
729    /// ```rust
730    /// use auth_framework::AuthError;
731    ///
732    /// let err = AuthError::rate_limit("too many login attempts");
733    /// assert!(err.to_string().contains("too many login attempts"));
734    /// ```
735    pub fn rate_limit(message: impl Into<String>) -> Self {
736        Self::RateLimit {
737            message: message.into(),
738        }
739    }
740
741    /// Create a new crypto error.
742    ///
743    /// # Example
744    ///
745    /// ```rust
746    /// use auth_framework::AuthError;
747    ///
748    /// let err = AuthError::crypto("HMAC key too short");
749    /// assert!(err.to_string().contains("HMAC key too short"));
750    /// ```
751    pub fn crypto(message: impl Into<String>) -> Self {
752        Self::Crypto {
753            message: message.into(),
754        }
755    }
756
757    /// Create a new validation error.
758    ///
759    /// # Example
760    ///
761    /// ```rust
762    /// use auth_framework::AuthError;
763    ///
764    /// let err = AuthError::validation("email format invalid");
765    /// assert!(err.to_string().contains("email format invalid"));
766    /// ```
767    pub fn validation(message: impl Into<String>) -> Self {
768        Self::Validation {
769            message: message.into(),
770        }
771    }
772
773    /// Create a new internal error.
774    ///
775    /// # Example
776    ///
777    /// ```rust
778    /// use auth_framework::AuthError;
779    ///
780    /// let err = AuthError::internal("unexpected state");
781    /// assert!(err.to_string().contains("unexpected state"));
782    /// ```
783    pub fn internal(message: impl Into<String>) -> Self {
784        Self::Internal {
785            message: message.into(),
786        }
787    }
788
789    /// Create an authorization error.
790    ///
791    /// # Example
792    ///
793    /// ```rust
794    /// use auth_framework::AuthError;
795    ///
796    /// let err = AuthError::authorization("insufficient privileges");
797    /// assert!(err.to_string().contains("insufficient privileges"));
798    /// ```
799    pub fn authorization(message: impl Into<String>) -> Self {
800        Self::Permission(PermissionError::Denied {
801            action: "authorize".to_string(),
802            resource: "resource".to_string(),
803            message: message.into(),
804        })
805    }
806
807    /// Create an access denied error.
808    ///
809    /// # Example
810    ///
811    /// ```rust
812    /// use auth_framework::AuthError;
813    ///
814    /// let err = AuthError::access_denied("admin role required");
815    /// assert!(err.to_string().contains("admin role required"));
816    /// ```
817    pub fn access_denied(message: impl Into<String>) -> Self {
818        Self::Permission(PermissionError::Denied {
819            action: "access".to_string(),
820            resource: "resource".to_string(),
821            message: message.into(),
822        })
823    }
824
825    /// Create a token error.
826    ///
827    /// # Example
828    ///
829    /// ```rust
830    /// use auth_framework::AuthError;
831    ///
832    /// let err = AuthError::token("signature mismatch");
833    /// assert!(err.to_string().contains("signature mismatch"));
834    /// ```
835    pub fn token(message: impl Into<String>) -> Self {
836        Self::Token(TokenError::Invalid {
837            message: message.into(),
838        })
839    }
840
841    /// Create a device flow error.
842    ///
843    /// # Example
844    ///
845    /// ```rust
846    /// use auth_framework::errors::{AuthError, DeviceFlowError};
847    ///
848    /// let err = AuthError::device_flow(DeviceFlowError::ExpiredToken);
849    /// assert!(err.to_string().contains("expired"));
850    /// ```
851    pub fn device_flow(error: DeviceFlowError) -> Self {
852        Self::DeviceFlow(error)
853    }
854
855    /// Create an OAuth provider error.
856    ///
857    /// # Example
858    ///
859    /// ```rust
860    /// use auth_framework::errors::{AuthError, OAuthProviderError};
861    ///
862    /// let err = AuthError::oauth_provider(OAuthProviderError::InvalidRedirectUri);
863    /// assert!(err.to_string().contains("redirect"));
864    /// ```
865    pub fn oauth_provider(error: OAuthProviderError) -> Self {
866        Self::OAuthProvider(error)
867    }
868
869    /// Create a user profile error.
870    ///
871    /// # Example
872    ///
873    /// ```rust
874    /// use auth_framework::AuthError;
875    ///
876    /// let err = AuthError::user_profile("email already in use");
877    /// assert!(err.to_string().contains("email already in use"));
878    /// ```
879    pub fn user_profile(message: impl Into<String>) -> Self {
880        Self::UserProfile {
881            message: message.into(),
882        }
883    }
884
885    /// Create an invalid credential error.
886    ///
887    /// # Example
888    ///
889    /// ```rust
890    /// use auth_framework::AuthError;
891    ///
892    /// let err = AuthError::invalid_credential("password", "too short");
893    /// assert!(err.to_string().contains("password"));
894    /// ```
895    pub fn invalid_credential(
896        credential_type: impl Into<String>,
897        message: impl Into<String>,
898    ) -> Self {
899        Self::InvalidCredential {
900            credential_type: credential_type.into(),
901            message: message.into(),
902        }
903    }
904
905    /// Create a timeout error.
906    ///
907    /// # Example
908    ///
909    /// ```rust
910    /// use auth_framework::AuthError;
911    ///
912    /// let err = AuthError::timeout(30);
913    /// assert!(err.to_string().contains("30"));
914    /// ```
915    pub fn timeout(timeout_seconds: u64) -> Self {
916        Self::Timeout { timeout_seconds }
917    }
918
919    /// Create a provider not configured error.
920    ///
921    /// # Example
922    ///
923    /// ```rust
924    /// use auth_framework::AuthError;
925    ///
926    /// let err = AuthError::provider_not_configured("github");
927    /// assert!(err.to_string().contains("github"));
928    /// ```
929    pub fn provider_not_configured(provider: impl Into<String>) -> Self {
930        Self::ProviderNotConfigured {
931            provider: provider.into(),
932        }
933    }
934
935    /// Create a rate limited error (alias for [`rate_limit`](Self::rate_limit)).
936    ///
937    /// # Example
938    ///
939    /// ```rust
940    /// use auth_framework::AuthError;
941    ///
942    /// let err = AuthError::rate_limited("5 requests per second exceeded");
943    /// assert!(err.to_string().contains("5 requests"));
944    /// ```
945    pub fn rate_limited(message: impl Into<String>) -> Self {
946        Self::RateLimit {
947            message: message.into(),
948        }
949    }
950
951    /// Create a configuration error (alias for [`config`](Self::config)).
952    ///
953    /// # Example
954    ///
955    /// ```rust
956    /// use auth_framework::AuthError;
957    ///
958    /// let err = AuthError::configuration("invalid issuer URL");
959    /// assert!(err.to_string().contains("invalid issuer URL"));
960    /// ```
961    pub fn configuration(message: impl Into<String>) -> Self {
962        Self::Configuration {
963            message: message.into(),
964            source: None,
965            help: None,
966            docs_url: None,
967            suggested_fix: None,
968        }
969    }
970
971    // -----------------------------------------------------------------------
972    // Classification helpers — framework-agnostic error introspection
973    // -----------------------------------------------------------------------
974
975    /// Return the HTTP status code that best represents this error.
976    ///
977    /// This is framework-agnostic and works without enabling any web framework
978    /// feature flag. Web framework integrations (actix-web, axum, …) use this
979    /// internally but you can also call it directly when building custom
980    /// response types.
981    ///
982    /// # Example
983    ///
984    /// ```rust
985    /// use auth_framework::AuthError;
986    ///
987    /// assert_eq!(AuthError::rate_limit("slow down").http_status_code(), 429);
988    /// assert_eq!(AuthError::internal("oops").http_status_code(), 500);
989    /// assert_eq!(AuthError::token("bad jwt").http_status_code(), 401);
990    /// ```
991    pub fn http_status_code(&self) -> u16 {
992        match self {
993            // 400 Bad Request
994            Self::InvalidInput(_)
995            | Self::Validation { .. }
996            | Self::InvalidCredential { .. }
997            | Self::HardwareToken(_)
998            | Self::InvalidRequest(_)
999            | Self::InvalidSecret => 400,
1000
1001            // 401 Unauthorized
1002            Self::Token(_)
1003            | Self::AuthMethod { .. }
1004            | Self::Jwt(_)
1005            | Self::Unauthorized(_)
1006            | Self::PasswordVerification(_) => 401,
1007
1008            // 403 Forbidden
1009            Self::Permission(_)
1010            | Self::StepUpRequired { .. } => 403,
1011
1012            // 404 Not Found
1013            Self::UserNotFound
1014            | Self::ProviderNotConfigured { .. } => 404,
1015
1016            // 408 Request Timeout
1017            Self::Timeout { .. } => 408,
1018
1019            // 429 Too Many Requests
1020            Self::RateLimit { .. }
1021            | Self::TooManyConcurrentSessions => 429,
1022
1023            // 502 Bad Gateway (upstream provider errors)
1024            Self::OAuthProvider(_)
1025            | Self::Network(_) => 502,
1026
1027            // 503 Service Unavailable (storage / infra)
1028            Self::Storage(_) => 503,
1029
1030            // 500 Internal Server Error (everything else)
1031            Self::Configuration { .. }
1032            | Self::Crypto { .. }
1033            | Self::Internal { .. }
1034            | Self::Json(_)
1035            | Self::Yaml(_)
1036            | Self::Toml(_)
1037            | Self::Io(_)
1038            | Self::Mfa(_)
1039            | Self::DeviceFlow(_)
1040            | Self::TokenGeneration(_)
1041            | Self::SessionError(_)
1042            | Self::UserProfile { .. }
1043            | Self::Cli(_)
1044            | Self::SystemTime(_)
1045            | Self::PasswordHashing(_)
1046            | Self::BackupCodeVerification(_)
1047            | Self::BackupCodeHashing(_)
1048            | Self::UnsupportedProvider(_) => 500,
1049
1050            #[cfg(feature = "prometheus")]
1051            Self::Metrics(_) => 500,
1052
1053            // Deprecated variants
1054            #[allow(deprecated)]
1055            Self::InvalidToken(_)
1056            | Self::NetworkError(_)
1057            | Self::ParseError(_)
1058            | Self::ConfigurationError(_) => 500,
1059        }
1060    }
1061
1062    /// Whether this error is transient and the operation may succeed on retry.
1063    ///
1064    /// Returns `true` for rate-limit, timeout, network, and storage
1065    /// connectivity errors. Returns `false` for authentication failures,
1066    /// validation errors, and other permanent conditions.
1067    ///
1068    /// # Example
1069    ///
1070    /// ```rust
1071    /// use auth_framework::AuthError;
1072    ///
1073    /// assert!(AuthError::rate_limit("too fast").is_retryable());
1074    /// assert!(AuthError::timeout(30).is_retryable());
1075    /// assert!(!AuthError::token("invalid").is_retryable());
1076    /// assert!(!AuthError::validation("bad input").is_retryable());
1077    /// ```
1078    pub fn is_retryable(&self) -> bool {
1079        matches!(
1080            self,
1081            Self::RateLimit { .. }
1082                | Self::Timeout { .. }
1083                | Self::TooManyConcurrentSessions
1084                | Self::Network(_)
1085                | Self::Storage(StorageError::ConnectionFailed { .. })
1086        )
1087    }
1088
1089    /// A short, stable, machine-readable code for this error category.
1090    ///
1091    /// Useful for API responses, metrics labels, and log filtering.
1092    /// These codes are stable across patch releases.
1093    ///
1094    /// # Example
1095    ///
1096    /// ```rust
1097    /// use auth_framework::AuthError;
1098    ///
1099    /// assert_eq!(AuthError::rate_limit("nope").error_code(), "rate_limit");
1100    /// assert_eq!(AuthError::token("bad").error_code(), "invalid_token");
1101    /// assert_eq!(AuthError::config("bad").error_code(), "configuration");
1102    /// ```
1103    pub fn error_code(&self) -> &'static str {
1104        match self {
1105            Self::Configuration { .. } => "configuration",
1106            Self::AuthMethod { .. } => "auth_method",
1107            Self::Token(_) => "invalid_token",
1108            Self::Permission(_) => "insufficient_permissions",
1109            Self::Storage(_) => "storage",
1110            Self::Network(_) => "network",
1111            Self::Json(_) | Self::Yaml(_) | Self::Toml(_) => "serialization",
1112            Self::Jwt(_) => "jwt",
1113            Self::Io(_) => "io",
1114            Self::RateLimit { .. } => "rate_limit",
1115            Self::TooManyConcurrentSessions => "concurrent_sessions",
1116            Self::Mfa(_) => "mfa",
1117            Self::DeviceFlow(_) => "device_flow",
1118            Self::OAuthProvider(_) => "oauth_provider",
1119            Self::PasswordVerification(_) => "password_verification",
1120            Self::UserNotFound => "user_not_found",
1121            Self::InvalidInput(_) => "invalid_input",
1122            Self::HardwareToken(_) => "hardware_token",
1123            Self::InvalidCredential { .. } => "invalid_credential",
1124            Self::Timeout { .. } => "timeout",
1125            Self::Crypto { .. } => "crypto",
1126            Self::Validation { .. } => "validation",
1127            Self::Internal { .. } => "internal",
1128            Self::StepUpRequired { .. } => "step_up_required",
1129            Self::SessionError(_) => "session",
1130            Self::Unauthorized(_) => "unauthorized",
1131            Self::TokenGeneration(_) => "token_generation",
1132            Self::UserProfile { .. } => "user_profile",
1133            Self::ProviderNotConfigured { .. } => "provider_not_configured",
1134            Self::Cli(_) => "cli",
1135            Self::SystemTime(_) => "internal",
1136            Self::PasswordHashing(_) => "password_hashing",
1137            Self::BackupCodeVerification(_) => "backup_code",
1138            Self::BackupCodeHashing(_) => "backup_code",
1139            Self::InvalidSecret => "invalid_secret",
1140            Self::InvalidRequest(_) => "invalid_request",
1141            Self::UnsupportedProvider(_) => "unsupported_provider",
1142            #[cfg(feature = "prometheus")]
1143            Self::Metrics(_) => "metrics",
1144            #[allow(deprecated)]
1145            Self::InvalidToken(_) => "invalid_token",
1146            #[allow(deprecated)]
1147            Self::NetworkError(_) => "network",
1148            #[allow(deprecated)]
1149            Self::ParseError(_) => "parse",
1150            #[allow(deprecated)]
1151            Self::ConfigurationError(_) => "configuration",
1152        }
1153    }
1154
1155    /// Whether this is a client-side error (4xx status code).
1156    ///
1157    /// # Example
1158    ///
1159    /// ```rust
1160    /// use auth_framework::AuthError;
1161    ///
1162    /// assert!(AuthError::validation("bad").is_client_error());
1163    /// assert!(!AuthError::internal("oops").is_client_error());
1164    /// ```
1165    pub fn is_client_error(&self) -> bool {
1166        (400..500).contains(&self.http_status_code())
1167    }
1168
1169    /// Whether this is a server-side error (5xx status code).
1170    ///
1171    /// # Example
1172    ///
1173    /// ```rust
1174    /// use auth_framework::AuthError;
1175    ///
1176    /// assert!(AuthError::internal("oops").is_server_error());
1177    /// assert!(!AuthError::validation("bad").is_server_error());
1178    /// ```
1179    pub fn is_server_error(&self) -> bool {
1180        self.http_status_code() >= 500
1181    }
1182}
1183
1184impl TokenError {
1185    /// Create a new token creation failed error.
1186    ///
1187    /// # Example
1188    ///
1189    /// ```rust
1190    /// use auth_framework::errors::TokenError;
1191    ///
1192    /// let err = TokenError::creation_failed("signing key unavailable");
1193    /// assert!(err.to_string().contains("signing key unavailable"));
1194    /// ```
1195    pub fn creation_failed(message: impl Into<String>) -> Self {
1196        Self::CreationFailed {
1197            message: message.into(),
1198        }
1199    }
1200
1201    /// Create a new token refresh failed error.
1202    ///
1203    /// # Example
1204    ///
1205    /// ```rust
1206    /// use auth_framework::errors::TokenError;
1207    ///
1208    /// let err = TokenError::refresh_failed("refresh token revoked");
1209    /// assert!(err.to_string().contains("refresh token revoked"));
1210    /// ```
1211    pub fn refresh_failed(message: impl Into<String>) -> Self {
1212        Self::RefreshFailed {
1213            message: message.into(),
1214        }
1215    }
1216
1217    /// Create a new token revocation failed error.
1218    ///
1219    /// # Example
1220    ///
1221    /// ```rust
1222    /// use auth_framework::errors::TokenError;
1223    ///
1224    /// let err = TokenError::revocation_failed("storage write failed");
1225    /// assert!(err.to_string().contains("storage write failed"));
1226    /// ```
1227    pub fn revocation_failed(message: impl Into<String>) -> Self {
1228        Self::RevocationFailed {
1229            message: message.into(),
1230        }
1231    }
1232}
1233
1234impl PermissionError {
1235    /// Create a new access denied error.
1236    ///
1237    /// # Example
1238    ///
1239    /// ```rust
1240    /// use auth_framework::errors::PermissionError;
1241    ///
1242    /// let err = PermissionError::access_denied("write", "documents");
1243    /// assert!(err.to_string().contains("write"));
1244    /// ```
1245    pub fn access_denied(permission: impl Into<String>, resource: impl Into<String>) -> Self {
1246        Self::AccessDenied {
1247            permission: permission.into(),
1248            resource: resource.into(),
1249        }
1250    }
1251
1252    /// Create a new role not found error.
1253    ///
1254    /// # Example
1255    ///
1256    /// ```rust
1257    /// use auth_framework::errors::PermissionError;
1258    ///
1259    /// let err = PermissionError::role_not_found("superadmin");
1260    /// assert!(err.to_string().contains("superadmin"));
1261    /// ```
1262    pub fn role_not_found(role: impl Into<String>) -> Self {
1263        Self::RoleNotFound { role: role.into() }
1264    }
1265
1266    /// Create a new permission not found error.
1267    ///
1268    /// # Example
1269    ///
1270    /// ```rust
1271    /// use auth_framework::errors::PermissionError;
1272    ///
1273    /// let err = PermissionError::permission_not_found("delete_user");
1274    /// assert!(err.to_string().contains("delete_user"));
1275    /// ```
1276    pub fn permission_not_found(permission: impl Into<String>) -> Self {
1277        Self::PermissionNotFound {
1278            permission: permission.into(),
1279        }
1280    }
1281
1282    /// Create a new invalid format error.
1283    ///
1284    /// # Example
1285    ///
1286    /// ```rust
1287    /// use auth_framework::errors::PermissionError;
1288    ///
1289    /// let err = PermissionError::invalid_format("missing resource:action separator");
1290    /// assert!(err.to_string().contains("separator"));
1291    /// ```
1292    pub fn invalid_format(message: impl Into<String>) -> Self {
1293        Self::InvalidFormat {
1294            message: message.into(),
1295        }
1296    }
1297}
1298
1299impl StorageError {
1300    /// Create a new connection failed error.
1301    ///
1302    /// # Example
1303    ///
1304    /// ```rust
1305    /// use auth_framework::errors::StorageError;
1306    ///
1307    /// let err = StorageError::connection_failed("connection refused");
1308    /// assert!(err.to_string().contains("connection refused"));
1309    /// ```
1310    pub fn connection_failed(message: impl Into<String>) -> Self {
1311        Self::ConnectionFailed {
1312            message: message.into(),
1313        }
1314    }
1315
1316    /// Create a new operation failed error.
1317    ///
1318    /// # Example
1319    ///
1320    /// ```rust
1321    /// use auth_framework::errors::StorageError;
1322    ///
1323    /// let err = StorageError::operation_failed("table not found");
1324    /// assert!(err.to_string().contains("table not found"));
1325    /// ```
1326    pub fn operation_failed(message: impl Into<String>) -> Self {
1327        Self::OperationFailed {
1328            message: message.into(),
1329        }
1330    }
1331
1332    /// Create a new serialization error.
1333    ///
1334    /// # Example
1335    ///
1336    /// ```rust
1337    /// use auth_framework::errors::StorageError;
1338    ///
1339    /// let err = StorageError::serialization("invalid JSON");
1340    /// assert!(err.to_string().contains("invalid JSON"));
1341    /// ```
1342    pub fn serialization(message: impl Into<String>) -> Self {
1343        Self::Serialization {
1344            message: message.into(),
1345        }
1346    }
1347}
1348
1349impl MfaError {
1350    /// Create a new method not supported error.
1351    ///
1352    /// # Example
1353    ///
1354    /// ```rust
1355    /// use auth_framework::errors::MfaError;
1356    ///
1357    /// let err = MfaError::method_not_supported("biometric");
1358    /// assert!(err.to_string().contains("biometric"));
1359    /// ```
1360    pub fn method_not_supported(method: impl Into<String>) -> Self {
1361        Self::MethodNotSupported {
1362            method: method.into(),
1363        }
1364    }
1365
1366    /// Create a new verification failed error.
1367    ///
1368    /// # Example
1369    ///
1370    /// ```rust
1371    /// use auth_framework::errors::MfaError;
1372    ///
1373    /// let err = MfaError::verification_failed("code expired");
1374    /// assert!(err.to_string().contains("code expired"));
1375    /// ```
1376    pub fn verification_failed(message: impl Into<String>) -> Self {
1377        Self::VerificationFailed {
1378            message: message.into(),
1379        }
1380    }
1381}
1382
1383// Actix-web ResponseError implementation
1384#[cfg(feature = "actix-web")]
1385impl actix_web::ResponseError for AuthError {
1386    fn error_response(&self) -> actix_web::HttpResponse {
1387        match self {
1388            AuthError::Token(_) => {
1389                actix_web::HttpResponse::Unauthorized().json(serde_json::json!({
1390                    "error": "invalid_token",
1391                    "error_description": self.to_string()
1392                }))
1393            }
1394            AuthError::Permission(_) => {
1395                actix_web::HttpResponse::Forbidden().json(serde_json::json!({
1396                    "error": "insufficient_permissions",
1397                    "error_description": self.to_string()
1398                }))
1399            }
1400            AuthError::RateLimit { .. } => {
1401                actix_web::HttpResponse::TooManyRequests().json(serde_json::json!({
1402                    "error": "rate_limit_exceeded",
1403                    "error_description": self.to_string()
1404                }))
1405            }
1406            AuthError::Configuration { .. }
1407            | AuthError::Storage(_)
1408            | AuthError::Internal { .. } => {
1409                actix_web::HttpResponse::InternalServerError().json(serde_json::json!({
1410                    "error": "internal_error",
1411                    "error_description": "An internal error occurred"
1412                }))
1413            }
1414            _ => actix_web::HttpResponse::BadRequest().json(serde_json::json!({
1415                "error": "bad_request",
1416                "error_description": self.to_string()
1417            })),
1418        }
1419    }
1420
1421    fn status_code(&self) -> actix_web::http::StatusCode {
1422        match self {
1423            AuthError::Token(_) => actix_web::http::StatusCode::UNAUTHORIZED,
1424            AuthError::Permission(_) => actix_web::http::StatusCode::FORBIDDEN,
1425            AuthError::RateLimit { .. } => actix_web::http::StatusCode::TOO_MANY_REQUESTS,
1426            AuthError::Internal { .. }
1427            | AuthError::Configuration { .. }
1428            | AuthError::Storage(_) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
1429            _ => actix_web::http::StatusCode::BAD_REQUEST,
1430        }
1431    }
1432}
1433
1434// Additional From implementations for admin tools
1435impl From<Box<dyn std::error::Error + Send + Sync>> for AuthError {
1436    fn from(error: Box<dyn std::error::Error + Send + Sync>) -> Self {
1437        AuthError::Cli(format!("Admin tool error: {}", error))
1438    }
1439}
1440
1441impl From<Box<dyn std::error::Error>> for AuthError {
1442    fn from(error: Box<dyn std::error::Error>) -> Self {
1443        AuthError::Cli(format!("Admin tool error: {}", error))
1444    }
1445}
1446
1447#[cfg(test)]
1448mod tests {
1449    use super::*;
1450    use std::error::Error;
1451
1452    #[test]
1453    fn test_auth_error_creation() {
1454        let token_error = AuthError::token("Invalid JWT signature");
1455        assert!(matches!(token_error, AuthError::Token(_)));
1456        assert!(token_error.to_string().contains("Invalid JWT signature"));
1457
1458        let permission_error = AuthError::access_denied("Access denied");
1459        assert!(matches!(permission_error, AuthError::Permission(_)));
1460        assert!(permission_error.to_string().contains("Access denied"));
1461
1462        let config_error = AuthError::config("Database connection failed");
1463        assert!(matches!(config_error, AuthError::Configuration { .. }));
1464        assert!(
1465            config_error
1466                .to_string()
1467                .contains("Database connection failed")
1468        );
1469    }
1470
1471    #[test]
1472    fn test_auth_error_categorization() {
1473        // Test various error types maintain their category
1474        let errors = vec![
1475            (AuthError::token("test"), "Token"),
1476            (AuthError::access_denied("test"), "Permission"),
1477            (AuthError::config("test"), "Configuration"),
1478            (AuthError::crypto("test"), "Crypto"),
1479            (AuthError::validation("test"), "Validation"),
1480        ];
1481
1482        for (error, expected_category) in errors {
1483            let error_string = format!("{:?}", error);
1484            assert!(
1485                error_string.contains(expected_category),
1486                "Error {:?} should contain category {}",
1487                error,
1488                expected_category
1489            );
1490        }
1491    }
1492
1493    #[test]
1494    fn test_rate_limit_error() {
1495        let rate_limit_error = AuthError::rate_limit("Too many requests");
1496
1497        match rate_limit_error {
1498            AuthError::RateLimit { message } => {
1499                assert_eq!(message, "Too many requests");
1500            }
1501            _ => panic!("Expected RateLimit error"),
1502        }
1503    }
1504
1505    #[test]
1506    fn test_validation_error() {
1507        let validation_error = AuthError::validation("username must not be empty");
1508
1509        match validation_error {
1510            AuthError::Validation { message } => {
1511                assert_eq!(message, "username must not be empty");
1512            }
1513            _ => panic!("Expected Validation error"),
1514        }
1515    }
1516
1517    #[test]
1518    fn test_configuration_error() {
1519        let config_error = AuthError::config("jwt_secret is required");
1520
1521        match config_error {
1522            AuthError::Configuration { message, .. } => {
1523                assert_eq!(message, "jwt_secret is required");
1524            }
1525            _ => panic!("Expected Configuration error"),
1526        }
1527    }
1528
1529    #[test]
1530    fn test_error_chain() {
1531        let root_cause = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
1532        let auth_error = AuthError::internal(format!("Config file error: {}", root_cause));
1533
1534        // Test that error information is preserved
1535        assert!(auth_error.to_string().contains("File not found"));
1536        assert!(auth_error.to_string().contains("Config file error"));
1537    }
1538
1539    #[test]
1540    fn test_error_source() {
1541        let token_error = AuthError::token("JWT parsing failed");
1542
1543        // AuthError implements Error trait
1544        // Token error wraps TokenError, so it should have a source
1545        assert!(token_error.source().is_some());
1546
1547        // Test error display
1548        let error_msg = format!("{}", token_error);
1549        assert!(error_msg.contains("JWT parsing failed"));
1550    }
1551
1552    #[test]
1553    fn test_from_conversions() {
1554        // Test conversion from std::io::Error
1555        let io_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Access denied");
1556        let auth_error: AuthError = io_error.into();
1557        assert!(matches!(auth_error, AuthError::Io(_)));
1558
1559        // Test conversion from serde_json::Error
1560        let json_str = r#"{"invalid": json"#;
1561        let json_error: serde_json::Error =
1562            serde_json::from_str::<serde_json::Value>(json_str).unwrap_err();
1563        let auth_error: AuthError = json_error.into();
1564        assert!(matches!(auth_error, AuthError::Json(_)));
1565    }
1566
1567    #[test]
1568    fn test_error_equality() {
1569        let error1 = AuthError::token("Same message");
1570        let error2 = AuthError::token("Same message");
1571        let error3 = AuthError::token("Different message");
1572
1573        // Test Debug representation for consistency
1574        assert_eq!(format!("{:?}", error1), format!("{:?}", error2));
1575        assert_ne!(format!("{:?}", error1), format!("{:?}", error3));
1576    }
1577
1578    #[test]
1579    fn test_actix_web_integration() {
1580        #[cfg(feature = "actix-web")]
1581        {
1582            use actix_web::ResponseError;
1583
1584            // Test status codes
1585            assert_eq!(
1586                AuthError::token("test").status_code(),
1587                actix_web::http::StatusCode::UNAUTHORIZED
1588            );
1589            assert_eq!(
1590                AuthError::access_denied("test").status_code(),
1591                actix_web::http::StatusCode::FORBIDDEN
1592            );
1593            assert_eq!(
1594                AuthError::rate_limit("test").status_code(),
1595                actix_web::http::StatusCode::TOO_MANY_REQUESTS
1596            );
1597            assert_eq!(
1598                AuthError::internal("test").status_code(),
1599                actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
1600            );
1601        }
1602    }
1603
1604    #[test]
1605    fn test_error_message_safety() {
1606        // Ensure error messages don't leak sensitive information
1607        let sensitive_data = "password123";
1608        let safe_error = AuthError::token("Invalid credentials");
1609
1610        // Error message should not contain sensitive data
1611        assert!(!safe_error.to_string().contains(sensitive_data));
1612
1613        // Test that we can create errors without exposing internals
1614        let config_error = AuthError::config("connection failed");
1615        assert!(!config_error.to_string().contains("password"));
1616        assert!(!config_error.to_string().contains("secret"));
1617    }
1618
1619    #[test]
1620    fn test_cli_error_conversion() {
1621        let boxed_error: Box<dyn std::error::Error + Send + Sync> = "CLI operation failed".into();
1622        let auth_error: AuthError = boxed_error.into();
1623
1624        assert!(matches!(auth_error, AuthError::Cli(_)));
1625        assert!(auth_error.to_string().contains("CLI operation failed"));
1626    }
1627
1628    #[test]
1629    fn test_error_variants_coverage() {
1630        // Ensure all error variants can be created and have proper messages
1631        let test_errors = vec![
1632            AuthError::token("token error"),
1633            AuthError::access_denied("permission error"),
1634            AuthError::internal("internal error"),
1635            AuthError::crypto("crypto error"),
1636            AuthError::Cli("cli error".to_string()),
1637            AuthError::validation("validation error"),
1638            AuthError::config("config error"),
1639            AuthError::rate_limit("rate limit error"),
1640        ];
1641
1642        for error in test_errors {
1643            // All errors should have non-empty messages
1644            assert!(
1645                !error.to_string().is_empty(),
1646                "Error should have message: {:?}",
1647                error
1648            );
1649
1650            // All errors should implement Debug
1651            let debug_repr = format!("{:?}", error);
1652            assert!(
1653                !debug_repr.is_empty(),
1654                "Error should have debug representation: {:?}",
1655                error
1656            );
1657        }
1658    }
1659
1660    #[test]
1661    fn test_oauth_specific_errors() {
1662        // Test OAuth-specific error creation using auth_method
1663        let invalid_client = AuthError::auth_method("oauth", "Client authentication failed");
1664        assert!(
1665            invalid_client
1666                .to_string()
1667                .contains("Client authentication failed")
1668        );
1669
1670        let invalid_grant = AuthError::auth_method("oauth", "Authorization code expired");
1671        assert!(
1672            invalid_grant
1673                .to_string()
1674                .contains("Authorization code expired")
1675        );
1676    }
1677
1678    #[test]
1679    fn test_error_context_preservation() {
1680        // Test that errors maintain context through transformations
1681        let original_msg = "Original error message";
1682        let context_msg = "Additional context";
1683
1684        let base_error = AuthError::internal(original_msg);
1685        let contextual_error = AuthError::internal(format!("{}: {}", context_msg, base_error));
1686
1687        assert!(contextual_error.to_string().contains(original_msg));
1688        assert!(contextual_error.to_string().contains(context_msg));
1689    }
1690
1691    #[test]
1692    fn test_error_serialization() {
1693        // Test that errors can be converted to JSON for API responses
1694        let error = AuthError::validation("email invalid format");
1695
1696        // Should be able to include in structured responses
1697        let error_response = serde_json::json!({
1698            "error": "validation_failed",
1699            "message": error.to_string(),
1700            "field": "email"
1701        });
1702
1703        assert!(
1704            error_response["message"]
1705                .as_str()
1706                .unwrap()
1707                .contains("invalid format")
1708        );
1709    }
1710
1711    #[test]
1712    fn test_concurrent_error_creation() {
1713        use std::thread;
1714
1715        let handles: Vec<_> = (0..10)
1716            .map(|i| {
1717                thread::spawn(move || {
1718                    let error = AuthError::token(format!("Concurrent error {}", i));
1719                    assert!(
1720                        error
1721                            .to_string()
1722                            .contains(&format!("Concurrent error {}", i))
1723                    );
1724                    error
1725                })
1726            })
1727            .collect();
1728
1729        // Wait for all threads and verify errors
1730        for handle in handles {
1731            let error = handle.join().unwrap();
1732            assert!(!error.to_string().is_empty());
1733        }
1734    }
1735}