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>;