Skip to main content

alien_core/
error.rs

1use alien_error::AlienErrorData;
2use serde::{Deserialize, Serialize};
3
4use crate::{Platform, ResourceType};
5
6/// Core error data exposed by the `alien-core` crate.
7#[derive(Debug, Clone, AlienErrorData, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub enum ErrorData {
10    /// Federation metadata cannot safely identify the intended trust or target.
11    #[error(
12        code = "GCP_FEDERATION_INVALID",
13        message = "Invalid GCP federation field '{field}': {reason}",
14        retryable = "false",
15        internal = "false",
16        http_status_code = 400
17    )]
18    GcpFederationInvalid { field: String, reason: String },
19
20    /// The image's command identity cannot be resolved safely.
21    #[error(
22        code = "SANDBOX_IMAGE_USER_INVALID",
23        message = "Invalid sandbox image USER '{user}': {reason}",
24        retryable = "false",
25        internal = "false",
26        http_status_code = 400
27    )]
28    SandboxImageUserInvalid { user: String, reason: String },
29    /// Policy exceeds the portable target-count or canonical JSON size limit.
30    #[error(
31        code = "SANDBOX_EGRESS_POLICY_TOO_LARGE",
32        message = "Sandbox egress policy exceeds {limit}",
33        retryable = "false",
34        internal = "false",
35        http_status_code = 400
36    )]
37    SandboxEgressPolicyTooLarge { limit: String },
38
39    /// An egress target cannot be enforced safely.
40    #[error(
41        code = "SANDBOX_EGRESS_INVALID",
42        message = "Invalid sandbox egress target '{target}': {reason}",
43        retryable = "false",
44        internal = "false",
45        http_status_code = 400
46    )]
47    SandboxEgressInvalid { target: String, reason: String },
48
49    /// An AWS internet sandbox reached template rendering without the egress gateway.
50    #[error(
51        code = "SANDBOX_INTERNET_GATEWAY_MISSING",
52        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",
53        retryable = "false",
54        internal = "false"
55    )]
56    SandboxInternetGatewayMissing { sandbox_id: String },
57
58    /// A deployment selected resources outside the release's declared choices.
59    #[error(
60        code = "CONTAINER_RESOURCE_SELECTION_INVALID",
61        message = "Invalid {dimension} selection for container '{resource_id}': {reason}",
62        retryable = "false",
63        internal = "false",
64        http_status_code = 400
65    )]
66    ContainerResourceSelectionInvalid {
67        resource_id: String,
68        dimension: String,
69        reason: String,
70    },
71    /// A fallback error when nothing more specific matches.
72    #[error(
73        code = "GENERIC_ERROR",
74        message = "{message}",
75        retryable = "true",
76        internal = "false"
77    )]
78    GenericError {
79        /// Human-readable description of the error.
80        message: String,
81    },
82
83    /// Resource type mismatch found during stack operations.
84    #[error(
85        code = "UNEXPECTED_RESOURCE_TYPE",
86        message = "Unexpected resource type for resource '{resource_id}': expected {expected}, but got {actual}",
87        retryable = "false",
88        internal = "false"
89    )]
90    UnexpectedResourceType {
91        resource_id: String,
92        expected: ResourceType,
93        actual: ResourceType,
94    },
95
96    /// Attempt to update a resource that does not support updates.
97    #[error(
98        code = "INVALID_RESOURCE_UPDATE",
99        message = "Resource '{resource_id}' cannot be updated: {reason}",
100        retryable = "false",
101        internal = "false"
102    )]
103    InvalidResourceUpdate { resource_id: String, reason: String },
104
105    /// Worker execution timeout is outside the supported range.
106    #[error(
107        code = "WORKER_TIMEOUT_INVALID",
108        message = "Worker timeoutSeconds must be between 1 and {max_timeout_seconds} seconds, got {timeout_seconds}",
109        retryable = "false",
110        internal = "false",
111        http_status_code = 400
112    )]
113    WorkerTimeoutInvalid {
114        timeout_seconds: u32,
115        max_timeout_seconds: u32,
116    },
117
118    /// The resource exists but has not produced any outputs yet.
119    #[error(
120        code = "RESOURCE_HAS_NO_OUTPUTS",
121        message = "Resource '{resource_id}' has no outputs",
122        retryable = "true",
123        internal = "false"
124    )]
125    ResourceHasNoOutputs { resource_id: String },
126
127    /// Requested resource absent from the stack state.
128    #[error(
129        code = "RESOURCE_NOT_FOUND",
130        message = "Resource '{resource_id}' not found in stack state. Available resources: {available_resources:?}",
131        retryable = "false",
132        internal = "false",
133        http_status_code = 404
134    )]
135    ResourceNotFound {
136        resource_id: String,
137        available_resources: Vec<String>,
138    },
139
140    /// Binding configuration is invalid or missing required fields.
141    #[error(
142        code = "BINDING_CONFIG_INVALID",
143        message = "Invalid binding configuration for '{binding_name}': {reason}",
144        retryable = "false",
145        internal = "false"
146    )]
147    BindingConfigInvalid {
148        binding_name: String,
149        reason: String,
150    },
151
152    /// Environment variable for binding is missing.
153    #[error(
154        code = "BINDING_ENV_VAR_MISSING",
155        message = "Missing environment variable '{env_var}' for binding '{binding_name}'",
156        retryable = "false",
157        internal = "false"
158    )]
159    BindingEnvVarMissing {
160        binding_name: String,
161        env_var: String,
162    },
163
164    /// Failed to parse binding JSON from environment variable.
165    #[error(
166        code = "BINDING_JSON_PARSE_FAILED",
167        message = "Failed to parse binding JSON for '{binding_name}': {reason}",
168        retryable = "false",
169        internal = "false"
170    )]
171    BindingJsonParseFailed {
172        binding_name: String,
173        reason: String,
174    },
175
176    /// Unexpected combination of resource statuses when computing stack status.
177    #[error(
178        code = "UNEXPECTED_RESOURCE_STATUS_COMBINATION",
179        message = "Unexpected resource status combination during {operation}: {resource_statuses:?}",
180        retryable = "false",
181        internal = "true"
182    )]
183    UnexpectedResourceStatusCombination {
184        resource_statuses: Vec<String>,
185        operation: String,
186    },
187
188    /// External binding type does not match the resource type.
189    #[error(
190        code = "EXTERNAL_BINDING_TYPE_MISMATCH",
191        message = "External binding type mismatch for resource '{resource_id}': expected {expected}, got {actual}",
192        retryable = "false",
193        internal = "false"
194    )]
195    ExternalBindingTypeMismatch {
196        resource_id: String,
197        expected: String,
198        actual: String,
199    },
200
201    // Presigned request errors
202    /// Presigned request has expired.
203    #[error(
204        code = "PRESIGNED_REQUEST_EXPIRED",
205        message = "Presigned request for '{path}' expired at {expired_at}",
206        retryable = "false",
207        internal = "false"
208    )]
209    PresignedRequestExpired {
210        path: String,
211        expired_at: chrono::DateTime<chrono::Utc>,
212    },
213
214    /// HTTP request failed.
215    #[error(
216        code = "HTTP_REQUEST_FAILED",
217        message = "HTTP {method} request to '{url}' failed",
218        retryable = "true",
219        internal = "false"
220    )]
221    HttpRequestFailed { url: String, method: String },
222
223    /// Operation not supported.
224    #[error(
225        code = "OPERATION_NOT_SUPPORTED",
226        message = "Operation '{operation}' not supported: {reason}",
227        retryable = "false",
228        internal = "false"
229    )]
230    OperationNotSupported { operation: String, reason: String },
231
232    /// Local filesystem error.
233    #[error(
234        code = "LOCAL_FILESYSTEM_ERROR",
235        message = "Local filesystem error for '{path}' during {operation}",
236        retryable = "false",
237        internal = "false"
238    )]
239    LocalFilesystemError { path: String, operation: String },
240
241    /// Feature not enabled.
242    #[error(
243        code = "FEATURE_NOT_ENABLED",
244        message = "Feature '{feature}' is not enabled",
245        retryable = "false",
246        internal = "false"
247    )]
248    FeatureNotEnabled { feature: String },
249
250    /// A resource requires setup capabilities unavailable to automatic management.
251    #[error(
252        code = "AUTOMATIC_SETUP_RESOURCE_UNSUPPORTED",
253        message = "Automatic infrastructure management does not support {resource_type} '{resource_id}' on {platform}; use manual setup",
254        retryable = "false",
255        internal = "false",
256        http_status_code = 400
257    )]
258    AutomaticSetupResourceUnsupported {
259        resource_type: String,
260        resource_id: String,
261        platform: Platform,
262    },
263
264    /// A sandbox capability is unavailable on the target platform.
265    #[error(
266        code = "SANDBOX_CAPABILITY_UNSUPPORTED",
267        message = "Sandbox capability '{capability}' is not supported on {platform}",
268        retryable = "false",
269        internal = "false",
270        http_status_code = 400
271    )]
272    SandboxCapabilityUnsupported {
273        /// The capability that was requested
274        capability: String,
275        /// The platform that does not offer it
276        platform: String,
277    },
278
279    /// The target platform has no sandbox backend at all.
280    #[error(
281        code = "SANDBOX_PLATFORM_UNSUPPORTED",
282        message = "Sandbox is not available on {platform}",
283        retryable = "false",
284        internal = "false",
285        http_status_code = 400
286    )]
287    SandboxPlatformUnsupported {
288        /// The platform with no sandbox backend
289        platform: String,
290    },
291
292    /// A bundle signature or signing key was rejected.
293    #[error(
294        code = "BUNDLE_SIGNATURE_INVALID",
295        message = "Bundle signature invalid: {reason}",
296        retryable = "false",
297        internal = "false",
298        http_status_code = 400
299    )]
300    BundleSignatureInvalid {
301        /// Why the signature or key was rejected
302        reason: String,
303    },
304
305    /// A sandbox capability was refused.
306    #[error(
307        code = "SANDBOX_CAPABILITY_REFUSED",
308        message = "Sandbox capability refused: {reason}",
309        retryable = "false",
310        internal = "false",
311        http_status_code = 403
312    )]
313    SandboxCapabilityRefused {
314        /// Why it was refused. Deliberately coarse — it must not reveal which sandboxes exist.
315        reason: String,
316    },
317
318    /// A sandbox limit is missing or not a valid quantity.
319    #[error(
320        code = "SANDBOX_LIMIT_INVALID",
321        message = "Sandbox '{resource_id}' has an invalid {field} limit '{value}': {reason}",
322        retryable = "false",
323        internal = "false",
324        http_status_code = 400
325    )]
326    SandboxLimitInvalid {
327        /// The sandbox whose limit failed validation
328        resource_id: String,
329        /// Which limit (cpu, memory, disk, maxProcesses)
330        field: String,
331        /// The rejected value
332        value: String,
333        /// Why it was rejected
334        reason: String,
335    },
336
337    // Commands protocol errors
338    /// Invalid Commands envelope.
339    #[error(
340        code = "INVALID_ENVELOPE",
341        message = "Invalid Commands envelope: {message}",
342        retryable = "false",
343        internal = "false"
344    )]
345    InvalidEnvelope {
346        message: String,
347        field: Option<String>,
348    },
349
350    /// A command-enabled resource exists but the deployment has no token to
351    /// authenticate its command transport.
352    #[error(
353        code = "COMMAND_TOKEN_MISSING",
354        message = "Deployment has command-enabled resource '{resource_id}' but no deployment token to authenticate it: {reason}",
355        retryable = "false",
356        internal = "false"
357    )]
358    CommandTokenMissing { resource_id: String, reason: String },
359
360    /// Public URL configuration is invalid.
361    #[error(
362        code = "PUBLIC_URL_INVALID",
363        message = "Invalid public URL for resource '{resource_id}': {reason}",
364        retryable = "false",
365        internal = "false",
366        http_status_code = 400
367    )]
368    PublicUrlInvalid { resource_id: String, reason: String },
369
370    /// JSON serialization failed.
371    #[error(
372        code = "JSON_SERIALIZATION_FAILED",
373        message = "Failed to serialize JSON: {reason}",
374        retryable = "false",
375        internal = "false"
376    )]
377    JsonSerializationFailed { reason: String },
378
379    /// JSON deserialization failed.
380    #[error(
381        code = "JSON_DESERIALIZATION_FAILED",
382        message = "Failed to deserialize JSON: {reason}",
383        retryable = "false",
384        internal = "false"
385    )]
386    JsonDeserializationFailed { reason: String },
387
388    /// Distribution template serialization failed.
389    #[error(
390        code = "TEMPLATE_SERIALIZATION_FAILED",
391        message = "Failed to serialize {format} template: {reason}",
392        retryable = "false",
393        internal = "true"
394    )]
395    TemplateSerializationFailed { format: String, reason: String },
396
397    /// A management ExternalId is not 32 to 128 ASCII letters and digits.
398    #[error(
399        code = "MANAGEMENT_EXTERNAL_ID_INVALID",
400        message = "Management ExternalId must be 32 to 128 ASCII letters and digits (got {length} characters)",
401        retryable = "false",
402        internal = "false",
403        http_status_code = 400
404    )]
405    ManagementExternalIdInvalid {
406        /// Length of the rejected value. The value itself is never echoed.
407        length: usize,
408    },
409
410    /// A role ARN a setup template is told to accept as its managing role is
411    /// not an IAM role ARN.
412    #[error(
413        code = "MANAGING_ROLE_ARN_INVALID",
414        message = "Managing role ARN '{arn}' is not an IAM role ARN (arn:<partition>:iam::<account>:role/<name>)",
415        retryable = "false",
416        internal = "false",
417        http_status_code = 400
418    )]
419    ManagingRoleArnInvalid {
420        /// The rejected ARN.
421        arn: String,
422    },
423
424    /// ImportData did not match the registered typed payload.
425    #[error(
426        code = "IMPORT_DATA_INVALID",
427        message = "Invalid ImportData for resource '{resource_id}' ({resource_type}) on {platform}",
428        retryable = "false",
429        internal = "false",
430        http_status_code = 400
431    )]
432    ImportDataInvalid {
433        resource_id: String,
434        resource_type: ResourceType,
435        platform: Platform,
436    },
437
438    /// No registry implementation exists for a resource/platform pair.
439    #[error(
440        code = "IMPORT_REGISTRATION_MISSING",
441        message = "Missing {registration_kind} registration for resource type '{resource_type}' on {platform}",
442        retryable = "false",
443        internal = "true"
444    )]
445    ImportRegistrationMissing {
446        resource_type: ResourceType,
447        platform: Platform,
448        registration_kind: String,
449    },
450
451    /// ImportData schema serialization failed.
452    #[error(
453        code = "IMPORT_SCHEMA_SERIALIZATION_FAILED",
454        message = "Failed to serialize ImportData schema",
455        retryable = "false",
456        internal = "true"
457    )]
458    ImportSchemaSerializationFailed,
459}
460
461pub type Result<T> = alien_error::Result<T, ErrorData>;