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