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 = "STACK_INPUT_INVALID",
13 message = "Stack input '{input_id}': {reason}",
14 retryable = "false",
15 internal = "false",
16 http_status_code = 400
17 )]
18 StackInputInvalid { input_id: String, reason: String },
19
20 #[error(
22 code = "GCP_FEDERATION_INVALID",
23 message = "Invalid GCP federation field '{field}': {reason}",
24 retryable = "false",
25 internal = "false",
26 http_status_code = 400
27 )]
28 GcpFederationInvalid { field: String, reason: String },
29
30 #[error(
32 code = "SANDBOX_IMAGE_USER_INVALID",
33 message = "Invalid sandbox image USER '{user}': {reason}",
34 retryable = "false",
35 internal = "false",
36 http_status_code = 400
37 )]
38 SandboxImageUserInvalid { user: String, reason: String },
39 #[error(
41 code = "SANDBOX_EGRESS_POLICY_TOO_LARGE",
42 message = "Sandbox egress policy exceeds {limit}",
43 retryable = "false",
44 internal = "false",
45 http_status_code = 400
46 )]
47 SandboxEgressPolicyTooLarge { limit: String },
48
49 #[error(
51 code = "SANDBOX_EGRESS_INVALID",
52 message = "Invalid sandbox egress target '{target}': {reason}",
53 retryable = "false",
54 internal = "false",
55 http_status_code = 400
56 )]
57 SandboxEgressInvalid { target: String, reason: String },
58
59 #[error(
61 code = "SANDBOX_INTERNET_GATEWAY_MISSING",
62 message = "Sandbox '{sandbox_id}' declares egress 'internet', which routes through the managed sandbox egress gateway, but this stack has no gateway. Prepare the stack with the deployment preflights before rendering its setup template",
63 retryable = "false",
64 internal = "false"
65 )]
66 SandboxInternetGatewayMissing { sandbox_id: String },
67
68 #[error(
70 code = "CONTAINER_RESOURCE_SELECTION_INVALID",
71 message = "Invalid {dimension} selection for container '{resource_id}': {reason}",
72 retryable = "false",
73 internal = "false",
74 http_status_code = 400
75 )]
76 ContainerResourceSelectionInvalid {
77 resource_id: String,
78 dimension: String,
79 reason: String,
80 },
81 #[error(
83 code = "GENERIC_ERROR",
84 message = "{message}",
85 retryable = "true",
86 internal = "false"
87 )]
88 GenericError {
89 message: String,
91 },
92
93 #[error(
95 code = "UNEXPECTED_RESOURCE_TYPE",
96 message = "Unexpected resource type for resource '{resource_id}': expected {expected}, but got {actual}",
97 retryable = "false",
98 internal = "false"
99 )]
100 UnexpectedResourceType {
101 resource_id: String,
102 expected: ResourceType,
103 actual: ResourceType,
104 },
105
106 #[error(
108 code = "INVALID_RESOURCE_UPDATE",
109 message = "Resource '{resource_id}' cannot be updated: {reason}",
110 retryable = "false",
111 internal = "false"
112 )]
113 InvalidResourceUpdate { resource_id: String, reason: String },
114
115 #[error(
117 code = "WORKER_TIMEOUT_INVALID",
118 message = "Worker timeoutSeconds must be between 1 and {max_timeout_seconds} seconds, got {timeout_seconds}",
119 retryable = "false",
120 internal = "false",
121 http_status_code = 400
122 )]
123 WorkerTimeoutInvalid {
124 timeout_seconds: u32,
125 max_timeout_seconds: u32,
126 },
127
128 #[error(
130 code = "RESOURCE_HAS_NO_OUTPUTS",
131 message = "Resource '{resource_id}' has no outputs",
132 retryable = "true",
133 internal = "false"
134 )]
135 ResourceHasNoOutputs { resource_id: String },
136
137 #[error(
139 code = "RESOURCE_NOT_FOUND",
140 message = "Resource '{resource_id}' not found in stack state. Available resources: {available_resources:?}",
141 retryable = "false",
142 internal = "false",
143 http_status_code = 404
144 )]
145 ResourceNotFound {
146 resource_id: String,
147 available_resources: Vec<String>,
148 },
149
150 #[error(
152 code = "BINDING_CONFIG_INVALID",
153 message = "Invalid binding configuration for '{binding_name}': {reason}",
154 retryable = "false",
155 internal = "false"
156 )]
157 BindingConfigInvalid {
158 binding_name: String,
159 reason: String,
160 },
161
162 #[error(
164 code = "BINDING_ENV_VAR_MISSING",
165 message = "Missing environment variable '{env_var}' for binding '{binding_name}'",
166 retryable = "false",
167 internal = "false"
168 )]
169 BindingEnvVarMissing {
170 binding_name: String,
171 env_var: String,
172 },
173
174 #[error(
176 code = "BINDING_JSON_PARSE_FAILED",
177 message = "Failed to parse binding JSON for '{binding_name}': {reason}",
178 retryable = "false",
179 internal = "false"
180 )]
181 BindingJsonParseFailed {
182 binding_name: String,
183 reason: String,
184 },
185
186 #[error(
188 code = "UNEXPECTED_RESOURCE_STATUS_COMBINATION",
189 message = "Unexpected resource status combination during {operation}: {resource_statuses:?}",
190 retryable = "false",
191 internal = "true"
192 )]
193 UnexpectedResourceStatusCombination {
194 resource_statuses: Vec<String>,
195 operation: String,
196 },
197
198 #[error(
200 code = "EXTERNAL_BINDING_TYPE_MISMATCH",
201 message = "External binding type mismatch for resource '{resource_id}': expected {expected}, got {actual}",
202 retryable = "false",
203 internal = "false"
204 )]
205 ExternalBindingTypeMismatch {
206 resource_id: String,
207 expected: String,
208 actual: String,
209 },
210
211 #[error(
214 code = "PRESIGNED_REQUEST_EXPIRED",
215 message = "Presigned request for '{path}' expired at {expired_at}",
216 retryable = "false",
217 internal = "false"
218 )]
219 PresignedRequestExpired {
220 path: String,
221 expired_at: chrono::DateTime<chrono::Utc>,
222 },
223
224 #[error(
226 code = "HTTP_REQUEST_FAILED",
227 message = "HTTP {method} request to '{url}' failed",
228 retryable = "true",
229 internal = "false"
230 )]
231 HttpRequestFailed { url: String, method: String },
232
233 #[error(
235 code = "OPERATION_NOT_SUPPORTED",
236 message = "Operation '{operation}' not supported: {reason}",
237 retryable = "false",
238 internal = "false"
239 )]
240 OperationNotSupported { operation: String, reason: String },
241
242 #[error(
244 code = "LOCAL_FILESYSTEM_ERROR",
245 message = "Local filesystem error for '{path}' during {operation}",
246 retryable = "false",
247 internal = "false"
248 )]
249 LocalFilesystemError { path: String, operation: String },
250
251 #[error(
253 code = "FEATURE_NOT_ENABLED",
254 message = "Feature '{feature}' is not enabled",
255 retryable = "false",
256 internal = "false"
257 )]
258 FeatureNotEnabled { feature: String },
259
260 #[error(
262 code = "AUTOMATIC_SETUP_RESOURCE_UNSUPPORTED",
263 message = "Automatic infrastructure management does not support {resource_type} '{resource_id}' on {platform}; use manual setup",
264 retryable = "false",
265 internal = "false",
266 http_status_code = 400
267 )]
268 AutomaticSetupResourceUnsupported {
269 resource_type: String,
270 resource_id: String,
271 platform: Platform,
272 },
273
274 #[error(
276 code = "SANDBOX_CAPABILITY_UNSUPPORTED",
277 message = "Sandbox capability '{capability}' is not supported on {platform}",
278 retryable = "false",
279 internal = "false",
280 http_status_code = 400
281 )]
282 SandboxCapabilityUnsupported {
283 capability: String,
285 platform: String,
287 },
288
289 #[error(
291 code = "SANDBOX_PLATFORM_UNSUPPORTED",
292 message = "Sandbox is not available on {platform}",
293 retryable = "false",
294 internal = "false",
295 http_status_code = 400
296 )]
297 SandboxPlatformUnsupported {
298 platform: String,
300 },
301
302 #[error(
304 code = "BUNDLE_SIGNATURE_INVALID",
305 message = "Bundle signature invalid: {reason}",
306 retryable = "false",
307 internal = "false",
308 http_status_code = 400
309 )]
310 BundleSignatureInvalid {
311 reason: String,
313 },
314
315 #[error(
317 code = "SANDBOX_CAPABILITY_REFUSED",
318 message = "Sandbox capability refused: {reason}",
319 retryable = "false",
320 internal = "false",
321 http_status_code = 403
322 )]
323 SandboxCapabilityRefused {
324 reason: String,
326 },
327
328 #[error(
330 code = "SANDBOX_LIMIT_INVALID",
331 message = "Sandbox '{resource_id}' has an invalid {field} limit '{value}': {reason}",
332 retryable = "false",
333 internal = "false",
334 http_status_code = 400
335 )]
336 SandboxLimitInvalid {
337 resource_id: String,
339 field: String,
341 value: String,
343 reason: String,
345 },
346
347 #[error(
350 code = "INVALID_ENVELOPE",
351 message = "Invalid Commands envelope: {message}",
352 retryable = "false",
353 internal = "false"
354 )]
355 InvalidEnvelope {
356 message: String,
357 field: Option<String>,
358 },
359
360 #[error(
363 code = "COMMAND_TOKEN_MISSING",
364 message = "Deployment has command-enabled resource '{resource_id}' but no deployment token to authenticate it: {reason}",
365 retryable = "false",
366 internal = "false"
367 )]
368 CommandTokenMissing { resource_id: String, reason: String },
369
370 #[error(
372 code = "PUBLIC_URL_INVALID",
373 message = "Invalid public URL for resource '{resource_id}': {reason}",
374 retryable = "false",
375 internal = "false",
376 http_status_code = 400
377 )]
378 PublicUrlInvalid { resource_id: String, reason: String },
379
380 #[error(
382 code = "JSON_SERIALIZATION_FAILED",
383 message = "Failed to serialize JSON: {reason}",
384 retryable = "false",
385 internal = "false"
386 )]
387 JsonSerializationFailed { reason: String },
388
389 #[error(
391 code = "JSON_DESERIALIZATION_FAILED",
392 message = "Failed to deserialize JSON: {reason}",
393 retryable = "false",
394 internal = "false"
395 )]
396 JsonDeserializationFailed { reason: String },
397
398 #[error(
400 code = "TEMPLATE_SERIALIZATION_FAILED",
401 message = "Failed to serialize {format} template: {reason}",
402 retryable = "false",
403 internal = "true"
404 )]
405 TemplateSerializationFailed { format: String, reason: String },
406
407 #[error(
409 code = "MANAGEMENT_EXTERNAL_ID_INVALID",
410 message = "Management ExternalId must be 32 to 128 ASCII letters and digits (got {length} characters)",
411 retryable = "false",
412 internal = "false",
413 http_status_code = 400
414 )]
415 ManagementExternalIdInvalid {
416 length: usize,
418 },
419
420 #[error(
423 code = "MANAGING_ROLE_ARN_INVALID",
424 message = "Managing role ARN '{arn}' is not an IAM role ARN (arn:<partition>:iam::<account>:role/<name>)",
425 retryable = "false",
426 internal = "false",
427 http_status_code = 400
428 )]
429 ManagingRoleArnInvalid {
430 arn: String,
432 },
433
434 #[error(
436 code = "IMPORT_DATA_INVALID",
437 message = "Invalid ImportData for resource '{resource_id}' ({resource_type}) on {platform}",
438 retryable = "false",
439 internal = "false",
440 http_status_code = 400
441 )]
442 ImportDataInvalid {
443 resource_id: String,
444 resource_type: ResourceType,
445 platform: Platform,
446 },
447
448 #[error(
450 code = "IMPORT_REGISTRATION_MISSING",
451 message = "Missing {registration_kind} registration for resource type '{resource_type}' on {platform}",
452 retryable = "false",
453 internal = "true"
454 )]
455 ImportRegistrationMissing {
456 resource_type: ResourceType,
457 platform: Platform,
458 registration_kind: String,
459 },
460
461 #[error(
463 code = "IMPORT_SCHEMA_SERIALIZATION_FAILED",
464 message = "Failed to serialize ImportData schema",
465 retryable = "false",
466 internal = "true"
467 )]
468 ImportSchemaSerializationFailed,
469}
470
471pub type Result<T> = alien_error::Result<T, ErrorData>;