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 login")]
25    NotAuthenticated,
26
27    #[error("Token expired.\n\nRun: systemprompt cloud login")]
28    TokenExpired,
29
30    #[error("JWT decode error")]
31    JwtDecode,
32
33    #[error("Credentials file corrupted.\n\nRun: systemprompt cloud login")]
34    CredentialsCorrupted {
35        #[source]
36        source: serde_json::Error,
37    },
38
39    #[error("Tenants not synced.\n\nRun: systemprompt cloud login")]
40    TenantsNotSynced,
41
42    #[error("Tenants store corrupted.\n\nRun: systemprompt cloud login")]
43    TenantsStoreCorrupted {
44        #[source]
45        source: serde_json::Error,
46    },
47
48    #[error("Tenants store invalid: {message}")]
49    TenantsStoreInvalid { message: String },
50
51    #[error("API error: {message}")]
52    ApiError { message: String },
53
54    #[error(transparent)]
55    Network(#[from] reqwest::Error),
56
57    #[error(transparent)]
58    Io(#[from] std::io::Error),
59
60    #[error(transparent)]
61    Json(#[from] serde_json::Error),
62
63    #[error("Cloud API validation failed: {message}")]
64    ApiValidationFailed { message: String },
65
66    #[error("Cloud credentials file invalid: {message}")]
67    InvalidCredentials { message: String },
68
69    #[error("Cloud credentials file not found: {path}")]
70    CredentialsFileNotFound { path: String },
71
72    #[error("Credentials not initialized")]
73    CredentialsNotInitialized,
74
75    #[error("Credentials already initialized")]
76    CredentialsAlreadyInitialized,
77
78    #[error(
79        "Session file version mismatch: expected {min}-{max}, got {actual}. Delete {path} and \
80         retry."
81    )]
82    SessionVersionMismatch {
83        min: u32,
84        max: u32,
85        actual: u32,
86        path: String,
87    },
88
89    #[error("OAuth flow failed: {message}")]
90    OAuthFlow { message: String },
91
92    #[error("Checkout flow failed: {message}")]
93    CheckoutFlow { message: String },
94
95    #[error("SSE stream error: {message}")]
96    SseStream { message: String },
97
98    #[error("Provisioning failed: {message}")]
99    ProvisioningFailed { message: String },
100
101    #[error("{message}")]
102    Deploy {
103        message: String,
104        #[source]
105        source: Option<Box<dyn std::error::Error + Send + Sync>>,
106    },
107
108    #[error("{message}")]
109    Dockerfile { message: String },
110
111    #[error("{message}")]
112    Docker {
113        message: String,
114        #[source]
115        source: Option<Box<dyn std::error::Error + Send + Sync>>,
116    },
117
118    #[error("Authentication failed. Please run 'systemprompt cloud login' again.")]
119    Unauthorized,
120
121    #[error("Request failed with status {status}: {body}")]
122    HttpStatus { status: u16, body: String },
123
124    #[error("{message}")]
125    Other { message: String },
126}
127
128impl CloudError {
129    pub fn other(message: impl Into<String>) -> Self {
130        Self::Other {
131            message: message.into(),
132        }
133    }
134
135    pub fn deploy(message: impl Into<String>) -> Self {
136        Self::Deploy {
137            message: message.into(),
138            source: None,
139        }
140    }
141
142    pub fn deploy_with(
143        message: impl Into<String>,
144        source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
145    ) -> Self {
146        Self::Deploy {
147            message: message.into(),
148            source: Some(source.into()),
149        }
150    }
151
152    pub fn dockerfile(message: impl Into<String>) -> Self {
153        Self::Dockerfile {
154            message: message.into(),
155        }
156    }
157
158    pub fn docker(message: impl Into<String>) -> Self {
159        Self::Docker {
160            message: message.into(),
161            source: None,
162        }
163    }
164
165    pub fn docker_with(
166        message: impl Into<String>,
167        source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
168    ) -> Self {
169        Self::Docker {
170            message: message.into(),
171            source: Some(source.into()),
172        }
173    }
174
175    pub const fn is_missing_credentials_file(&self) -> bool {
176        matches!(self, Self::CredentialsFileNotFound { .. })
177    }
178
179    pub const fn is_local_mode_recoverable(&self) -> bool {
180        matches!(
181            self,
182            Self::CredentialsFileNotFound { .. }
183                | Self::TokenExpired
184                | Self::ApiValidationFailed { .. }
185        )
186    }
187}