//! Error types for the authentication plugin. //! //! This module provides structured error handling with specific error kinds //! for different failure scenarios in authentication operations. use std::fmt; /// Enum representing different kinds of authentication errors. #[derive(Debug, Clone, PartialEq, Eq)] pub enum AuthErrorKind { /// Failed to authenticate with invalid credentials InvalidCredentials, /// Token has expired TokenExpired, /// Token format is invalid InvalidToken, /// Failed to verify token signature SignatureVerificationFailed, /// Keyring operation failed KeyringError, /// Network or HTTP request failed NetworkError, /// Server returned an error response ServerError, /// MFA verification failed MfaFailed, /// User not found UserNotFound, /// Session not found or expired SessionNotFound, /// Configuration error ConfigurationError, /// Internal error InternalError, } impl fmt::Display for AuthErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::InvalidCredentials => write!(f, "invalid credentials"), Self::TokenExpired => write!(f, "token expired"), Self::InvalidToken => write!(f, "invalid token format"), Self::SignatureVerificationFailed => write!(f, "signature verification failed"), Self::KeyringError => write!(f, "keyring operation failed"), Self::NetworkError => write!(f, "network error"), Self::ServerError => write!(f, "server error"), Self::MfaFailed => write!(f, "MFA verification failed"), Self::UserNotFound => write!(f, "user not found"), Self::SessionNotFound => write!(f, "session not found"), Self::ConfigurationError => write!(f, "configuration error"), Self::InternalError => write!(f, "internal error"), } } } /// Structured error type for authentication operations. /// /// Provides detailed error information including: /// - Error kind for programmatic handling /// - Context message for additional details /// - Optional source error for error chaining #[derive(Debug)] pub struct AuthError { /// The kind of error that occurred pub kind: AuthErrorKind, /// Additional context about the error pub context: String, /// Optional underlying error pub source: Option>, } impl AuthError { /// Creates a new AuthError with the specified kind and context. /// /// # Arguments /// /// * `kind` - The type of authentication error /// * `context` - Additional context describing the error /// /// # Example /// /// ``` /// use nu_plugin_auth::error::{AuthError, AuthErrorKind}; /// /// let error = AuthError::new( /// AuthErrorKind::InvalidCredentials, /// "Username or password is incorrect" /// ); /// ``` pub fn new(kind: AuthErrorKind, context: impl Into) -> Self { Self { kind, context: context.into(), source: None, } } /// Creates an AuthError with an underlying source error. /// /// # Arguments /// /// * `kind` - The type of authentication error /// * `context` - Additional context describing the error /// * `source` - The underlying error that caused this error pub fn with_source( kind: AuthErrorKind, context: impl Into, source: impl std::error::Error + Send + Sync + 'static, ) -> Self { Self { kind, context: context.into(), source: Some(Box::new(source)), } } /// Creates an invalid credentials error. pub fn invalid_credentials(context: impl Into) -> Self { Self::new(AuthErrorKind::InvalidCredentials, context) } /// Creates a token expired error. pub fn token_expired(context: impl Into) -> Self { Self::new(AuthErrorKind::TokenExpired, context) } /// Creates an invalid token error. pub fn invalid_token(context: impl Into) -> Self { Self::new(AuthErrorKind::InvalidToken, context) } /// Creates a keyring error. pub fn keyring_error(context: impl Into) -> Self { Self::new(AuthErrorKind::KeyringError, context) } /// Creates a network error. pub fn network_error(context: impl Into) -> Self { Self::new(AuthErrorKind::NetworkError, context) } /// Creates a server error. pub fn server_error(context: impl Into) -> Self { Self::new(AuthErrorKind::ServerError, context) } /// Creates an MFA failed error. pub fn mfa_failed(context: impl Into) -> Self { Self::new(AuthErrorKind::MfaFailed, context) } /// Creates a configuration error. pub fn configuration_error(context: impl Into) -> Self { Self::new(AuthErrorKind::ConfigurationError, context) } } impl fmt::Display for AuthError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {}", self.kind, self.context)?; if let Some(ref source) = self.source { write!(f, " (caused by: {})", source)?; } Ok(()) } } impl std::error::Error for AuthError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.source .as_ref() .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)) } } impl From for nu_protocol::LabeledError { fn from(err: AuthError) -> Self { nu_protocol::LabeledError::new(err.to_string()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_error_display() { let error = AuthError::new( AuthErrorKind::InvalidCredentials, "username admin not found", ); assert!(error.to_string().contains("invalid credentials")); assert!(error.to_string().contains("username admin not found")); } #[test] fn test_error_with_source() { let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"); let error = AuthError::with_source( AuthErrorKind::KeyringError, "failed to read keyring", io_error, ); assert!(error.to_string().contains("caused by")); } #[test] fn test_error_kind_display() { assert_eq!(AuthErrorKind::InvalidCredentials.to_string(), "invalid credentials"); assert_eq!(AuthErrorKind::TokenExpired.to_string(), "token expired"); assert_eq!(AuthErrorKind::KeyringError.to_string(), "keyring operation failed"); } #[test] fn test_convenience_constructors() { let error = AuthError::invalid_credentials("bad password"); assert_eq!(error.kind, AuthErrorKind::InvalidCredentials); let error = AuthError::network_error("connection refused"); assert_eq!(error.kind, AuthErrorKind::NetworkError); } }