Skip to main content

systemprompt_api/error/
conversions.rs

1//! `From` impls mapping domain, repository, and service errors onto
2//! [`ApiHttpError`], keeping the variant-to-HTTP-status mapping in one place so
3//! non-OAuth handlers use `?`. `RepositoryError` and `ServiceError` already
4//! classify into [`ApiError`] in `systemprompt-models`; those impls are reused
5//! here. The umbrella domain errors are classified by variant so that, e.g., a
6//! repository failure surfaces as 500 while a missing entity surfaces as 404.
7
8use systemprompt_agent::{AgentError, ProtocolError};
9use systemprompt_marketplace::MarketplaceError;
10use systemprompt_models::api::ApiError;
11use systemprompt_models::errors::ServiceError;
12use systemprompt_models::execution::ContextExtractionError;
13use systemprompt_oauth::OauthError;
14use systemprompt_traits::RepositoryError;
15use systemprompt_users::UserError;
16
17use super::ApiHttpError;
18
19impl From<RepositoryError> for ApiHttpError {
20    fn from(err: RepositoryError) -> Self {
21        Self(ApiError::from(err))
22    }
23}
24
25impl From<ServiceError> for ApiHttpError {
26    fn from(err: ServiceError) -> Self {
27        Self(ApiError::from(err))
28    }
29}
30
31impl From<AgentError> for ApiHttpError {
32    fn from(err: AgentError) -> Self {
33        let api = match err {
34            AgentError::NotFound(msg) => ApiError::not_found(msg),
35            AgentError::Validation(msg)
36            | AgentError::Protocol(ProtocolError::ValidationFailed(msg)) => {
37                ApiError::bad_request(msg)
38            },
39            other => ApiError::internal_error(other.to_string()),
40        };
41        Self(api)
42    }
43}
44
45impl From<MarketplaceError> for ApiHttpError {
46    fn from(err: MarketplaceError) -> Self {
47        let api = match &err {
48            MarketplaceError::NotFound(_) | MarketplaceError::NoDefault => {
49                ApiError::not_found(err.to_string())
50            },
51            MarketplaceError::Validation(_) => ApiError::bad_request(err.to_string()),
52            MarketplaceError::Catalog(_)
53            | MarketplaceError::Signing(_)
54            | MarketplaceError::Filter(_) => ApiError::internal_error(err.to_string()),
55        };
56        Self(api)
57    }
58}
59
60impl From<UserError> for ApiHttpError {
61    fn from(err: UserError) -> Self {
62        let message = err.to_string();
63        let api = match err {
64            UserError::Repository(inner) => ApiError::from(RepositoryError::from(inner)),
65            UserError::NotFound(_) => ApiError::not_found(message),
66            UserError::EmailAlreadyExists(_) => ApiError::conflict(message),
67            UserError::Validation(_)
68            | UserError::InvalidStatus(_)
69            | UserError::InvalidRole(_)
70            | UserError::InvalidRoles(_) => ApiError::bad_request(message),
71            UserError::Pool(_) => ApiError::internal_error(message),
72        };
73        Self(api)
74    }
75}
76
77impl From<OauthError> for ApiHttpError {
78    fn from(err: OauthError) -> Self {
79        let message = err.to_string();
80        let api = match err {
81            OauthError::CodeNotFound(_)
82            | OauthError::TokenNotFound(_)
83            | OauthError::ClientNotFound(_)
84            | OauthError::UserNotFound(_) => ApiError::not_found(message),
85            OauthError::Validation(_) | OauthError::InvalidClientMetadata(_) => {
86                ApiError::bad_request(message)
87            },
88            OauthError::UsernameTaken(_) | OauthError::EmailRegistered(_) => {
89                ApiError::conflict(message)
90            },
91            OauthError::Unauthorized(_)
92            | OauthError::InvalidGrant(_)
93            | OauthError::InvalidClient(_)
94            | OauthError::TokenInvalid(_)
95            | OauthError::TokenAlgMismatch { .. }
96            | OauthError::TokenMissingKid
97            | OauthError::TokenUnknownKid { .. }
98            | OauthError::PkceMismatch(_)
99            | OauthError::Expired(_) => ApiError::unauthorized(message),
100            OauthError::Provider(_)
101            | OauthError::Session(_)
102            | OauthError::WebAuthn(_)
103            | OauthError::RegistrationStateExpired
104            | OauthError::WebAuthnVerificationFailed(_)
105            | OauthError::User(_)
106            | OauthError::Repository(_)
107            | OauthError::DatabaseRepository(_)
108            | OauthError::Config(_)
109            | OauthError::Crypto(_)
110            | OauthError::CimdFetch(_)
111            | OauthError::WebAuthnConfig(_)
112            | OauthError::Internal(_) => ApiError::internal_error(message),
113        };
114        Self(api)
115    }
116}
117
118impl From<ContextExtractionError> for ApiHttpError {
119    fn from(err: ContextExtractionError) -> Self {
120        let message = err.to_string();
121        let api = match err {
122            ContextExtractionError::MissingHeader(_)
123            | ContextExtractionError::MissingAuthHeader
124            | ContextExtractionError::InvalidToken(_)
125            | ContextExtractionError::Revoked
126            | ContextExtractionError::MissingSessionId
127            | ContextExtractionError::MissingUserId => ApiError::unauthorized(message),
128            ContextExtractionError::MissingContextId
129            | ContextExtractionError::InvalidHeaderValue { .. }
130            | ContextExtractionError::InvalidUserId(_) => ApiError::bad_request(message),
131            ContextExtractionError::ForbiddenHeader { .. } => ApiError::forbidden(message),
132            ContextExtractionError::UserNotFound(_) => ApiError::not_found(message),
133            ContextExtractionError::DatabaseError { .. } => ApiError::internal_error(message),
134        };
135        Self(api)
136    }
137}