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