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