Skip to main content

docbox_http/
error.rs

1use axum::{
2    Json,
3    http::{StatusCode, header::InvalidHeaderValue},
4    response::{IntoResponse, Response},
5};
6use serde::Serialize;
7use std::{
8    error::Error,
9    fmt::{Debug, Display},
10};
11use thiserror::Error;
12use utoipa::ToSchema;
13
14/// Type alias for dynamic error handling and JSON responses
15pub type HttpResult<T> = Result<Json<T>, DynHttpError>;
16
17pub type HttpStatusResult = Result<StatusCode, DynHttpError>;
18
19/// Wrapper for dynamic error handling using [HttpError] types
20pub struct DynHttpError {
21    /// The dynamic error cause
22    inner: Box<dyn HttpError>,
23}
24
25impl Debug for DynHttpError {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        f.debug_tuple(self.inner.type_name())
28            .field(&self.inner)
29            .finish()
30    }
31}
32
33impl Display for DynHttpError {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        Display::fmt(&self.inner, f)
36    }
37}
38
39impl Error for DynHttpError {}
40
41/// Handles converting the error into a response (Also logs the error before conversion)
42impl IntoResponse for DynHttpError {
43    fn into_response(self) -> Response {
44        // Create the response body
45        let body = Json(HttpErrorResponse {
46            reason: self.inner.reason(),
47        });
48        let status = self.inner.status();
49
50        (status, body).into_response()
51    }
52}
53
54/// Trait implemented by errors that can be converted into [HttpError]s
55/// and used as error responses
56pub trait HttpError: Error + Send + Sync + 'static {
57    /// Provides the HTTP [StatusCode] to use when creating this error response
58    fn status(&self) -> StatusCode {
59        StatusCode::INTERNAL_SERVER_ERROR
60    }
61
62    /// Provides the reason message to use in the error response
63    fn reason(&self) -> String {
64        self.to_string()
65    }
66
67    /// Provides the full type name for the actual error type thats been
68    /// erased by dynamic typing (For better error source clarity)
69    fn type_name(&self) -> &str {
70        std::any::type_name::<Self>()
71    }
72}
73
74/// Allow conversion from implementors of [HttpError] into a [DynHttpError]
75impl<E> From<E> for DynHttpError
76where
77    E: HttpError,
78{
79    fn from(value: E) -> Self {
80        DynHttpError {
81            inner: Box::new(value),
82        }
83    }
84}
85
86impl HttpError for axum::http::Error {
87    fn status(&self) -> StatusCode {
88        StatusCode::INTERNAL_SERVER_ERROR
89    }
90}
91
92impl HttpError for InvalidHeaderValue {
93    fn status(&self) -> StatusCode {
94        StatusCode::INTERNAL_SERVER_ERROR
95    }
96}
97
98/// HTTP error JSON format for serializing responses
99#[derive(Debug, Serialize, ToSchema)]
100#[serde(rename_all = "camelCase")]
101pub struct HttpErrorResponse {
102    pub reason: String,
103}
104
105#[derive(Debug, Error)]
106pub enum HttpCommonError {
107    #[error("internal server error")]
108    ServerError,
109    #[error("unsupported endpoint")]
110    Unsupported,
111}
112
113impl HttpError for HttpCommonError {
114    fn status(&self) -> axum::http::StatusCode {
115        match self {
116            HttpCommonError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
117            HttpCommonError::Unsupported => StatusCode::NOT_IMPLEMENTED,
118        }
119    }
120}