80 lines
2.2 KiB
Rust
80 lines
2.2 KiB
Rust
use axum::http::StatusCode;
|
|
use axum::response::{IntoResponse, Response};
|
|
use axum::Json;
|
|
use tracing::error;
|
|
use serde_with::DisplayFromStr;
|
|
use uuid::Uuid;
|
|
use validator::ValidationErrors;
|
|
|
|
/// An API-friendly error type.
|
|
#[derive(thiserror::Error, Debug)]
|
|
pub enum Error {
|
|
/// A SQLx call returned an error.
|
|
///
|
|
/// The exact error contents are not reported to the user in order to avoid leaking
|
|
/// information about database internals.
|
|
#[error("an internal database error occurred")]
|
|
Sqlx(#[from] sqlx::Error),
|
|
|
|
/// Similarly, we don't want to report random `anyhow` errors to the user.
|
|
#[error("an internal server error occurred")]
|
|
Anyhow(#[from] anyhow::Error),
|
|
|
|
#[error("an internal server error occurred")]
|
|
Reqwest(#[from] reqwest::Error),
|
|
|
|
#[error("validation error in request body")]
|
|
InvalidEntity(#[from] ValidationErrors),
|
|
|
|
#[error("{0}: {1} not found")]
|
|
NotFound(&'static str, Uuid),
|
|
|
|
#[error("referenced {0} not found")]
|
|
RelationNotFound(&'static str),
|
|
}
|
|
|
|
pub type Result<T, E = Error> = ::std::result::Result<T, E>;
|
|
|
|
impl IntoResponse for Error {
|
|
fn into_response(self) -> Response {
|
|
#[serde_with::serde_as]
|
|
#[serde_with::skip_serializing_none]
|
|
#[derive(serde::Serialize)]
|
|
struct ErrorResponse<'a> {
|
|
// Serialize the `Display` output as the error message
|
|
#[serde_as(as = "DisplayFromStr")]
|
|
message: &'a Error,
|
|
|
|
errors: Option<&'a ValidationErrors>,
|
|
}
|
|
|
|
let errors = match &self {
|
|
Error::InvalidEntity(errors) => Some(errors),
|
|
_ => None,
|
|
};
|
|
|
|
error!("API error: {:?}", self);
|
|
|
|
(
|
|
self.status_code(),
|
|
Json(ErrorResponse {
|
|
message: &self,
|
|
errors,
|
|
}),
|
|
)
|
|
.into_response()
|
|
}
|
|
}
|
|
|
|
impl Error {
|
|
fn status_code(&self) -> StatusCode {
|
|
use Error::*;
|
|
|
|
match self {
|
|
NotFound(_, _) => StatusCode::NOT_FOUND,
|
|
Sqlx(_) | Anyhow(_) | Reqwest(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
InvalidEntity(_) | RelationNotFound(_) => StatusCode::UNPROCESSABLE_ENTITY,
|
|
}
|
|
}
|
|
}
|