Files
crawlnicle/src/error.rs
2023-07-08 01:05:49 -04:00

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,
}
}
}