Skip to main content

af_web/
error.rs

1//! JSON error envelope. Port of the reusable shape of `agent_core/api/errors.py`.
2//!
3//! Every handler returns `Result<T, ApiError>`; the error renders as a stable
4//! JSON body `{ "error": "...", "request_id": "..." }` with the right status.
5//! Carrying the request id back to the client is the Error-Localization rule on
6//! the wire — a user-reported failure is greppable by id.
7
8use axum::http::StatusCode;
9use axum::response::{IntoResponse, Response};
10use axum::Json;
11use serde_json::json;
12
13/// A handler error with an HTTP status and a user-safe message.
14#[derive(Debug, Clone)]
15pub struct ApiError {
16    pub status: StatusCode,
17    pub message: String,
18    pub request_id: Option<String>,
19}
20
21impl ApiError {
22    pub fn new(status: StatusCode, message: impl Into<String>) -> Self {
23        Self {
24            status,
25            message: message.into(),
26            request_id: None,
27        }
28    }
29
30    pub fn bad_request(message: impl Into<String>) -> Self {
31        Self::new(StatusCode::BAD_REQUEST, message)
32    }
33    pub fn not_found(message: impl Into<String>) -> Self {
34        Self::new(StatusCode::NOT_FOUND, message)
35    }
36    pub fn internal(message: impl Into<String>) -> Self {
37        Self::new(StatusCode::INTERNAL_SERVER_ERROR, message)
38    }
39
40    /// Attach the request id (the latency/request-id middleware sets one).
41    pub fn with_request_id(mut self, id: impl Into<String>) -> Self {
42        self.request_id = Some(id.into());
43        self
44    }
45}
46
47impl IntoResponse for ApiError {
48    fn into_response(self) -> Response {
49        let body = json!({
50            "error": self.message,
51            "request_id": self.request_id,
52        });
53        (self.status, Json(body)).into_response()
54    }
55}