use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error("Entity not found: {0}")]
NotFound(String),
#[error("Illegal Operation: {0}")]
IllegalOperation(String),
#[error("Entity already exists: {0}")]
AlreadyExists(String),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Invalid entity type: {0}")]
InvalidEntityType(String),
#[error("Failed to resolve link: {0}")]
LinkResolution(String),
#[error("{0}")]
Generic(String),
}
#[cfg(feature = "axum")]
#[derive(
Debug,
Clone,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
utoipa::ToSchema,
utoipa::ToResponse,
)]
pub struct ApiError {
pub error: String,
pub status: u16,
}
#[cfg(feature = "axum")]
impl ApiError {
pub fn new<S: Into<u16>>(error: String, status: S) -> Self {
Self { error, status: status.into() }
}
}
#[cfg(feature = "axum")]
mod axum_impl {
use axum::Json;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use super::*;
impl IntoResponse for Error {
fn into_response(self) -> Response {
let (status, message) = match &self {
Error::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
Error::IllegalOperation(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
_ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
};
(status, Json(ApiError::new(message, status))).into_response()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_error_into_response() {
let error = Error::NotFound("Not found".to_string());
let response = error.into_response();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap();
let body: serde_json::Value = serde_json::from_reader(&*body).unwrap();
assert_eq!(body["error"], "Not found");
assert_eq!(body["status"], 404);
}
}
}