axum_util/
errors.rs

1use std::fmt;
2
3use axum::{
4    response::{IntoResponse, Response},
5    Json,
6};
7use http::{header::LOCATION, StatusCode};
8use log::error;
9use serde::{Deserialize, Serialize};
10use url::Url;
11
12#[derive(Serialize, Deserialize)]
13pub struct ErrorBody {
14    pub message: String,
15}
16
17#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
18pub enum RedirectMode {
19    MovedPermanently,
20    #[default]
21    Found,
22    SeeOther,
23    TemporaryRedirect,
24    PermanentRedirect,
25}
26
27impl RedirectMode {
28    pub fn status_code(&self) -> StatusCode {
29        match self {
30            RedirectMode::MovedPermanently => StatusCode::MOVED_PERMANENTLY,
31            RedirectMode::Found => StatusCode::FOUND,
32            RedirectMode::SeeOther => StatusCode::SEE_OTHER,
33            RedirectMode::TemporaryRedirect => StatusCode::TEMPORARY_REDIRECT,
34            RedirectMode::PermanentRedirect => StatusCode::PERMANENT_REDIRECT,
35        }
36    }
37}
38
39#[derive(Debug)]
40pub enum ApiError {
41    Redirect(RedirectMode, Url),
42    NotModified,
43    BadRequest(String),
44    Unauthorized(String),
45    Forbidden(String),
46    NotFound,
47    Response(Response),
48    Other(anyhow::Error),
49}
50
51impl fmt::Display for ApiError {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        <Self as fmt::Debug>::fmt(self, f)
54    }
55}
56
57impl<E: std::error::Error + Send + Sync + 'static> From<E> for ApiError {
58    fn from(error: E) -> Self {
59        Self::Other(anyhow::Error::from(error))
60    }
61}
62
63impl IntoResponse for ApiError {
64    fn into_response(self) -> Response {
65        match self {
66            ApiError::Redirect(mode, destination) => {
67                (mode.status_code(), [(LOCATION, destination.to_string())]).into_response()
68            }
69            ApiError::NotModified => StatusCode::NOT_MODIFIED.into_response(),
70            ApiError::BadRequest(message) => {
71                (StatusCode::BAD_REQUEST, Json(ErrorBody { message })).into_response()
72            }
73            ApiError::Unauthorized(message) => {
74                (StatusCode::UNAUTHORIZED, Json(ErrorBody { message })).into_response()
75            }
76            ApiError::Forbidden(message) => {
77                (StatusCode::FORBIDDEN, Json(ErrorBody { message })).into_response()
78            }
79            ApiError::NotFound => (
80                StatusCode::NOT_FOUND,
81                Json(ErrorBody {
82                    message: "not found".to_string(),
83                }),
84            )
85                .into_response(),
86            ApiError::Response(response) => response,
87            ApiError::Other(e) => {
88                error!("internal error: {:#}", e);
89                StatusCode::INTERNAL_SERVER_ERROR.into_response()
90            }
91        }
92    }
93}
94
95pub type ApiResult<T> = Result<T, ApiError>;