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