1use alien_error::AlienErrorData;
2use serde::{Deserialize, Serialize};
3
4use crate::{Platform, ResourceType};
5
6#[derive(Debug, Clone, AlienErrorData, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub enum ErrorData {
10 #[error(
12 code = "GENERIC_ERROR",
13 message = "{message}",
14 retryable = "true",
15 internal = "false"
16 )]
17 GenericError {
18 message: String,
20 },
21
22 #[error(
24 code = "UNEXPECTED_RESOURCE_TYPE",
25 message = "Unexpected resource type for resource '{resource_id}': expected {expected}, but got {actual}",
26 retryable = "false",
27 internal = "false"
28 )]
29 UnexpectedResourceType {
30 resource_id: String,
31 expected: ResourceType,
32 actual: ResourceType,
33 },
34
35 #[error(
37 code = "INVALID_RESOURCE_UPDATE",
38 message = "Resource '{resource_id}' cannot be updated: {reason}",
39 retryable = "false",
40 internal = "false"
41 )]
42 InvalidResourceUpdate { resource_id: String, reason: String },
43
44 #[error(
46 code = "WORKER_TIMEOUT_INVALID",
47 message = "Worker timeoutSeconds must be between 1 and {max_timeout_seconds} seconds, got {timeout_seconds}",
48 retryable = "false",
49 internal = "false",
50 http_status_code = 400
51 )]
52 WorkerTimeoutInvalid {
53 timeout_seconds: u32,
54 max_timeout_seconds: u32,
55 },
56
57 #[error(
59 code = "RESOURCE_HAS_NO_OUTPUTS",
60 message = "Resource '{resource_id}' has no outputs",
61 retryable = "true",
62 internal = "false"
63 )]
64 ResourceHasNoOutputs { resource_id: String },
65
66 #[error(
68 code = "RESOURCE_NOT_FOUND",
69 message = "Resource '{resource_id}' not found in stack state. Available resources: {available_resources:?}",
70 retryable = "false",
71 internal = "false",
72 http_status_code = 404
73 )]
74 ResourceNotFound {
75 resource_id: String,
76 available_resources: Vec<String>,
77 },
78
79 #[error(
81 code = "BINDING_CONFIG_INVALID",
82 message = "Invalid binding configuration for '{binding_name}': {reason}",
83 retryable = "false",
84 internal = "false"
85 )]
86 BindingConfigInvalid {
87 binding_name: String,
88 reason: String,
89 },
90
91 #[error(
93 code = "BINDING_ENV_VAR_MISSING",
94 message = "Missing environment variable '{env_var}' for binding '{binding_name}'",
95 retryable = "false",
96 internal = "false"
97 )]
98 BindingEnvVarMissing {
99 binding_name: String,
100 env_var: String,
101 },
102
103 #[error(
105 code = "BINDING_JSON_PARSE_FAILED",
106 message = "Failed to parse binding JSON for '{binding_name}': {reason}",
107 retryable = "false",
108 internal = "false"
109 )]
110 BindingJsonParseFailed {
111 binding_name: String,
112 reason: String,
113 },
114
115 #[error(
117 code = "UNEXPECTED_RESOURCE_STATUS_COMBINATION",
118 message = "Unexpected resource status combination during {operation}: {resource_statuses:?}",
119 retryable = "false",
120 internal = "true"
121 )]
122 UnexpectedResourceStatusCombination {
123 resource_statuses: Vec<String>,
124 operation: String,
125 },
126
127 #[error(
129 code = "EXTERNAL_BINDING_TYPE_MISMATCH",
130 message = "External binding type mismatch for resource '{resource_id}': expected {expected}, got {actual}",
131 retryable = "false",
132 internal = "false"
133 )]
134 ExternalBindingTypeMismatch {
135 resource_id: String,
136 expected: String,
137 actual: String,
138 },
139
140 #[error(
143 code = "PRESIGNED_REQUEST_EXPIRED",
144 message = "Presigned request for '{path}' expired at {expired_at}",
145 retryable = "false",
146 internal = "false"
147 )]
148 PresignedRequestExpired {
149 path: String,
150 expired_at: chrono::DateTime<chrono::Utc>,
151 },
152
153 #[error(
155 code = "HTTP_REQUEST_FAILED",
156 message = "HTTP {method} request to '{url}' failed",
157 retryable = "true",
158 internal = "false"
159 )]
160 HttpRequestFailed { url: String, method: String },
161
162 #[error(
164 code = "OPERATION_NOT_SUPPORTED",
165 message = "Operation '{operation}' not supported: {reason}",
166 retryable = "false",
167 internal = "false"
168 )]
169 OperationNotSupported { operation: String, reason: String },
170
171 #[error(
173 code = "LOCAL_FILESYSTEM_ERROR",
174 message = "Local filesystem error for '{path}' during {operation}",
175 retryable = "false",
176 internal = "false"
177 )]
178 LocalFilesystemError { path: String, operation: String },
179
180 #[error(
182 code = "FEATURE_NOT_ENABLED",
183 message = "Feature '{feature}' is not enabled",
184 retryable = "false",
185 internal = "false"
186 )]
187 FeatureNotEnabled { feature: String },
188
189 #[error(
191 code = "SANDBOX_CAPABILITY_UNSUPPORTED",
192 message = "Sandbox capability '{capability}' is not supported on {platform}",
193 retryable = "false",
194 internal = "false",
195 http_status_code = 400
196 )]
197 SandboxCapabilityUnsupported {
198 capability: String,
200 platform: String,
202 },
203
204 #[error(
206 code = "SANDBOX_PLATFORM_UNSUPPORTED",
207 message = "Sandbox is not available on {platform}",
208 retryable = "false",
209 internal = "false",
210 http_status_code = 400
211 )]
212 SandboxPlatformUnsupported {
213 platform: String,
215 },
216
217 #[error(
219 code = "SANDBOX_CAPABILITY_REFUSED",
220 message = "Sandbox capability refused: {reason}",
221 retryable = "false",
222 internal = "false",
223 http_status_code = 403
224 )]
225 SandboxCapabilityRefused {
226 reason: String,
228 },
229
230 #[error(
232 code = "SANDBOX_LIMIT_INVALID",
233 message = "Sandbox '{resource_id}' has an invalid {field} limit '{value}': {reason}",
234 retryable = "false",
235 internal = "false",
236 http_status_code = 400
237 )]
238 SandboxLimitInvalid {
239 resource_id: String,
241 field: String,
243 value: String,
245 reason: String,
247 },
248
249 #[error(
252 code = "INVALID_ENVELOPE",
253 message = "Invalid Commands envelope: {message}",
254 retryable = "false",
255 internal = "false"
256 )]
257 InvalidEnvelope {
258 message: String,
259 field: Option<String>,
260 },
261
262 #[error(
265 code = "COMMAND_TOKEN_MISSING",
266 message = "Deployment has command-enabled resource '{resource_id}' but no deployment token to authenticate it: {reason}",
267 retryable = "false",
268 internal = "false"
269 )]
270 CommandTokenMissing { resource_id: String, reason: String },
271
272 #[error(
274 code = "PUBLIC_URL_INVALID",
275 message = "Invalid public URL for resource '{resource_id}': {reason}",
276 retryable = "false",
277 internal = "false",
278 http_status_code = 400
279 )]
280 PublicUrlInvalid { resource_id: String, reason: String },
281
282 #[error(
284 code = "JSON_SERIALIZATION_FAILED",
285 message = "Failed to serialize JSON: {reason}",
286 retryable = "false",
287 internal = "false"
288 )]
289 JsonSerializationFailed { reason: String },
290
291 #[error(
293 code = "JSON_DESERIALIZATION_FAILED",
294 message = "Failed to deserialize JSON: {reason}",
295 retryable = "false",
296 internal = "false"
297 )]
298 JsonDeserializationFailed { reason: String },
299
300 #[error(
302 code = "TEMPLATE_SERIALIZATION_FAILED",
303 message = "Failed to serialize {format} template: {reason}",
304 retryable = "false",
305 internal = "true"
306 )]
307 TemplateSerializationFailed { format: String, reason: String },
308
309 #[error(
311 code = "IMPORT_DATA_INVALID",
312 message = "Invalid ImportData for resource '{resource_id}' ({resource_type}) on {platform}",
313 retryable = "false",
314 internal = "false",
315 http_status_code = 400
316 )]
317 ImportDataInvalid {
318 resource_id: String,
319 resource_type: ResourceType,
320 platform: Platform,
321 },
322
323 #[error(
325 code = "IMPORT_REGISTRATION_MISSING",
326 message = "Missing {registration_kind} registration for resource type '{resource_type}' on {platform}",
327 retryable = "false",
328 internal = "true"
329 )]
330 ImportRegistrationMissing {
331 resource_type: ResourceType,
332 platform: Platform,
333 registration_kind: String,
334 },
335
336 #[error(
338 code = "IMPORT_SCHEMA_SERIALIZATION_FAILED",
339 message = "Failed to serialize ImportData schema",
340 retryable = "false",
341 internal = "true"
342 )]
343 ImportSchemaSerializationFailed,
344}
345
346pub type Result<T> = alien_error::Result<T, ErrorData>;