mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-16 23:43:10 +00:00
Updated: Cargo.toml workspace members, CI workflow paths, Dockerfile COPY, AGENTS.md, skill mapping, and doc references. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
67 lines
1.5 KiB
Rust
67 lines
1.5 KiB
Rust
use axum::http::StatusCode;
|
|
use axum::response::{IntoResponse, Response};
|
|
use axum::Json;
|
|
use serde::Serialize;
|
|
|
|
#[derive(Serialize)]
|
|
struct ErrorEntry {
|
|
status: String,
|
|
title: String,
|
|
detail: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct ErrorBody {
|
|
errors: Vec<ErrorEntry>,
|
|
}
|
|
|
|
/// Uniform API error response.
|
|
///
|
|
/// Serializes to `{"errors": [{"status": "4xx", "title": "...", "detail": "..."}]}`.
|
|
pub struct ApiError {
|
|
status: StatusCode,
|
|
detail: String,
|
|
}
|
|
|
|
impl ApiError {
|
|
pub fn new(status: StatusCode, detail: impl Into<String>) -> Self {
|
|
Self {
|
|
status,
|
|
detail: detail.into(),
|
|
}
|
|
}
|
|
|
|
pub fn not_found(detail: impl Into<String>) -> Self {
|
|
Self::new(StatusCode::NOT_FOUND, detail)
|
|
}
|
|
|
|
pub fn bad_request(detail: impl Into<String>) -> Self {
|
|
Self::new(StatusCode::BAD_REQUEST, detail)
|
|
}
|
|
|
|
pub fn unauthorized() -> Self {
|
|
Self::new(StatusCode::UNAUTHORIZED, "Authentication required.")
|
|
}
|
|
|
|
pub fn forbidden() -> Self {
|
|
Self::new(StatusCode::FORBIDDEN, "Access denied.")
|
|
}
|
|
}
|
|
|
|
impl IntoResponse for ApiError {
|
|
fn into_response(self) -> Response {
|
|
let title = self
|
|
.status
|
|
.canonical_reason()
|
|
.unwrap_or("Unknown")
|
|
.to_string();
|
|
let body = ErrorBody {
|
|
errors: vec![ErrorEntry {
|
|
status: self.status.as_u16().to_string(),
|
|
title,
|
|
detail: self.detail,
|
|
}],
|
|
};
|
|
(self.status, Json(body)).into_response()
|
|
}
|
|
}
|