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//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use systemprompt_agent::{AgentError, ProtocolError};
12use systemprompt_marketplace::MarketplaceError;
13use systemprompt_models::api::ApiError;
14use systemprompt_models::errors::ServiceError;
15use systemprompt_models::execution::ContextExtractionError;
16use systemprompt_oauth::OauthError;
17use systemprompt_traits::RepositoryError;
18use systemprompt_users::UserError;
19
20use super::ApiHttpError;
21
22impl From<RepositoryError> for ApiHttpError {
23    fn from(err: RepositoryError) -> Self {
24        Self(ApiError::from(err))
25    }
26}
27
28impl From<ServiceError> for ApiHttpError {
29    fn from(err: ServiceError) -> Self {
30        Self(ApiError::from(err))
31    }
32}
33
34impl From<AgentError> for ApiHttpError {
35    fn from(err: AgentError) -> Self {
36        let api = match err {
37            AgentError::NotFound(msg) => ApiError::not_found(msg),
38            AgentError::Validation(msg)
39            | AgentError::Protocol(ProtocolError::ValidationFailed(msg)) => {
40                ApiError::bad_request(msg)
41            },
42            other => ApiError::internal_error(other.to_string()),
43        };
44        Self(api)
45    }
46}
47
48impl From<MarketplaceError> for ApiHttpError {
49    fn from(err: MarketplaceError) -> Self {
50        let api = match &err {
51            MarketplaceError::NotFound(_) | MarketplaceError::NoDefault => {
52                ApiError::not_found(err.to_string())
53            },
54            MarketplaceError::Validation(_) => ApiError::bad_request(err.to_string()),
55            MarketplaceError::Catalog(_)
56            | MarketplaceError::Signing(_)
57            | MarketplaceError::Filter(_) => ApiError::internal_error(err.to_string()),
58        };
59        Self(api)
60    }
61}
62
63impl From<UserError> for ApiHttpError {
64    fn from(err: UserError) -> Self {
65        let message = err.to_string();
66        let api = match err {
67            UserError::Repository(inner) => ApiError::from(RepositoryError::from(inner)),
68            UserError::NotFound(_) => ApiError::not_found(message),
69            UserError::EmailAlreadyExists(_) => ApiError::conflict(message),
70            UserError::Validation(_)
71            | UserError::InvalidStatus(_)
72            | UserError::InvalidRole(_)
73            | UserError::InvalidRoles(_) => ApiError::bad_request(message),
74            UserError::Pool(_) => ApiError::internal_error(message),
75        };
76        Self(api)
77    }
78}
79
80impl From<OauthError> for ApiHttpError {
81    fn from(err: OauthError) -> Self {
82        let message = err.to_string();
83        let api = match err {
84            OauthError::CodeNotFound(_)
85            | OauthError::TokenNotFound(_)
86            | OauthError::ClientNotFound(_)
87            | OauthError::UserNotFound(_) => ApiError::not_found(message),
88            OauthError::Validation(_) | OauthError::InvalidClientMetadata(_) => {
89                ApiError::bad_request(message)
90            },
91            OauthError::UsernameTaken(_) | OauthError::EmailRegistered(_) => {
92                ApiError::conflict(message)
93            },
94            OauthError::Unauthorized(_)
95            | OauthError::InvalidGrant(_)
96            | OauthError::InvalidClient(_)
97            | OauthError::TokenInvalid(_)
98            | OauthError::TokenAlgMismatch { .. }
99            | OauthError::TokenMissingKid
100            | OauthError::TokenUnknownKid { .. }
101            | OauthError::PkceMismatch(_)
102            | OauthError::Expired(_) => ApiError::unauthorized(message),
103            OauthError::Provider(_)
104            | OauthError::Session(_)
105            | OauthError::WebAuthn(_)
106            | OauthError::RegistrationStateExpired
107            | OauthError::WebAuthnVerificationFailed(_)
108            | OauthError::User(_)
109            | OauthError::Repository(_)
110            | OauthError::DatabaseRepository(_)
111            | OauthError::Config(_)
112            | OauthError::Crypto(_)
113            | OauthError::CimdFetch(_)
114            | OauthError::WebAuthnConfig(_)
115            | OauthError::Internal(_) => ApiError::internal_error(message),
116        };
117        Self(api)
118    }
119}
120
121impl From<ContextExtractionError> for ApiHttpError {
122    fn from(err: ContextExtractionError) -> Self {
123        let message = err.to_string();
124        let api = match err {
125            ContextExtractionError::MissingHeader(_)
126            | ContextExtractionError::MissingAuthHeader
127            | ContextExtractionError::InvalidToken(_)
128            | ContextExtractionError::Revoked
129            | ContextExtractionError::MissingSessionId
130            | ContextExtractionError::MissingUserId => ApiError::unauthorized(message),
131            ContextExtractionError::MissingContextId
132            | ContextExtractionError::InvalidHeaderValue { .. }
133            | ContextExtractionError::InvalidUserId(_) => ApiError::bad_request(message),
134            ContextExtractionError::ForbiddenHeader { .. } => ApiError::forbidden(message),
135            ContextExtractionError::UserNotFound(_) => ApiError::not_found(message),
136            ContextExtractionError::DatabaseError { .. } => ApiError::internal_error(message),
137        };
138        Self(api)
139    }
140}