Skip to main content

authx_axum/
errors.rs

1use axum::{
2    http::StatusCode,
3    response::{IntoResponse, Json, Response},
4};
5use serde_json::json;
6
7use authx_core::error::AuthError;
8
9/// Newtype wrapper so we can implement axum's [`IntoResponse`] for
10/// [`AuthError`] without violating the orphan rule.
11pub struct AuthErrorResponse(pub AuthError);
12
13impl From<AuthError> for AuthErrorResponse {
14    fn from(e: AuthError) -> Self {
15        Self(e)
16    }
17}
18
19impl IntoResponse for AuthErrorResponse {
20    fn into_response(self) -> Response {
21        let err = &self.0;
22        let (status, code) = match err {
23            AuthError::InvalidCredentials => (StatusCode::UNAUTHORIZED, "invalid_credentials"),
24            AuthError::UserNotFound => (StatusCode::NOT_FOUND, "user_not_found"),
25            AuthError::SessionNotFound => (StatusCode::UNAUTHORIZED, "session_not_found"),
26            AuthError::EmailTaken => (StatusCode::CONFLICT, "email_taken"),
27            AuthError::EmailNotVerified => (StatusCode::FORBIDDEN, "email_not_verified"),
28            AuthError::InvalidToken => (StatusCode::UNAUTHORIZED, "invalid_token"),
29            AuthError::AccountLocked => (StatusCode::TOO_MANY_REQUESTS, "account_locked"),
30            AuthError::WeakPassword => (StatusCode::UNPROCESSABLE_ENTITY, "weak_password"),
31            AuthError::Forbidden(_) => (StatusCode::FORBIDDEN, "forbidden"),
32            AuthError::HashError(_)
33            | AuthError::EncryptionError(_)
34            | AuthError::Internal(_)
35            | AuthError::Storage(_) => (StatusCode::INTERNAL_SERVER_ERROR, "internal_error"),
36        };
37
38        tracing::warn!(status = status.as_u16(), error = code, detail = %err);
39        (
40            status,
41            Json(json!({ "error": code, "message": err.to_string() })),
42        )
43            .into_response()
44    }
45}