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("Credentials not initialized")]
87    CredentialsNotInitialized,
88
89    #[error("Credentials already initialized")]
90    CredentialsAlreadyInitialized,
91
92    #[error(
93        "Session file version mismatch: expected {min}-{max}, got {actual}. Delete {path} and \
94         retry."
95    )]
96    SessionVersionMismatch {
97        min: u32,
98        max: u32,
99        actual: u32,
100        path: String,
101    },
102
103    #[error(
104        "Session store at {path} is stale or corrupt and cannot be parsed.\n\nRun: systemprompt \
105         admin session switch <profile> to reset it"
106    )]
107    SessionStoreCorrupted {
108        path: String,
109        #[source]
110        source: serde_json::Error,
111    },
112
113    #[error("OAuth flow failed: {message}")]
114    OAuthFlow { message: String },
115
116    #[error("{message}")]
117    Deploy {
118        message: String,
119        #[source]
120        source: Option<DeploySource>,
121    },
122
123    #[error("{message}")]
124    Dockerfile { message: String },
125
126    #[error("{message}")]
127    Docker {
128        message: String,
129        #[source]
130        source: Option<std::io::Error>,
131    },
132
133    #[error("Authentication failed. Please run 'systemprompt cloud auth login' again.")]
134    Unauthorized,
135
136    #[error("Request failed with status {status}: {body}")]
137    HttpStatus { status: u16, body: String },
138}
139
140impl CloudError {
141    pub fn deploy(message: impl Into<String>) -> Self {
142        Self::Deploy {
143            message: message.into(),
144            source: None,
145        }
146    }
147
148    pub fn deploy_with(message: impl Into<String>, source: impl Into<DeploySource>) -> Self {
149        Self::Deploy {
150            message: message.into(),
151            source: Some(source.into()),
152        }
153    }
154
155    pub fn dockerfile(message: impl Into<String>) -> Self {
156        Self::Dockerfile {
157            message: message.into(),
158        }
159    }
160
161    pub fn docker(message: impl Into<String>) -> Self {
162        Self::Docker {
163            message: message.into(),
164            source: None,
165        }
166    }
167
168    pub fn docker_with(message: impl Into<String>, source: std::io::Error) -> Self {
169        Self::Docker {
170            message: message.into(),
171            source: Some(source),
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}
188
189/// The underlying failure behind a `CloudError::Deploy`.
190#[derive(Debug, thiserror::Error)]
191pub enum DeploySource {
192    #[error(transparent)]
193    Io(#[from] std::io::Error),
194    #[error(transparent)]
195    Json(#[from] serde_json::Error),
196}