use crate::error::ServiceError;
use std::fmt;
#[derive(Debug)]
pub enum AuthError {
InvalidTenant(String),
InvalidSession(String),
MissingCredentials,
InactiveSession,
SessionGateway(String),
SessionTimeout(String),
SessionTransport(String),
AuthorizationBackend(String),
AuthorizationBackendUnavailable(String),
MissingTenant,
MissingPermission,
}
impl AuthError {
pub fn failure_class(&self) -> &'static str {
match self {
AuthError::InvalidTenant(_) => "invalid_tenant",
AuthError::InvalidSession(_) => "invalid_session",
AuthError::MissingCredentials => "missing_credentials",
AuthError::InactiveSession => "inactive",
AuthError::SessionGateway(_) => "gateway_error",
AuthError::SessionTimeout(_) => "timeout",
AuthError::SessionTransport(_) => "transport",
AuthError::AuthorizationBackend(_) => "authorization_backend",
AuthError::AuthorizationBackendUnavailable(_) => "authorization_backend_unavailable",
AuthError::MissingTenant => "missing_tenant",
AuthError::MissingPermission => "missing_permission",
}
}
pub fn is_transient(&self) -> bool {
matches!(
self,
AuthError::SessionGateway(_)
| AuthError::SessionTimeout(_)
| AuthError::SessionTransport(_)
| AuthError::AuthorizationBackendUnavailable(_)
)
}
}
impl fmt::Display for AuthError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuthError::InvalidTenant(msg) => write!(f, "invalid tenant: {msg}"),
AuthError::InvalidSession(msg) => write!(f, "invalid session: {msg}"),
AuthError::MissingCredentials => {
write!(f, "missing authorization header or session cookie")
}
AuthError::InactiveSession => write!(f, "session or token is not active"),
AuthError::SessionGateway(msg) => {
write!(f, "session validation backend error: {msg}")
}
AuthError::SessionTimeout(msg) => {
write!(f, "session validation timed out: {msg}")
}
AuthError::SessionTransport(msg) => {
write!(f, "session validation unreachable: {msg}")
}
AuthError::AuthorizationBackend(msg) => {
write!(f, "authorization backend error: {msg}")
}
AuthError::AuthorizationBackendUnavailable(msg) => {
write!(f, "authorization backend unavailable: {msg}")
}
AuthError::MissingTenant => write!(f, "missing tenant"),
AuthError::MissingPermission => write!(f, "permission denied"),
}
}
}
impl std::error::Error for AuthError {}
impl From<AuthError> for ServiceError {
fn from(err: AuthError) -> Self {
match err {
AuthError::InvalidTenant(_)
| AuthError::InvalidSession(_)
| AuthError::MissingCredentials
| AuthError::InactiveSession
| AuthError::MissingTenant => ServiceError::Unauthenticated(err.to_string()),
AuthError::SessionGateway(_)
| AuthError::SessionTimeout(_)
| AuthError::SessionTransport(_) => ServiceError::Unauthenticated(err.to_string()),
AuthError::MissingPermission => ServiceError::PermissionDenied(err.to_string()),
AuthError::AuthorizationBackend(msg)
| AuthError::AuthorizationBackendUnavailable(msg) => ServiceError::Internal(msg),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn failure_classes_are_stable() {
assert_eq!(
AuthError::InvalidSession("x".into()).failure_class(),
"invalid_session"
);
assert_eq!(AuthError::InactiveSession.failure_class(), "inactive");
assert_eq!(
AuthError::SessionGateway("x".into()).failure_class(),
"gateway_error"
);
assert_eq!(
AuthError::SessionTimeout("x".into()).failure_class(),
"timeout"
);
assert_eq!(
AuthError::SessionTransport("x".into()).failure_class(),
"transport"
);
assert_eq!(
AuthError::AuthorizationBackend("x".into()).failure_class(),
"authorization_backend"
);
assert_eq!(AuthError::MissingTenant.failure_class(), "missing_tenant");
assert_eq!(
AuthError::MissingPermission.failure_class(),
"missing_permission"
);
}
#[test]
fn transient_classification() {
assert!(AuthError::SessionGateway("x".into()).is_transient());
assert!(AuthError::SessionTimeout("x".into()).is_transient());
assert!(AuthError::SessionTransport("x".into()).is_transient());
assert!(!AuthError::InactiveSession.is_transient());
assert!(!AuthError::InvalidSession("x".into()).is_transient());
}
#[test]
fn session_failures_map_to_unauthenticated() {
let err: ServiceError = AuthError::SessionGateway("bad gateway".into()).into();
assert!(matches!(err, ServiceError::Unauthenticated(_)));
let err: ServiceError = AuthError::SessionTimeout("slow".into()).into();
assert!(matches!(err, ServiceError::Unauthenticated(_)));
}
}