Skip to main content

systemprompt_api/error/
mod.rs

1//! Entry-local HTTP error type for non-OAuth API routes.
2//!
3//! Handlers return `Result<_, ApiHttpError>` and propagate domain, repository,
4//! and service errors with `?`. The variant-to-HTTP-status mapping lives once,
5//! in the `conversions` submodule's `From` impls, so `domain/*` never
6//! references the HTTP envelope and the boundary decides the status code from
7//! the error variant rather than at each call site.
8//!
9//! The wire shape is the shared [`ApiError`] JSON envelope; [`ApiHttpError`] is
10//! a thin entry-local newtype whose only reason to exist is the orphan rule —
11//! `impl From<DomainError> for ApiError` is forbidden in this crate (both types
12//! are foreign), so a local target type is required to obtain bare `?`.
13//! `into_response` delegates to `ApiError`, which logs exactly once by status
14//! class.
15
16mod conversions;
17
18use axum::response::{IntoResponse, Response};
19use systemprompt_models::api::ApiError;
20
21#[derive(Debug)]
22pub struct ApiHttpError(ApiError);
23
24impl ApiHttpError {
25    pub fn not_found(message: impl Into<String>) -> Self {
26        Self(ApiError::not_found(message))
27    }
28
29    pub fn bad_request(message: impl Into<String>) -> Self {
30        Self(ApiError::bad_request(message))
31    }
32
33    pub fn forbidden(message: impl Into<String>) -> Self {
34        Self(ApiError::forbidden(message))
35    }
36
37    pub fn internal_error(message: impl Into<String>) -> Self {
38        Self(ApiError::internal_error(message))
39    }
40
41    pub fn into_inner(self) -> ApiError {
42        self.0
43    }
44}
45
46impl From<ApiError> for ApiHttpError {
47    fn from(error: ApiError) -> Self {
48        Self(error)
49    }
50}
51
52impl IntoResponse for ApiHttpError {
53    fn into_response(self) -> Response {
54        self.0.into_response()
55    }
56}