use crate::state::AppState;
use axum::extract::State;
use axum::response::{IntoResponse, Response};
use http::StatusCode;
use std::sync::{Arc, Mutex};
pub struct Error(Box<postrust_core::Error>);
impl Error {
pub fn status(&self) -> StatusCode {
self.0.status_code()
}
pub fn as_core(&self) -> &postrust_core::Error {
&self.0
}
pub fn into_core(self) -> postrust_core::Error {
*self.0
}
}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&*self.0)
}
}
impl From<postrust_core::Error> for Error {
fn from(error: postrust_core::Error) -> Self {
Self(Box::new(error))
}
}
impl From<postrust_core::db::DbError> for Error {
fn from(error: postrust_core::db::DbError) -> Self {
postrust_core::Error::from(error).into()
}
}
impl From<sqlx::Error> for Error {
fn from(error: sqlx::Error) -> Self {
postrust_core::db::DbError::from(error).into()
}
}
impl IntoResponse for Error {
fn into_response(self) -> Response {
if self.status().is_server_error() {
tracing::error!("Custom route error: {}", self.0);
} else {
tracing::debug!("Custom route refused with {}: {}", self.status(), self.0);
}
let kept = match &*self.0 {
postrust_core::Error::Database(db) => Some(Unrendered(Arc::new(Mutex::new(Some(
postrust_core::Error::Database(copy_of(db)),
))))),
_ => None,
};
let mut response = postrust_engine::error_response(*self.0, false);
if let Some(kept) = kept {
response.extensions_mut().insert(kept);
}
response.map(axum::body::Body::from)
}
}
fn copy_of(db: &postrust_core::error::DatabaseError) -> postrust_core::error::DatabaseError {
postrust_core::error::DatabaseError {
code: db.code.clone(),
message: db.message.clone(),
details: db.details.clone(),
hint: db.hint.clone(),
constraint: db.constraint.clone(),
table: db.table.clone(),
column: db.column.clone(),
}
}
#[derive(Clone)]
struct Unrendered(Arc<Mutex<Option<postrust_core::Error>>>);
pub(crate) async fn render_verbatim(
State(state): State<Arc<AppState>>,
mut response: Response,
) -> Response {
let Some(Unrendered(error)) = response.extensions_mut().remove::<Unrendered>() else {
return response;
};
let error = error.lock().ok().and_then(|mut error| error.take());
match error {
Some(error) => {
let (_, body) = postrust_engine::error_response_for(&state.config, error).into_parts();
let (parts, _) = response.into_parts();
Response::from_parts(parts, axum::body::Body::from(body))
}
None => response,
}
}