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