use axum::{
Json,
http::{StatusCode, header::InvalidHeaderValue},
response::{IntoResponse, Response},
};
use serde::Serialize;
use std::{
error::Error,
fmt::{Debug, Display},
};
use thiserror::Error;
use utoipa::ToSchema;
pub type HttpResult<T> = Result<Json<T>, DynHttpError>;
pub type HttpStatusResult = Result<StatusCode, DynHttpError>;
pub struct DynHttpError {
inner: Box<dyn HttpError>,
}
impl Debug for DynHttpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple(self.inner.type_name())
.field(&self.inner)
.finish()
}
}
impl Display for DynHttpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl Error for DynHttpError {}
impl IntoResponse for DynHttpError {
fn into_response(self) -> Response {
let body = Json(HttpErrorResponse {
reason: self.inner.reason(),
});
let status = self.inner.status();
(status, body).into_response()
}
}
pub trait HttpError: Error + Send + Sync + 'static {
fn status(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
fn reason(&self) -> String {
self.to_string()
}
fn type_name(&self) -> &str {
std::any::type_name::<Self>()
}
}
impl<E> From<E> for DynHttpError
where
E: HttpError,
{
fn from(value: E) -> Self {
DynHttpError {
inner: Box::new(value),
}
}
}
impl HttpError for axum::http::Error {
fn status(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
}
impl HttpError for InvalidHeaderValue {
fn status(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
}
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct HttpErrorResponse {
pub reason: String,
}
#[derive(Debug, Error)]
pub enum HttpCommonError {
#[error("internal server error")]
ServerError,
#[error("unsupported endpoint")]
Unsupported,
}
impl HttpError for HttpCommonError {
fn status(&self) -> axum::http::StatusCode {
match self {
HttpCommonError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
HttpCommonError::Unsupported => StatusCode::NOT_IMPLEMENTED,
}
}
}