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