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_loader::BundleError;
13use systemprompt_marketplace::MarketplaceError;
14use systemprompt_models::api::ApiError;
15use systemprompt_models::errors::ServiceError;
16use systemprompt_models::execution::ContextExtractionError;
17use systemprompt_oauth::OauthError;
18use systemprompt_oauth::services::SessionCreationError;
19use systemprompt_traits::RepositoryError;
20use systemprompt_users::UserError;
21
22use super::ApiHttpError;
23
24impl From<RepositoryError> for ApiHttpError {
25    fn from(err: RepositoryError) -> Self {
26        Self(ApiError::from(err))
27    }
28}
29
30impl From<ServiceError> for ApiHttpError {
31    fn from(err: ServiceError) -> Self {
32        Self(ApiError::from(err))
33    }
34}
35
36impl From<AgentError> for ApiHttpError {
37    fn from(err: AgentError) -> Self {
38        let api = match err {
39            AgentError::NotFound(msg) => ApiError::not_found(msg),
40            AgentError::Validation(msg)
41            | AgentError::Protocol(ProtocolError::ValidationFailed(msg)) => {
42                ApiError::bad_request(msg)
43            },
44            other => ApiError::internal_error(other.to_string()),
45        };
46        Self(api)
47    }
48}
49
50impl From<MarketplaceError> for ApiHttpError {
51    fn from(err: MarketplaceError) -> Self {
52        let api = match &err {
53            MarketplaceError::NotFound(_) | MarketplaceError::NoDefault => {
54                ApiError::not_found(err.to_string())
55            },
56            MarketplaceError::Validation(_) => ApiError::bad_request(err.to_string()),
57            MarketplaceError::Catalog(_)
58            | MarketplaceError::Import { .. }
59            | MarketplaceError::Signing(_)
60            | MarketplaceError::Filter(_) => ApiError::internal_error(err.to_string()),
61        };
62        Self(api)
63    }
64}
65
66impl From<UserError> for ApiHttpError {
67    fn from(err: UserError) -> Self {
68        let message = err.to_string();
69        let api = match err {
70            UserError::Repository(inner) => ApiError::from(RepositoryError::from(inner)),
71            UserError::NotFound(_) => ApiError::not_found(message),
72            UserError::EmailAlreadyExists(_) => ApiError::conflict(message),
73            UserError::Validation(_)
74            | UserError::InvalidStatus(_)
75            | UserError::InvalidRole(_)
76            | UserError::InvalidRoles(_) => ApiError::bad_request(message),
77            UserError::Pool(_) => ApiError::internal_error(message),
78        };
79        Self(api)
80    }
81}
82
83impl From<OauthError> for ApiHttpError {
84    fn from(err: OauthError) -> Self {
85        let message = err.to_string();
86        let api = match err {
87            OauthError::CodeNotFound(_)
88            | OauthError::TokenNotFound(_)
89            | OauthError::ClientNotFound(_)
90            | OauthError::UserNotFound(_) => ApiError::not_found(message),
91            OauthError::Validation(_) | OauthError::InvalidClientMetadata(_) => {
92                ApiError::bad_request(message)
93            },
94            OauthError::UsernameTaken(_) | OauthError::EmailRegistered(_) => {
95                ApiError::conflict(message)
96            },
97            OauthError::Unauthorized(_)
98            | OauthError::InvalidGrant(_)
99            | OauthError::InvalidClient(_)
100            | OauthError::TokenInvalid(_)
101            | OauthError::TokenAlgMismatch { .. }
102            | OauthError::TokenMissingKid
103            | OauthError::TokenUnknownKid { .. }
104            | OauthError::PkceMismatch(_)
105            | OauthError::Expired(_) => ApiError::unauthorized(message),
106            OauthError::Provider(_)
107            | OauthError::Session(_)
108            | OauthError::WebAuthn(_)
109            | OauthError::RegistrationStateExpired
110            | OauthError::WebAuthnVerificationFailed(_)
111            | OauthError::User(_)
112            | OauthError::Repository(_)
113            | OauthError::DatabaseRepository(_)
114            | OauthError::Config(_)
115            | OauthError::Crypto(_)
116            | OauthError::CimdFetch(_)
117            | OauthError::WebAuthnConfig(_)
118            | OauthError::Internal(_) => ApiError::internal_error(message),
119        };
120        Self(api)
121    }
122}
123
124impl From<SessionCreationError> for ApiHttpError {
125    fn from(err: SessionCreationError) -> Self {
126        let message = err.to_string();
127        Self(match err {
128            SessionCreationError::UserNotFound { .. } => ApiError::not_found(message),
129            SessionCreationError::Internal(_) => ApiError::internal_error(message),
130        })
131    }
132}
133
134impl From<ContextExtractionError> for ApiHttpError {
135    fn from(err: ContextExtractionError) -> Self {
136        let message = err.to_string();
137        let api = match err {
138            ContextExtractionError::MissingHeader(_)
139            | ContextExtractionError::MissingAuthHeader
140            | ContextExtractionError::InvalidToken(_)
141            | ContextExtractionError::Revoked
142            | ContextExtractionError::MissingSessionId
143            | ContextExtractionError::MissingUserId => ApiError::unauthorized(message),
144            ContextExtractionError::MissingContextId
145            | ContextExtractionError::InvalidHeaderValue { .. }
146            | ContextExtractionError::InvalidUserId(_) => ApiError::bad_request(message),
147            ContextExtractionError::ForbiddenHeader { .. } => ApiError::forbidden(message),
148            ContextExtractionError::UserNotFound(_) => ApiError::not_found(message),
149            ContextExtractionError::DatabaseError { .. } => ApiError::internal_error(message),
150        };
151        Self(api)
152    }
153}
154
155impl From<BundleError> for ApiHttpError {
156    fn from(err: BundleError) -> Self {
157        let message = err.to_string();
158        let api = match err {
159            BundleError::Auth { .. } => ApiError::forbidden(message),
160            BundleError::Verify(_) | BundleError::Ownership { .. } => {
161                ApiError::bad_request(message)
162            },
163            BundleError::SourceMissing { .. } => ApiError::not_found(message),
164            BundleError::Fetch { .. }
165            | BundleError::Extract { .. }
166            | BundleError::Io(_)
167            | BundleError::Policy { .. }
168            | BundleError::TooLarge { .. } => ApiError::internal_error(message),
169        };
170        Self(api)
171    }
172}