use core::panic::Location;
use bytes::Bytes;
use http::{HeaderValue, Response, StatusCode, header};
use serde::{Deserialize, Serialize};
use ts_error::Report;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ErrorResponse {
BadRequest(BadRequest),
NotFound,
InternalServerError,
}
impl ErrorResponse {
pub fn not_found() -> Self {
Self::NotFound
}
pub fn internal_server_error() -> Self {
Self::InternalServerError
}
pub fn bad_request<S1: ToString, S2: ToString>(pointer: S1, detail: S2) -> Self {
Self::BadRequest(BadRequest::basic(pointer, detail))
}
#[track_caller]
pub fn into_response<B: From<Bytes>>(self) -> Response<B> {
match self {
Self::BadRequest(bad_request) => bad_request.into_response(),
Self::NotFound => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(B::from(Bytes::new()))
.expect("response should be valid"),
Self::InternalServerError => Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(B::from(Bytes::new()))
.expect("response should be valid"),
}
}
}
impl From<BadRequest> for ErrorResponse {
fn from(value: BadRequest) -> Self {
Self::BadRequest(value)
}
}
impl<E: core::error::Error> From<E> for ErrorResponse {
#[track_caller]
fn from(value: E) -> Self {
let error = Report::new(value);
let location = Location::caller();
log::error!("[{location}] {error}");
Self::InternalServerError
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Problem {
pub pointer: String,
pub detail: String,
}
impl Problem {
pub fn new<S1: ToString, S2: ToString>(pointer: S1, detail: S2) -> Self {
Self {
pointer: pointer.to_string(),
detail: detail.to_string(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BadRequest {
pub problems: Vec<Problem>,
}
impl BadRequest {
pub fn basic<S1: ToString, S2: ToString>(pointer: S1, detail: S2) -> Self {
Self {
problems: vec![Problem::new(pointer, detail)],
}
}
pub fn new() -> Self {
Self { problems: vec![] }
}
pub fn add_problem(&mut self, problem: Problem) {
self.problems.push(problem);
}
pub fn has_problems(&self) -> bool {
!self.problems.is_empty()
}
#[track_caller]
pub fn into_response<B: From<Bytes>>(self) -> Response<B> {
assert!(
self.has_problems(),
"{:?} An empty bad request cannot be returned",
Location::caller()
);
let json =
serde_json::to_string(&self).expect("error response should always be serializable");
Response::builder()
.status(StatusCode::BAD_REQUEST)
.header(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
)
.body(B::from(Bytes::from(json)))
.expect("error response should produce a valid response")
}
}
#[cfg(feature = "axum")]
impl axum::response::IntoResponse for BadRequest {
fn into_response(self) -> axum::response::Response {
self.into_response::<axum::body::Body>().into_response()
}
}
#[cfg(feature = "axum")]
impl axum::response::IntoResponse for ErrorResponse {
fn into_response(self) -> axum::response::Response {
self.into_response()
}
}