use axum::{
Json,
http::StatusCode,
response::{IntoResponse, Response},
};
use serde::Serialize;
use utoipa::ToSchema;
#[derive(Debug, Serialize, ToSchema)]
pub struct ApiError {
pub error: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<String>,
}
impl ApiError {
pub fn new(error: impl Into<String>) -> Self {
Self {
error: error.into(),
details: None,
}
}
pub fn with_details(
error: impl Into<String>,
details: impl Into<String>,
) -> Self {
Self {
error: error.into(),
details: Some(details.into()),
}
}
pub fn not_found(resource: &str, name: &str) -> Self {
Self::new(format!("{} '{}' not found", resource, name))
}
pub fn already_exists(resource: &str, name: &str) -> Self {
Self::new(format!("{} '{}' already exists", resource, name))
}
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let status = match self.error.as_str() {
e if e.contains("not found") => StatusCode::NOT_FOUND,
e if e.contains("already exists") => StatusCode::CONFLICT,
e if e.contains("invalid") || e.contains("missing") => {
StatusCode::BAD_REQUEST
}
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
(status, Json(self)).into_response()
}
}
impl From<crate::BloomError> for ApiError {
fn from(err: crate::BloomError) -> Self {
Self::with_details("Bloom filter error", err.to_string())
}
}
impl From<crate::EbloomError> for ApiError {
fn from(err: crate::EbloomError) -> Self {
Self::with_details("Expiring bloom filter error", err.to_string())
}
}
pub type ApiResult<T> = Result<T, ApiError>;