use std::error::Error;
use std::fmt::{Debug, Display};
use std::marker::PhantomData;
use axum_core::response::{IntoResponse, Response};
use http::StatusCode;
#[cfg(feature = "anyhow")]
pub mod anyhow;
pub type HttpResult<T, I = TextErrorResponse> = Result<T, DynHttpError<I>>;
pub struct DynHttpError<I: IntoHttpErrorResponse = TextErrorResponse> {
inner: Box<dyn HttpError>,
_marker: PhantomData<I>,
}
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<I: IntoHttpErrorResponse> IntoResponse for DynHttpError<I> {
fn into_response(self) -> Response {
let error = self.inner;
#[cfg(feature = "log")]
{
error.log();
}
I::into_response(error)
}
}
pub trait IntoHttpErrorResponse {
fn into_response(error: Box<dyn HttpError>) -> Response;
}
pub struct TextErrorResponse;
impl IntoHttpErrorResponse for TextErrorResponse {
fn into_response(error: Box<dyn HttpError>) -> Response {
(error.status(), error.reason()).into_response()
}
}
pub trait HttpError: Error + Send + Sync + 'static {
#[cfg(feature = "log")]
fn log(&self) {
log::error!("{self}: {self:?}");
}
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>()
}
}