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