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(
90        "Session store at {path} is stale or corrupt and cannot be parsed.\n\nRun: systemprompt \
91         admin session switch <profile> to reset it"
92    )]
93    SessionStoreCorrupted {
94        path: String,
95        #[source]
96        source: serde_json::Error,
97    },
98
99    #[error("OAuth flow failed: {message}")]
100    OAuthFlow { message: String },
101
102    #[error("{message}")]
103    Deploy {
104        message: String,
105        #[source]
106        source: Option<Box<dyn std::error::Error + Send + Sync>>,
107    },
108
109    #[error("{message}")]
110    Dockerfile { message: String },
111
112    #[error("{message}")]
113    Docker {
114        message: String,
115        #[source]
116        source: Option<Box<dyn std::error::Error + Send + Sync>>,
117    },
118
119    #[error("Authentication failed. Please run 'systemprompt cloud login' again.")]
120    Unauthorized,
121
122    #[error("Request failed with status {status}: {body}")]
123    HttpStatus { status: u16, body: String },
124
125    #[error("{message}")]
126    Other { message: String },
127}
128
129impl CloudError {
130    pub fn other(message: impl Into<String>) -> Self {
131        Self::Other {
132            message: message.into(),
133        }
134    }
135
136    pub fn deploy(message: impl Into<String>) -> Self {
137        Self::Deploy {
138            message: message.into(),
139            source: None,
140        }
141    }
142
143    pub fn deploy_with(
144        message: impl Into<String>,
145        source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
146    ) -> Self {
147        Self::Deploy {
148            message: message.into(),
149            source: Some(source.into()),
150        }
151    }
152
153    pub fn dockerfile(message: impl Into<String>) -> Self {
154        Self::Dockerfile {
155            message: message.into(),
156        }
157    }
158
159    pub fn docker(message: impl Into<String>) -> Self {
160        Self::Docker {
161            message: message.into(),
162            source: None,
163        }
164    }
165
166    pub fn docker_with(
167        message: impl Into<String>,
168        source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
169    ) -> Self {
170        Self::Docker {
171            message: message.into(),
172            source: Some(source.into()),
173        }
174    }
175
176    pub const fn is_missing_credentials_file(&self) -> bool {
177        matches!(self, Self::CredentialsFileNotFound { .. })
178    }
179
180    pub const fn is_local_mode_recoverable(&self) -> bool {
181        matches!(
182            self,
183            Self::CredentialsFileNotFound { .. }
184                | Self::TokenExpired
185                | Self::ApiValidationFailed { .. }
186        )
187    }
188}