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    /// The resource exists but has not produced any outputs yet.
45    #[error(
46        code = "RESOURCE_HAS_NO_OUTPUTS",
47        message = "Resource '{resource_id}' has no outputs",
48        retryable = "true",
49        internal = "false"
50    )]
51    ResourceHasNoOutputs { resource_id: String },
52
53    /// Requested resource absent from the stack state.
54    #[error(
55        code = "RESOURCE_NOT_FOUND",
56        message = "Resource '{resource_id}' not found in stack state. Available resources: {available_resources:?}",
57        retryable = "false",
58        internal = "false",
59        http_status_code = 404
60    )]
61    ResourceNotFound {
62        resource_id: String,
63        available_resources: Vec<String>,
64    },
65
66    /// Binding configuration is invalid or missing required fields.
67    #[error(
68        code = "BINDING_CONFIG_INVALID",
69        message = "Invalid binding configuration for '{binding_name}': {reason}",
70        retryable = "false",
71        internal = "false"
72    )]
73    BindingConfigInvalid {
74        binding_name: String,
75        reason: String,
76    },
77
78    /// Environment variable for binding is missing.
79    #[error(
80        code = "BINDING_ENV_VAR_MISSING",
81        message = "Missing environment variable '{env_var}' for binding '{binding_name}'",
82        retryable = "false",
83        internal = "false"
84    )]
85    BindingEnvVarMissing {
86        binding_name: String,
87        env_var: String,
88    },
89
90    /// Failed to parse binding JSON from environment variable.
91    #[error(
92        code = "BINDING_JSON_PARSE_FAILED",
93        message = "Failed to parse binding JSON for '{binding_name}': {reason}",
94        retryable = "false",
95        internal = "false"
96    )]
97    BindingJsonParseFailed {
98        binding_name: String,
99        reason: String,
100    },
101
102    /// Unexpected combination of resource statuses when computing stack status.
103    #[error(
104        code = "UNEXPECTED_RESOURCE_STATUS_COMBINATION",
105        message = "Unexpected resource status combination during {operation}: {resource_statuses:?}",
106        retryable = "false",
107        internal = "true"
108    )]
109    UnexpectedResourceStatusCombination {
110        resource_statuses: Vec<String>,
111        operation: String,
112    },
113
114    /// External binding type does not match the resource type.
115    #[error(
116        code = "EXTERNAL_BINDING_TYPE_MISMATCH",
117        message = "External binding type mismatch for resource '{resource_id}': expected {expected}, got {actual}",
118        retryable = "false",
119        internal = "false"
120    )]
121    ExternalBindingTypeMismatch {
122        resource_id: String,
123        expected: String,
124        actual: String,
125    },
126
127    // Presigned request errors
128    /// Presigned request has expired.
129    #[error(
130        code = "PRESIGNED_REQUEST_EXPIRED",
131        message = "Presigned request for '{path}' expired at {expired_at}",
132        retryable = "false",
133        internal = "false"
134    )]
135    PresignedRequestExpired {
136        path: String,
137        expired_at: chrono::DateTime<chrono::Utc>,
138    },
139
140    /// HTTP request failed.
141    #[error(
142        code = "HTTP_REQUEST_FAILED",
143        message = "HTTP {method} request to '{url}' failed",
144        retryable = "true",
145        internal = "false"
146    )]
147    HttpRequestFailed { url: String, method: String },
148
149    /// Operation not supported.
150    #[error(
151        code = "OPERATION_NOT_SUPPORTED",
152        message = "Operation '{operation}' not supported: {reason}",
153        retryable = "false",
154        internal = "false"
155    )]
156    OperationNotSupported { operation: String, reason: String },
157
158    /// Local filesystem error.
159    #[error(
160        code = "LOCAL_FILESYSTEM_ERROR",
161        message = "Local filesystem error for '{path}' during {operation}",
162        retryable = "false",
163        internal = "false"
164    )]
165    LocalFilesystemError { path: String, operation: String },
166
167    /// Feature not enabled.
168    #[error(
169        code = "FEATURE_NOT_ENABLED",
170        message = "Feature '{feature}' is not enabled",
171        retryable = "false",
172        internal = "false"
173    )]
174    FeatureNotEnabled { feature: String },
175
176    // Commands protocol errors
177    /// Invalid Commands envelope.
178    #[error(
179        code = "INVALID_ENVELOPE",
180        message = "Invalid Commands envelope: {message}",
181        retryable = "false",
182        internal = "false"
183    )]
184    InvalidEnvelope {
185        message: String,
186        field: Option<String>,
187    },
188
189    /// Public URL configuration is invalid.
190    #[error(
191        code = "PUBLIC_URL_INVALID",
192        message = "Invalid public URL for resource '{resource_id}': {reason}",
193        retryable = "false",
194        internal = "false",
195        http_status_code = 400
196    )]
197    PublicUrlInvalid { resource_id: String, reason: String },
198
199    /// JSON serialization failed.
200    #[error(
201        code = "JSON_SERIALIZATION_FAILED",
202        message = "Failed to serialize JSON: {reason}",
203        retryable = "false",
204        internal = "false"
205    )]
206    JsonSerializationFailed { reason: String },
207
208    /// JSON deserialization failed.
209    #[error(
210        code = "JSON_DESERIALIZATION_FAILED",
211        message = "Failed to deserialize JSON: {reason}",
212        retryable = "false",
213        internal = "false"
214    )]
215    JsonDeserializationFailed { reason: String },
216
217    /// Distribution template serialization failed.
218    #[error(
219        code = "TEMPLATE_SERIALIZATION_FAILED",
220        message = "Failed to serialize {format} template: {reason}",
221        retryable = "false",
222        internal = "true"
223    )]
224    TemplateSerializationFailed { format: String, reason: String },
225
226    /// ImportData did not match the registered typed payload.
227    #[error(
228        code = "IMPORT_DATA_INVALID",
229        message = "Invalid ImportData for resource '{resource_id}' ({resource_type}) on {platform}",
230        retryable = "false",
231        internal = "false",
232        http_status_code = 400
233    )]
234    ImportDataInvalid {
235        resource_id: String,
236        resource_type: ResourceType,
237        platform: Platform,
238    },
239
240    /// No registry implementation exists for a resource/platform pair.
241    #[error(
242        code = "IMPORT_REGISTRATION_MISSING",
243        message = "Missing {registration_kind} registration for resource type '{resource_type}' on {platform}",
244        retryable = "false",
245        internal = "true"
246    )]
247    ImportRegistrationMissing {
248        resource_type: ResourceType,
249        platform: Platform,
250        registration_kind: String,
251    },
252
253    /// ImportData schema serialization failed.
254    #[error(
255        code = "IMPORT_SCHEMA_SERIALIZATION_FAILED",
256        message = "Failed to serialize ImportData schema",
257        retryable = "false",
258        internal = "true"
259    )]
260    ImportSchemaSerializationFailed,
261}
262
263pub type Result<T> = alien_error::Result<T, ErrorData>;