pub use app_routes::{AppRoutes, ListRoutes};
use axum::{
extract::FromRequest,
http::StatusCode,
response::{IntoResponse, Response},
};
use colored::Colorize;
pub use routes::Routes;
use serde::Serialize;
use crate::{errors::Error, Result};
mod app_routes;
mod backtrace;
mod describe;
pub mod extractor;
pub mod format;
#[cfg(feature = "with-db")]
mod health;
pub mod middleware;
mod ping;
mod routes;
pub mod views;
pub fn unauthorized<T: Into<String>, U>(msg: T) -> Result<U> {
Err(Error::Unauthorized(msg.into()))
}
pub fn bad_request<T: Into<String>, U>(msg: T) -> Result<U> {
Err(Error::BadRequest(msg.into()))
}
pub fn not_found<T>() -> Result<T> {
Err(Error::NotFound)
}
#[derive(Debug, Serialize)]
pub struct ErrorDetail {
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub errors: Option<serde_json::Value>,
}
impl ErrorDetail {
#[must_use]
pub fn new<T: Into<String> + AsRef<str>>(error: T, description: T) -> Self {
let description = (!description.as_ref().is_empty()).then(|| description.into());
Self {
error: Some(error.into()),
description,
errors: None,
}
}
#[must_use]
pub fn with_reason<T: Into<String>>(error: T) -> Self {
Self {
error: Some(error.into()),
description: None,
errors: None,
}
}
}
#[derive(Debug, FromRequest)]
#[from_request(via(axum::Json), rejection(Error))]
pub struct Json<T>(pub T);
impl<T: Serialize> IntoResponse for Json<T> {
fn into_response(self) -> axum::response::Response {
axum::Json(self.0).into_response()
}
}
impl IntoResponse for Error {
#[allow(clippy::cognitive_complexity)]
fn into_response(self) -> Response {
match &self {
Self::WithBacktrace {
inner,
backtrace: _,
} => {
tracing::error!(
error.msg = %inner,
error.details = ?inner,
"controller_error"
);
}
err => {
tracing::error!(
error.msg = %err,
error.details = ?err,
"controller_error"
);
}
}
let public_facing_error = match self {
Self::NotFound => (
StatusCode::NOT_FOUND,
ErrorDetail::new("not_found", "Resource was not found"),
),
Self::Unauthorized(err) => {
tracing::warn!(err);
(
StatusCode::UNAUTHORIZED,
ErrorDetail::new(
"unauthorized",
"You do not have permission to access this resource",
),
)
}
Self::CustomError(status_code, data) => (status_code, data),
Self::WithBacktrace { inner, backtrace } => {
println!("\n{}", inner.to_string().red().underline());
backtrace::print_backtrace(&backtrace).unwrap();
(
StatusCode::BAD_REQUEST,
ErrorDetail::with_reason("Bad Request"),
)
}
Self::BadRequest(err) => (
StatusCode::BAD_REQUEST,
ErrorDetail::new("Bad Request", &err),
),
Self::JsonRejection(err) => {
tracing::debug!(err = err.body_text(), "json rejection");
(err.status(), ErrorDetail::with_reason("Bad Request"))
}
Self::ValidationError(ref errors) => serde_json::to_value(errors).map_or_else(
|_| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ErrorDetail::new("internal_server_error", "Internal Server Error"),
)
},
|errors| {
(
StatusCode::BAD_REQUEST,
ErrorDetail {
error: None,
description: None,
errors: Some(errors),
},
)
},
),
_ => (
StatusCode::INTERNAL_SERVER_ERROR,
ErrorDetail::new("internal_server_error", "Internal Server Error"),
),
};
(public_facing_error.0, Json(public_facing_error.1)).into_response()
}
}