use axum::http::StatusCode; use thiserror::Error; use uuid::Uuid; use super::{AppError, ErrorCategory, ErrorSeverity, impl_into_response}; /// Errors related to user management operations #[derive(Error, Debug)] pub enum UserError { #[error("User not found")] NotFound, #[error("User with ID {id} not found")] NotFoundById { id: Uuid }, #[error("Username '{username}' already exists")] DuplicateUsername { username: String }, #[error("Email '{email}' already exists")] DuplicateEmail { email: String }, #[error("Invalid role '{role}'. Valid roles are: admin, user")] InvalidRole { role: String }, #[error("Permission denied: {reason}")] PermissionDenied { reason: String }, #[error("Invalid credentials")] InvalidCredentials, #[error("Account is disabled")] AccountDisabled, #[error("Password does not meet requirements: {requirements}")] InvalidPassword { requirements: String }, #[error("Username '{username}' is invalid: {reason}")] InvalidUsername { username: String, reason: String }, #[error("Email '{email}' is invalid")] InvalidEmail { email: String }, #[error("Cannot delete user with ID {id}: {reason}")] DeleteRestricted { id: Uuid, reason: String }, #[error("OIDC authentication failed: {details}")] OidcAuthenticationFailed { details: String }, #[error("Authentication provider '{provider}' is not configured")] AuthProviderNotConfigured { provider: String }, #[error("Token has expired")] TokenExpired, #[error("Invalid token format")] InvalidToken, #[error("User session has expired, please login again")] SessionExpired, #[error("Internal server error: {message}")] InternalServerError { message: String }, } impl AppError for UserError { fn status_code(&self) -> StatusCode { match self { UserError::NotFound | UserError::NotFoundById { .. } => StatusCode::NOT_FOUND, UserError::DuplicateUsername { .. } | UserError::DuplicateEmail { .. } => StatusCode::CONFLICT, UserError::InvalidRole { .. } => StatusCode::BAD_REQUEST, UserError::PermissionDenied { .. } => StatusCode::FORBIDDEN, UserError::InvalidCredentials => StatusCode::UNAUTHORIZED, UserError::AccountDisabled => StatusCode::FORBIDDEN, UserError::InvalidPassword { .. } => StatusCode::BAD_REQUEST, UserError::InvalidUsername { .. } => StatusCode::BAD_REQUEST, UserError::InvalidEmail { .. } => StatusCode::BAD_REQUEST, UserError::DeleteRestricted { .. } => StatusCode::FORBIDDEN, UserError::OidcAuthenticationFailed { .. } => StatusCode::UNAUTHORIZED, UserError::AuthProviderNotConfigured { .. } => StatusCode::BAD_REQUEST, UserError::TokenExpired => StatusCode::UNAUTHORIZED, UserError::InvalidToken => StatusCode::UNAUTHORIZED, UserError::SessionExpired => StatusCode::UNAUTHORIZED, UserError::InternalServerError { .. } => StatusCode::INTERNAL_SERVER_ERROR, } } fn user_message(&self) -> String { match self { UserError::NotFound | UserError::NotFoundById { .. } => "User not found".to_string(), UserError::DuplicateUsername { .. } => "Username already exists".to_string(), UserError::DuplicateEmail { .. } => "Email already exists".to_string(), UserError::InvalidRole { .. } => "Invalid user role specified".to_string(), UserError::PermissionDenied { reason } => format!("Permission denied: {}", reason), UserError::InvalidCredentials => "Invalid username or password".to_string(), UserError::AccountDisabled => "Account is disabled".to_string(), UserError::InvalidPassword { requirements } => format!("Password does not meet requirements: {}", requirements), UserError::InvalidUsername { reason, .. } => format!("Invalid username: {}", reason), UserError::InvalidEmail { .. } => "Invalid email address".to_string(), UserError::DeleteRestricted { reason, .. } => format!("Cannot delete user: {}", reason), UserError::OidcAuthenticationFailed { .. } => "OIDC authentication failed".to_string(), UserError::AuthProviderNotConfigured { .. } => "Authentication provider not configured".to_string(), UserError::TokenExpired => "Token has expired".to_string(), UserError::InvalidToken => "Invalid token".to_string(), UserError::SessionExpired => "Session has expired, please login again".to_string(), UserError::InternalServerError { .. } => "An internal error occurred".to_string(), } } fn error_code(&self) -> &'static str { match self { UserError::NotFound => "USER_NOT_FOUND", UserError::NotFoundById { .. } => "USER_NOT_FOUND_BY_ID", UserError::DuplicateUsername { .. } => "USER_DUPLICATE_USERNAME", UserError::DuplicateEmail { .. } => "USER_DUPLICATE_EMAIL", UserError::InvalidRole { .. } => "USER_INVALID_ROLE", UserError::PermissionDenied { .. } => "USER_PERMISSION_DENIED", UserError::InvalidCredentials => "USER_INVALID_CREDENTIALS", UserError::AccountDisabled => "USER_ACCOUNT_DISABLED", UserError::InvalidPassword { .. } => "USER_INVALID_PASSWORD", UserError::InvalidUsername { .. } => "USER_INVALID_USERNAME", UserError::InvalidEmail { .. } => "USER_INVALID_EMAIL", UserError::DeleteRestricted { .. } => "USER_DELETE_RESTRICTED", UserError::OidcAuthenticationFailed { .. } => "USER_OIDC_AUTH_FAILED", UserError::AuthProviderNotConfigured { .. } => "USER_AUTH_PROVIDER_NOT_CONFIGURED", UserError::TokenExpired => "USER_TOKEN_EXPIRED", UserError::InvalidToken => "USER_INVALID_TOKEN", UserError::SessionExpired => "USER_SESSION_EXPIRED", UserError::InternalServerError { .. } => "USER_INTERNAL_SERVER_ERROR", } } fn error_category(&self) -> ErrorCategory { ErrorCategory::Auth } fn error_severity(&self) -> ErrorSeverity { match self { UserError::PermissionDenied { .. } | UserError::DeleteRestricted { .. } => ErrorSeverity::Important, UserError::OidcAuthenticationFailed { .. } | UserError::AuthProviderNotConfigured { .. } => ErrorSeverity::Critical, UserError::InvalidCredentials | UserError::AccountDisabled => ErrorSeverity::Expected, UserError::InternalServerError { .. } => ErrorSeverity::Critical, _ => ErrorSeverity::Minor, } } fn suppression_key(&self) -> Option { match self { UserError::InvalidCredentials => Some("user_invalid_credentials".to_string()), UserError::NotFound => Some("user_not_found".to_string()), _ => None, } } fn suggested_action(&self) -> Option { match self { UserError::DuplicateUsername { .. } => Some("Please choose a different username".to_string()), UserError::DuplicateEmail { .. } => Some("Please use a different email address".to_string()), UserError::InvalidPassword { .. } => Some("Password must be at least 8 characters long and contain uppercase, lowercase, and numbers".to_string()), UserError::InvalidCredentials => Some("Please check your username and password".to_string()), UserError::SessionExpired | UserError::TokenExpired => Some("Please login again".to_string()), UserError::AccountDisabled => Some("Please contact an administrator".to_string()), _ => None, } } } impl_into_response!(UserError); /// Convenience methods for creating common user errors impl UserError { pub fn not_found_by_id(id: Uuid) -> Self { Self::NotFoundById { id } } pub fn duplicate_username>(username: S) -> Self { Self::DuplicateUsername { username: username.into() } } pub fn duplicate_email>(email: S) -> Self { Self::DuplicateEmail { email: email.into() } } pub fn invalid_role>(role: S) -> Self { Self::InvalidRole { role: role.into() } } pub fn permission_denied>(reason: S) -> Self { Self::PermissionDenied { reason: reason.into() } } pub fn invalid_password>(requirements: S) -> Self { Self::InvalidPassword { requirements: requirements.into() } } pub fn invalid_username>(username: S, reason: S) -> Self { Self::InvalidUsername { username: username.into(), reason: reason.into() } } pub fn invalid_email>(email: S) -> Self { Self::InvalidEmail { email: email.into() } } pub fn delete_restricted>(id: Uuid, reason: S) -> Self { Self::DeleteRestricted { id, reason: reason.into() } } pub fn oidc_authentication_failed>(details: S) -> Self { Self::OidcAuthenticationFailed { details: details.into() } } pub fn auth_provider_not_configured>(provider: S) -> Self { Self::AuthProviderNotConfigured { provider: provider.into() } } pub fn internal_server_error>(message: S) -> Self { Self::InternalServerError { message: message.into() } } }