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    // Commands protocol errors
190    /// Invalid Commands envelope.
191    #[error(
192        code = "INVALID_ENVELOPE",
193        message = "Invalid Commands envelope: {message}",
194        retryable = "false",
195        internal = "false"
196    )]
197    InvalidEnvelope {
198        message: String,
199        field: Option<String>,
200    },
201
202    /// A command-enabled resource exists but the deployment has no token to
203    /// authenticate its command transport.
204    #[error(
205        code = "COMMAND_TOKEN_MISSING",
206        message = "Deployment has command-enabled resource '{resource_id}' but no deployment token to authenticate it: {reason}",
207        retryable = "false",
208        internal = "false"
209    )]
210    CommandTokenMissing { resource_id: String, reason: String },
211
212    /// Public URL configuration is invalid.
213    #[error(
214        code = "PUBLIC_URL_INVALID",
215        message = "Invalid public URL for resource '{resource_id}': {reason}",
216        retryable = "false",
217        internal = "false",
218        http_status_code = 400
219    )]
220    PublicUrlInvalid { resource_id: String, reason: String },
221
222    /// JSON serialization failed.
223    #[error(
224        code = "JSON_SERIALIZATION_FAILED",
225        message = "Failed to serialize JSON: {reason}",
226        retryable = "false",
227        internal = "false"
228    )]
229    JsonSerializationFailed { reason: String },
230
231    /// JSON deserialization failed.
232    #[error(
233        code = "JSON_DESERIALIZATION_FAILED",
234        message = "Failed to deserialize JSON: {reason}",
235        retryable = "false",
236        internal = "false"
237    )]
238    JsonDeserializationFailed { reason: String },
239
240    /// Distribution template serialization failed.
241    #[error(
242        code = "TEMPLATE_SERIALIZATION_FAILED",
243        message = "Failed to serialize {format} template: {reason}",
244        retryable = "false",
245        internal = "true"
246    )]
247    TemplateSerializationFailed { format: String, reason: String },
248
249    /// ImportData did not match the registered typed payload.
250    #[error(
251        code = "IMPORT_DATA_INVALID",
252        message = "Invalid ImportData for resource '{resource_id}' ({resource_type}) on {platform}",
253        retryable = "false",
254        internal = "false",
255        http_status_code = 400
256    )]
257    ImportDataInvalid {
258        resource_id: String,
259        resource_type: ResourceType,
260        platform: Platform,
261    },
262
263    /// No registry implementation exists for a resource/platform pair.
264    #[error(
265        code = "IMPORT_REGISTRATION_MISSING",
266        message = "Missing {registration_kind} registration for resource type '{resource_type}' on {platform}",
267        retryable = "false",
268        internal = "true"
269    )]
270    ImportRegistrationMissing {
271        resource_type: ResourceType,
272        platform: Platform,
273        registration_kind: String,
274    },
275
276    /// ImportData schema serialization failed.
277    #[error(
278        code = "IMPORT_SCHEMA_SERIALIZATION_FAILED",
279        message = "Failed to serialize ImportData schema",
280        retryable = "false",
281        internal = "true"
282    )]
283    ImportSchemaSerializationFailed,
284}
285
286pub type Result<T> = alien_error::Result<T, ErrorData>;