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("Tenants store invalid: {source}")]
49    TenantsStoreInvalid {
50        #[source]
51        source: Box<dyn std::error::Error + Send + Sync + 'static>,
52    },
53
54    #[error("API error ({status}) {code}: {message}")]
55    ApiError {
56        status: u16,
57        code: String,
58        message: String,
59    },
60
61    #[error(transparent)]
62    Network(#[from] reqwest::Error),
63
64    #[error(transparent)]
65    Io(#[from] std::io::Error),
66
67    #[error(transparent)]
68    Json(#[from] serde_json::Error),
69
70    #[error("Cloud API validation failed: {message}: {source}")]
71    ApiValidationFailed {
72        message: String,
73        #[source]
74        source: Box<Self>,
75    },
76
77    #[error("Cloud credentials file invalid: {source}")]
78    InvalidCredentials {
79        #[source]
80        source: Box<Self>,
81    },
82
83    #[error("Cloud credentials file not found: {path}")]
84    CredentialsFileNotFound { path: String },
85
86    #[error(
87        "Session file version mismatch: expected {min}-{max}, got {actual}. Delete {path} and \
88         retry."
89    )]
90    SessionVersionMismatch {
91        min: u32,
92        max: u32,
93        actual: u32,
94        path: String,
95    },
96
97    #[error(
98        "Session store at {path} is stale or corrupt and cannot be parsed.\n\nRun: systemprompt \
99         admin session switch <profile> to reset it"
100    )]
101    SessionStoreCorrupted {
102        path: String,
103        #[source]
104        source: serde_json::Error,
105    },
106
107    #[error("OAuth flow failed: {message}")]
108    OAuthFlow { message: String },
109
110    #[error("{message}")]
111    Deploy {
112        message: String,
113        #[source]
114        source: Option<DeploySource>,
115    },
116
117    #[error("{message}")]
118    Dockerfile { message: String },
119
120    #[error("{message}")]
121    Docker {
122        message: String,
123        #[source]
124        source: Option<std::io::Error>,
125    },
126
127    #[error("Authentication failed. Please run 'systemprompt cloud auth login' again.")]
128    Unauthorized,
129
130    #[error("Request failed with status {status}: {body}")]
131    HttpStatus { status: u16, body: String },
132}
133
134impl CloudError {
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(message: impl Into<String>, source: impl Into<DeploySource>) -> 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(message: impl Into<String>, source: std::io::Error) -> Self {
163        Self::Docker {
164            message: message.into(),
165            source: Some(source),
166        }
167    }
168
169    pub const fn is_missing_credentials_file(&self) -> bool {
170        matches!(self, Self::CredentialsFileNotFound { .. })
171    }
172
173    pub const fn is_local_mode_recoverable(&self) -> bool {
174        matches!(
175            self,
176            Self::CredentialsFileNotFound { .. }
177                | Self::TokenExpired
178                | Self::ApiValidationFailed { .. }
179        )
180    }
181}
182
183/// The underlying failure behind a `CloudError::Deploy`.
184#[derive(Debug, thiserror::Error)]
185pub enum DeploySource {
186    #[error(transparent)]
187    Io(#[from] std::io::Error),
188    #[error(transparent)]
189    Json(#[from] serde_json::Error),
190}