systemprompt_cloud/error/
mod.rs1use 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#[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}