Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Id99798df6f7e4470caae8a193c2654aa6a6a6964
99 lines
3.6 KiB
Rust
99 lines
3.6 KiB
Rust
use axum::http::StatusCode;
|
|
use axum::response::{IntoResponse, Response};
|
|
use serde::Serialize;
|
|
|
|
#[derive(Debug, Serialize)]
|
|
struct ErrorResponse {
|
|
error: String,
|
|
}
|
|
|
|
pub struct ApiError(pub pinakes_core::error::PinakesError);
|
|
|
|
impl IntoResponse for ApiError {
|
|
fn into_response(self) -> Response {
|
|
use pinakes_core::error::PinakesError;
|
|
let (status, message) = match &self.0 {
|
|
PinakesError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
|
|
PinakesError::FileNotFound(path) => {
|
|
// Only expose the file name, not the full path
|
|
let name = path
|
|
.file_name()
|
|
.map(|n| n.to_string_lossy().to_string())
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
tracing::debug!(path = %path.display(), "file not found");
|
|
(StatusCode::NOT_FOUND, format!("file not found: {name}"))
|
|
}
|
|
PinakesError::TagNotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
|
|
PinakesError::CollectionNotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
|
|
PinakesError::DuplicateHash(msg) => (StatusCode::CONFLICT, msg.clone()),
|
|
PinakesError::UnsupportedMediaType(path) => {
|
|
let name = path
|
|
.file_name()
|
|
.map(|n| n.to_string_lossy().to_string())
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
(
|
|
StatusCode::BAD_REQUEST,
|
|
format!("unsupported media type: {name}"),
|
|
)
|
|
}
|
|
PinakesError::SearchParse(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
|
|
PinakesError::InvalidOperation(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
|
|
PinakesError::Authentication(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),
|
|
PinakesError::Authorization(msg) => (StatusCode::FORBIDDEN, msg.clone()),
|
|
PinakesError::Config(_) => {
|
|
tracing::error!(error = %self.0, "configuration error");
|
|
(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
"internal configuration error".to_string(),
|
|
)
|
|
}
|
|
_ => {
|
|
tracing::error!(error = %self.0, "internal server error");
|
|
(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
"internal server error".to_string(),
|
|
)
|
|
}
|
|
};
|
|
|
|
let body = serde_json::to_string(&ErrorResponse {
|
|
error: message.clone(),
|
|
})
|
|
.unwrap_or_else(|_| format!(r#"{{"error":"{}"}}"#, message));
|
|
(status, [("content-type", "application/json")], body).into_response()
|
|
}
|
|
}
|
|
|
|
impl From<pinakes_core::error::PinakesError> for ApiError {
|
|
fn from(e: pinakes_core::error::PinakesError) -> Self {
|
|
Self(e)
|
|
}
|
|
}
|
|
|
|
impl ApiError {
|
|
pub fn bad_request(msg: impl Into<String>) -> Self {
|
|
Self(pinakes_core::error::PinakesError::InvalidOperation(
|
|
msg.into(),
|
|
))
|
|
}
|
|
|
|
pub fn not_found(msg: impl Into<String>) -> Self {
|
|
Self(pinakes_core::error::PinakesError::NotFound(msg.into()))
|
|
}
|
|
|
|
pub fn internal(msg: impl Into<String>) -> Self {
|
|
Self(pinakes_core::error::PinakesError::Database(msg.into()))
|
|
}
|
|
|
|
pub fn forbidden(msg: impl Into<String>) -> Self {
|
|
Self(pinakes_core::error::PinakesError::Authorization(msg.into()))
|
|
}
|
|
|
|
pub fn unauthorized(msg: impl Into<String>) -> Self {
|
|
Self(pinakes_core::error::PinakesError::Authentication(
|
|
msg.into(),
|
|
))
|
|
}
|
|
}
|
|
|
|
pub type ApiResult<T> = Result<T, ApiError>;
|