use crate::error::Error;
use axum::Json;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde_derive::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use tracing::error;
#[serde_with::skip_serializing_none]
#[derive(Debug, Error, Serialize, Deserialize)]
#[cfg_attr(feature = "open-api", derive(aide::OperationIo, schemars::JsonSchema))]
#[non_exhaustive]
pub struct HttpError<T = ()> {
#[serde(skip)]
pub status: StatusCode,
pub error: Option<String>,
pub details: Option<T>,
#[source]
#[serde(skip)]
pub source: Option<Box<dyn Send + Sync + std::error::Error>>,
}
impl<T> Display for HttpError<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Http Error {} - {:?}", self.status, self.error)
}
}
impl HttpError {
pub fn bad_request() -> Self {
Self::new(StatusCode::BAD_REQUEST)
}
pub fn unauthorized() -> Self {
Self::new(StatusCode::UNAUTHORIZED)
}
pub fn forbidden() -> Self {
Self::new(StatusCode::FORBIDDEN)
}
pub fn not_found() -> Self {
Self::new(StatusCode::NOT_FOUND)
}
pub fn gone() -> Self {
Self::new(StatusCode::GONE)
}
pub fn unprocessable_entity() -> Self {
Self::new(StatusCode::UNPROCESSABLE_ENTITY)
}
pub fn unprocessable_content() -> Self {
Self::unprocessable_entity()
}
pub fn internal_server_error() -> Self {
Self::new(StatusCode::INTERNAL_SERVER_ERROR)
}
pub fn not_implemented() -> Self {
Self::new(StatusCode::NOT_IMPLEMENTED)
}
}
impl<T> HttpError<T> {
pub fn new(status: StatusCode) -> Self {
Self {
status,
error: None,
details: None,
source: None,
}
}
pub fn error(self, error: impl ToString) -> Self {
Self {
error: Some(error.to_string()),
..self
}
}
pub fn details<T2>(self, details: T2) -> HttpError<T2>
where
T2: serde::Serialize,
{
HttpError {
details: Some(details),
error: self.error,
status: self.status,
source: self.source,
}
}
pub fn source(self, source: impl 'static + Send + Sync + std::error::Error) -> Self {
Self {
source: Some(Box::new(source)),
..self
}
}
}
impl<T> HttpError<T>
where
T: serde::Serialize,
{
pub fn to_err(self) -> Error {
self.into()
}
pub(crate) fn details_serialized(self) -> HttpError<serde_json::Value> {
let details =
self.details
.as_ref()
.and_then(|details| match serde_json::to_value(details) {
Ok(details) => Some(details),
Err(err) => {
error!("Unable to serialize error details: {err}");
None
}
});
HttpError {
details,
error: self.error,
status: self.status,
source: self.source,
}
}
}
impl From<StatusCode> for HttpError {
fn from(value: StatusCode) -> Self {
HttpError::new(value)
}
}
impl From<StatusCode> for Error {
fn from(value: StatusCode) -> Self {
HttpError::<serde_json::Value>::new(value).into()
}
}
impl<T> IntoResponse for HttpError<T>
where
T: serde::Serialize,
{
fn into_response(self) -> Response {
let status = self.status;
let mut res = Json(self).into_response();
*res.status_mut() = status;
res
}
}