Skip to main content

nythos_core/
error.rs

1//! Core error types shared across `nythos-core`.
2use std::fmt;
3
4/// Standard result type for core operations.
5pub type NythosResult<T> = Result<T, AuthError>;
6
7/// Common failure type for domain and application-level auth logic.
8///
9/// This error model is intentionally transport-agnostic. It does not encode
10/// HTTP status codes, framework errors, or infra-specific concerns.
11#[non_exhaustive]
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum AuthError {
14    UserNotFound,
15    InvalidCredentials,
16    AccountLocked,
17    SessionRevoked,
18    SessionExpired,
19    TenantNotFound,
20    PermissionDenied,
21    OAuthIdentityAlreadyLinked,
22    OAuthIdentityAlreadyLinkedToSelf,
23    UserNotFoundOrInactive,
24    ValidationError(String),
25    Internal(String),
26}
27
28impl fmt::Display for AuthError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            AuthError::UserNotFound => f.write_str("user not found"),
32            AuthError::InvalidCredentials => f.write_str("invalid credentials"),
33            AuthError::AccountLocked => f.write_str("account locked"),
34            AuthError::SessionRevoked => f.write_str("session revoked"),
35            AuthError::SessionExpired => f.write_str("session expired"),
36            AuthError::TenantNotFound => f.write_str("tenant not found"),
37            AuthError::PermissionDenied => f.write_str("permission denied"),
38            AuthError::OAuthIdentityAlreadyLinked => f.write_str("OAuth identity already linked"),
39            AuthError::OAuthIdentityAlreadyLinkedToSelf => {
40                f.write_str("OAuth identity already linked to this user")
41            }
42            AuthError::UserNotFoundOrInactive => f.write_str("user not found or inactive"),
43            AuthError::ValidationError(msg) => write!(f, "validation error: {}", msg),
44            AuthError::Internal(msg) => write!(f, "internal error: {}", msg),
45        }
46    }
47}
48
49impl std::error::Error for AuthError {}
50
51#[cfg(test)]
52mod tests {
53    use super::{AuthError, NythosResult};
54
55    #[test]
56    fn string_payload_variants_preserve_messages() {
57        let validation_error = AuthError::ValidationError("invalid email".to_owned());
58        let internal_error = AuthError::Internal("signer unavailable".to_owned());
59
60        assert_eq!(
61            validation_error.to_string(),
62            "validation error: invalid email"
63        );
64
65        assert_eq!(
66            internal_error.to_string(),
67            "internal error: signer unavailable"
68        );
69    }
70
71    #[test]
72    fn unit_variants_match_as_expected() {
73        let err = AuthError::InvalidCredentials;
74
75        assert!(matches!(err, AuthError::InvalidCredentials));
76        assert_ne!(err, AuthError::UserNotFound);
77    }
78
79    #[test]
80    fn display_messages_are_transport_agnostic() {
81        assert_eq!(AuthError::UserNotFound.to_string(), "user not found");
82        assert_eq!(
83            AuthError::ValidationError("bad input".to_owned()).to_string(),
84            "validation error: bad input"
85        );
86        assert_eq!(
87            AuthError::Internal("store failed".to_owned()).to_string(),
88            "internal error: store failed"
89        );
90    }
91
92    #[test]
93    fn nythos_result_alias_uses_auth_error() {
94        fn fails() -> NythosResult<()> {
95            Err(AuthError::SessionExpired)
96        }
97
98        let result = fails();
99
100        assert!(matches!(result, Err(AuthError::SessionExpired)));
101    }
102}