Skip to main content

systemprompt_cloud/error/
mod.rs

1//! Public error type for `systemprompt-cloud`.
2//!
3//! All public APIs of this crate return [`CloudError`] (or
4//! [`CloudResult<T>`]) instead of `anyhow::Error`. The enum is
5//! `#[non_exhaustive]` so additional variants can be added in patch
6//! releases without breaking downstream code that performs exhaustive
7//! matching only on the documented variants.
8//!
9//! Upstream errors are composed via `#[from]` (`reqwest`, `std::io`,
10//! `serde_json`) so callers can use `?` transparently.
11//!
12//! Copyright (c) systemprompt.io — Business Source License 1.1.
13//! See <https://systemprompt.io> for licensing details.
14
15use thiserror::Error;
16
17mod messages;
18
19pub type CloudResult<T> = Result<T, CloudError>;
20
21#[derive(Debug, Error)]
22#[non_exhaustive]
23pub enum CloudError {
24    #[error("Authentication required.\n\nRun: systemprompt cloud auth login")]
25    NotAuthenticated,
26
27    #[error("Token expired.\n\nRun: systemprompt cloud auth login")]
28    TokenExpired,
29
30    #[error("JWT decode error")]
31    JwtDecode,
32
33    #[error("Credentials file corrupted.\n\nRun: systemprompt cloud auth login")]
34    CredentialsCorrupted {
35        #[source]
36        source: Box<dyn std::error::Error + Send + Sync + 'static>,
37    },
38
39    #[error("Tenants not synced.\n\nRun: systemprompt cloud auth login")]
40    TenantsNotSynced,
41
42    #[error("Tenants store corrupted.\n\nRun: systemprompt cloud auth login")]
43    TenantsStoreCorrupted {
44        #[source]
45        source: serde_json::Error,
46    },
47
48    #[error("Tenant {tenant_id} is already in the tenant store")]
49    TenantAlreadyExists {
50        tenant_id: systemprompt_identifiers::TenantId,
51    },
52
53    #[error(
54        "Tenant {tenant_id} is managed by systemprompt.io; it is synced from the control plane, \
55         not added or removed locally"
56    )]
57    TenantNotSelfManaged {
58        tenant_id: systemprompt_identifiers::TenantId,
59    },
60
61    #[error("Tenants store invalid: {source}")]
62    TenantsStoreInvalid {
63        #[source]
64        source: Box<dyn std::error::Error + Send + Sync + 'static>,
65    },
66
67    #[error("API error ({status}) {code}: {message}")]
68    ApiError {
69        status: u16,
70        code: String,
71        message: String,
72    },
73
74    #[error(transparent)]
75    Network(#[from] reqwest::Error),
76
77    #[error(transparent)]
78    Io(#[from] std::io::Error),
79
80    #[error(transparent)]
81    Json(#[from] serde_json::Error),
82
83    #[error("Cloud API validation failed: {message}: {source}")]
84    ApiValidationFailed {
85        message: String,
86        #[source]
87        source: Box<Self>,
88    },
89
90    #[error("Cloud credentials file invalid: {source}")]
91    InvalidCredentials {
92        #[source]
93        source: Box<Self>,
94    },
95
96    #[error("Cloud credentials file not found: {path}")]
97    CredentialsFileNotFound { path: String },
98
99    #[error(
100        "Session file version mismatch: expected {min}-{max}, got {actual}. Delete {path} and \
101         retry."
102    )]
103    SessionVersionMismatch {
104        min: u32,
105        max: u32,
106        actual: u32,
107        path: String,
108    },
109
110    #[error(
111        "Session store at {path} is stale or corrupt and cannot be parsed.\n\nRun: systemprompt \
112         admin session switch <profile> to reset it"
113    )]
114    SessionStoreCorrupted {
115        path: String,
116        #[source]
117        source: serde_json::Error,
118    },
119
120    #[error("OAuth flow failed: {message}")]
121    OAuthFlow { message: String },
122
123    #[error("{message}")]
124    Deploy {
125        message: String,
126        #[source]
127        source: Option<DeploySource>,
128    },
129
130    #[error("{message}")]
131    Dockerfile { message: String },
132
133    #[error("{message}")]
134    Docker {
135        message: String,
136        #[source]
137        source: Option<std::io::Error>,
138    },
139
140    #[error("Authentication failed. Please run 'systemprompt cloud auth login' again.")]
141    Unauthorized,
142
143    #[error("Request failed with status {status}: {body}")]
144    HttpStatus { status: u16, body: String },
145
146    #[error(
147        "Provider '{provider}' has no deploy pipeline; deploy a docker_local tenant with \
148         'systemprompt infra services start'"
149    )]
150    ProviderNotDeployable {
151        provider: systemprompt_manifest::HostingProvider,
152    },
153
154    #[error("Profile has no cloud.deploy section; provider '{provider}' deploys through GitOps")]
155    DeployNotConfigured {
156        provider: systemprompt_manifest::HostingProvider,
157    },
158
159    #[error("{message}")]
160    GitOps {
161        message: String,
162        #[source]
163        source: Option<std::io::Error>,
164    },
165
166    #[error("Values file {path} could not be updated")]
167    ValuesFile {
168        path: String,
169        #[source]
170        source: crate::deploy::provider::ValuesFileError,
171    },
172
173    #[error("Tenant at {url} did not become ready within {waited_secs}s (last: {last})")]
174    TenantUnready {
175        url: String,
176        waited_secs: u64,
177        last: String,
178    },
179
180    #[error("Operator sign-in failed: {message}")]
181    OperatorAuth { message: String },
182
183    #[error("Operator issuer endpoint {endpoint} was refused")]
184    OperatorEndpoint {
185        endpoint: String,
186        #[source]
187        source: systemprompt_models::net::OutboundUrlError,
188    },
189
190    #[error("Operator ID token email is not a valid address")]
191    OperatorEmail {
192        #[source]
193        source: systemprompt_identifiers::error::IdValidationError,
194    },
195
196    #[error(
197        "No operator sign-in.\n\nRun: systemprompt cloud auth login --issuer <url> --client-id <id>"
198    )]
199    OperatorNotAuthenticated,
200}
201
202impl CloudError {
203    pub fn deploy(message: impl Into<String>) -> Self {
204        Self::Deploy {
205            message: message.into(),
206            source: None,
207        }
208    }
209
210    pub fn deploy_with(message: impl Into<String>, source: impl Into<DeploySource>) -> Self {
211        Self::Deploy {
212            message: message.into(),
213            source: Some(source.into()),
214        }
215    }
216
217    pub fn dockerfile(message: impl Into<String>) -> Self {
218        Self::Dockerfile {
219            message: message.into(),
220        }
221    }
222
223    pub fn docker(message: impl Into<String>) -> Self {
224        Self::Docker {
225            message: message.into(),
226            source: None,
227        }
228    }
229
230    pub fn docker_with(message: impl Into<String>, source: std::io::Error) -> Self {
231        Self::Docker {
232            message: message.into(),
233            source: Some(source),
234        }
235    }
236
237    pub fn gitops(message: impl Into<String>) -> Self {
238        Self::GitOps {
239            message: message.into(),
240            source: None,
241        }
242    }
243
244    pub fn gitops_with(message: impl Into<String>, source: std::io::Error) -> Self {
245        Self::GitOps {
246            message: message.into(),
247            source: Some(source),
248        }
249    }
250
251    pub fn operator_auth(message: impl Into<String>) -> Self {
252        Self::OperatorAuth {
253            message: message.into(),
254        }
255    }
256
257    pub const fn is_missing_credentials_file(&self) -> bool {
258        matches!(self, Self::CredentialsFileNotFound { .. })
259    }
260
261    pub const fn is_local_mode_recoverable(&self) -> bool {
262        matches!(
263            self,
264            Self::CredentialsFileNotFound { .. }
265                | Self::TokenExpired
266                | Self::ApiValidationFailed { .. }
267        )
268    }
269}
270
271/// The underlying failure behind a `CloudError::Deploy`.
272#[derive(Debug, thiserror::Error)]
273pub enum DeploySource {
274    #[error(transparent)]
275    Io(#[from] std::io::Error),
276    #[error(transparent)]
277    Json(#[from] serde_json::Error),
278}