Skip to main content

alien_bindings/
error.rs

1use alien_error::{AlienError, AlienErrorData, ContextError};
2use serde::{Deserialize, Serialize};
3
4/// Derives the exact `ALIEN_<NAME>_BINDING` environment variable name that would have
5/// configured a binding with the given name — the same derivation `alien-core` uses to
6/// generate binding env vars, so this is guaranteed to match `parse_bindings_from_env`'s
7/// reverse parsing (see `provider.rs`).
8pub fn binding_env_var(binding_name: &str) -> String {
9    alien_core::bindings::binding_env_var_name(binding_name)
10}
11
12/// Errors related to alien-bindings operations.
13#[derive(Debug, Clone, AlienErrorData, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub enum ErrorData {
16    /// Plaintext or context is outside the portable Key binding limits.
17    #[error(
18        code = "KEY_INPUT_INVALID",
19        message = "Key input is invalid: {reason}",
20        retryable = "false",
21        internal = "false",
22        http_status_code = 400
23    )]
24    KeyInputInvalid { reason: String },
25
26    /// Decrypted provider data is not a valid Alien Key frame.
27    #[error(
28        code = "KEY_CIPHERTEXT_INVALID",
29        message = "Key ciphertext is invalid: {reason}",
30        retryable = "false",
31        internal = "false",
32        http_status_code = 400
33    )]
34    KeyCiphertextInvalid { reason: String },
35
36    /// No binding configuration was found for the requested binding name (the
37    /// `ALIEN_<NAME>_BINDING` environment variable was not set).
38    #[error(
39        code = "BINDING_NOT_CONFIGURED",
40        message = "No binding configured for '{binding_name}': environment variable '{env_var}' is not set",
41        retryable = "false",
42        internal = "false",
43        http_status_code = 400
44    )]
45    BindingNotConfigured {
46        /// Name of the binding that was requested
47        binding_name: String,
48        /// The exact environment variable name that would configure this binding
49        env_var: String,
50    },
51
52    /// Binding provider configuration is invalid or missing.
53    #[error(
54        code = "BINDING_CONFIG_INVALID",
55        message = "Binding configuration invalid for binding '{binding_name}' (env var '{env_var}'): {reason}",
56        retryable = "false",
57        internal = "false",
58        http_status_code = 400
59    )]
60    BindingConfigInvalid {
61        /// Name of the binding
62        binding_name: String,
63        /// The exact environment variable name that configures this binding
64        env_var: String,
65        /// Specific reason why the configuration is invalid
66        reason: String,
67    },
68
69    /// Binding configuration named a provider that this build does not support.
70    #[error(
71        code = "UNSUPPORTED_BINDING_PROVIDER",
72        message = "Binding '{binding_name}' (env var '{env_var}') uses unsupported provider '{provider}'",
73        retryable = "false",
74        internal = "false",
75        http_status_code = 501
76    )]
77    UnsupportedBindingProvider {
78        /// Name of the binding
79        binding_name: String,
80        /// The exact environment variable name that configures this binding
81        env_var: String,
82        /// The provider string that is not supported
83        provider: String,
84    },
85
86    /// Storage operation failed due to provider issues.
87    #[error(
88        code = "STORAGE_OPERATION_FAILED",
89        message = "Storage operation failed for binding '{binding_name}': {operation}",
90        retryable = "true",
91        internal = "false",
92        http_status_code = 502
93    )]
94    StorageOperationFailed {
95        /// Name of the storage binding
96        binding_name: String,
97        /// Description of the operation that failed
98        operation: String,
99    },
100
101    /// Storage credentials are missing permission for an operation.
102    #[error(
103        code = "STORAGE_ACCESS_DENIED",
104        message = "Storage access denied for binding '{binding_name}' while attempting to {operation}",
105        retryable = "false",
106        internal = "false",
107        http_status_code = 403,
108        hint = "Check that the customer's Storage connection is active and its Access identity still has the required permissions."
109    )]
110    StorageAccessDenied {
111        /// Name of the storage binding.
112        binding_name: String,
113        /// Provider-independent operation name.
114        operation: String,
115    },
116
117    /// The requested object does not exist.
118    #[error(
119        code = "STORAGE_OBJECT_NOT_FOUND",
120        message = "Storage object not found for binding '{binding_name}' while attempting to {operation}",
121        retryable = "false",
122        internal = "false",
123        http_status_code = 404
124    )]
125    StorageObjectNotFound {
126        /// Name of the storage binding.
127        binding_name: String,
128        /// Provider-independent operation name.
129        operation: String,
130    },
131
132    /// Build operation failed due to provider issues.
133    #[error(
134        code = "BUILD_OPERATION_FAILED",
135        message = "Build operation failed for binding '{binding_name}': {operation}",
136        retryable = "true",
137        internal = "false",
138        http_status_code = 502
139    )]
140    BuildOperationFailed {
141        /// Name of the build binding
142        binding_name: String,
143        /// Description of the operation that failed
144        operation: String,
145    },
146
147    /// Required environment variable is missing or invalid.
148    #[error(
149        code = "ENVIRONMENT_VARIABLE_MISSING",
150        message = "Required environment variable '{variable_name}' is missing",
151        retryable = "false",
152        internal = "false",
153        http_status_code = 500
154    )]
155    EnvironmentVariableMissing {
156        /// Name of the missing environment variable
157        variable_name: String,
158    },
159
160    /// Environment variable has an invalid value.
161    #[error(
162        code = "INVALID_ENVIRONMENT_VARIABLE",
163        message = "Environment variable '{variable_name}' has invalid value '{value}': {reason}",
164        retryable = "false",
165        internal = "false",
166        http_status_code = 500
167    )]
168    InvalidEnvironmentVariable {
169        /// Name of the environment variable
170        variable_name: String,
171        /// The invalid value
172        value: String,
173        /// Reason why the value is invalid
174        reason: String,
175    },
176
177    /// Configuration URL is malformed or invalid.
178    #[error(
179        code = "INVALID_CONFIGURATION_URL",
180        message = "Invalid configuration URL '{url}': {reason}",
181        retryable = "false",
182        internal = "false",
183        http_status_code = 400
184    )]
185    InvalidConfigurationUrl {
186        /// The invalid URL
187        url: String,
188        /// Specific reason why the URL is invalid
189        reason: String,
190    },
191
192    /// Event processing failed due to malformed or unsupported data.
193    #[error(
194        code = "EVENT_PROCESSING_FAILED",
195        message = "Event processing failed for type '{event_type}': {reason}",
196        retryable = "true",
197        internal = "false",
198        http_status_code = 400
199    )]
200    EventProcessingFailed {
201        /// Type of event that failed to process
202        event_type: String,
203        /// Specific reason for the processing failure
204        reason: String,
205    },
206
207    /// gRPC connection failed or became unavailable.
208    #[error(
209        code = "GRPC_CONNECTION_FAILED",
210        message = "gRPC connection failed to endpoint '{endpoint}': {reason}",
211        retryable = "true",
212        internal = "false",
213        http_status_code = 502
214    )]
215    GrpcConnectionFailed {
216        /// The gRPC endpoint that failed to connect
217        endpoint: String,
218        /// Reason for the connection failure
219        reason: String,
220    },
221
222    /// gRPC service unavailable or returned error.
223    #[error(
224        code = "GRPC_SERVICE_UNAVAILABLE",
225        message = "gRPC service '{service}' unavailable at endpoint '{endpoint}': {reason}",
226        retryable = "true",
227        internal = "false",
228        http_status_code = 503
229    )]
230    GrpcServiceUnavailable {
231        /// Name of the gRPC service
232        service: String,
233        /// The gRPC endpoint
234        endpoint: String,
235        /// Reason for service unavailability
236        reason: String,
237    },
238
239    /// gRPC request failed with an error status.
240    #[error(
241        code = "GRPC_REQUEST_FAILED",
242        message = "gRPC request to service '{service}' method '{method}' failed: {details}",
243        retryable = "true",
244        internal = "false",
245        http_status_code = 502
246    )]
247    GrpcRequestFailed {
248        /// Name of the gRPC service
249        service: String,
250        /// Name of the gRPC method
251        method: String,
252        /// Error details from the gRPC status
253        details: String,
254    },
255
256    /// Server failed to bind to the specified address.
257    #[error(
258        code = "SERVER_BIND_FAILED",
259        message = "Failed to bind server to address '{address}': {reason}",
260        retryable = "true",
261        internal = "true",
262        http_status_code = 500
263    )]
264    ServerBindFailed {
265        /// The address that failed to bind
266        address: String,
267        /// Reason for the bind failure
268        reason: String,
269    },
270
271    /// Authentication failed for the configured provider.
272    #[error(
273        code = "AUTHENTICATION_FAILED",
274        message = "Authentication failed for provider '{provider}' and binding '{binding_name}': {reason}",
275        retryable = "true",
276        internal = "false",
277        http_status_code = 401
278    )]
279    AuthenticationFailed {
280        /// Name of the provider (aws, gcp, azure, etc.)
281        provider: String,
282        /// Name of the binding
283        binding_name: String,
284        /// Reason for authentication failure
285        reason: String,
286    },
287
288    /// Operation not supported by the configured provider.
289    #[error(
290        code = "OPERATION_NOT_SUPPORTED",
291        message = "Operation '{operation}' not supported: {reason}",
292        retryable = "false",
293        internal = "false",
294        http_status_code = 501
295    )]
296    OperationNotSupported {
297        /// Name of the unsupported operation
298        operation: String,
299        /// Reason why the operation is not supported
300        reason: String,
301    },
302
303    /// Feature is not enabled in the compiled binary.
304    #[error(
305        code = "FEATURE_NOT_ENABLED",
306        message = "Feature '{feature}' is not enabled in this build",
307        retryable = "false",
308        internal = "false",
309        http_status_code = 501
310    )]
311    FeatureNotEnabled {
312        /// Name of the feature that is not enabled
313        feature: String,
314    },
315
316    /// gRPC call failed.
317    #[error(
318        code = "GRPC_CALL_FAILED",
319        message = "gRPC call to service '{service}' method '{method}' failed: {reason}",
320        retryable = "true",
321        internal = "false",
322        http_status_code = 502
323    )]
324    GrpcCallFailed {
325        /// Name of the gRPC service
326        service: String,
327        /// Name of the gRPC method
328        method: String,
329        /// Reason for the call failure
330        reason: String,
331    },
332
333    /// Deserialization of data failed.
334    #[error(
335        code = "DESERIALIZATION_FAILED",
336        message = "Failed to deserialize {type_name}: {message}",
337        retryable = "false",
338        internal = "false",
339        http_status_code = 400
340    )]
341    DeserializationFailed {
342        /// Human-readable error message
343        message: String,
344        /// Name of the type being deserialized
345        type_name: String,
346    },
347
348    /// Serialization of data failed.
349    #[error(
350        code = "SERIALIZATION_FAILED",
351        message = "Failed to serialize data: {message}",
352        retryable = "false",
353        internal = "false",
354        http_status_code = 500
355    )]
356    SerializationFailed {
357        /// Human-readable error message
358        message: String,
359    },
360
361    /// Response format from provider API is unexpected or missing required fields.
362    #[error(
363        code = "UNEXPECTED_RESPONSE_FORMAT",
364        message = "Unexpected response format from '{provider}' for binding '{binding_name}': missing field '{field}'. Response: {response_json}",
365        retryable = "false",
366        internal = "false",
367        http_status_code = 502
368    )]
369    UnexpectedResponseFormat {
370        /// Name of the provider (aws, gcp, azure, etc.)
371        provider: String,
372        /// Name of the binding
373        binding_name: String,
374        /// Name of the missing or malformed field
375        field: String,
376        /// The full response JSON for debugging
377        response_json: String,
378    },
379
380    /// Cloud platform API error.
381    #[error(
382        code = "CLOUD_PLATFORM_ERROR",
383        message = "Cloud platform error: {message}",
384        retryable = "true",
385        internal = "false",
386        http_status_code = 502
387    )]
388    CloudPlatformError {
389        /// Human-readable description of the error
390        message: String,
391        /// Optional resource ID that was involved in the error
392        resource_id: Option<String>,
393    },
394
395    /// Reading a cloud Postgres binding's password from its secret store failed.
396    ///
397    /// The retry and visibility metadata comes from the provider error being wrapped:
398    /// throttling remains retryable, while permanent provider failures do not become
399    /// retryable merely because they happened during secret resolution.
400    ///
401    /// `secret` is the locator, never the secret value.
402    #[error(
403        code = "POSTGRES_SECRET_RESOLUTION_FAILED",
404        message = "Failed to resolve the password for Postgres binding '{binding_name}' from secret '{secret}': {reason}",
405        retryable = "inherit",
406        internal = "inherit",
407        http_status_code = 502
408    )]
409    PostgresSecretResolutionFailed {
410        /// Name of the Postgres binding whose password could not be resolved
411        binding_name: String,
412        /// The secret locator that was read (an ARN / name / URI — never the value)
413        secret: String,
414        /// What went wrong while reading the secret
415        reason: String,
416    },
417
418    /// A cloud Postgres secret was read, but did not contain a usable password.
419    ///
420    /// Provider responses with missing, empty, or malformed values cannot become valid
421    /// by retrying the same version. `secret` is the locator, never the secret value.
422    #[error(
423        code = "POSTGRES_SECRET_VALUE_INVALID",
424        message = "Secret '{secret}' for Postgres binding '{binding_name}' does not contain a valid password: {reason}",
425        retryable = "false",
426        internal = "false",
427        http_status_code = 502
428    )]
429    PostgresSecretValueInvalid {
430        /// Name of the Postgres binding whose password value was invalid
431        binding_name: String,
432        /// The secret locator that was read (an ARN / name / URI — never the value)
433        secret: String,
434        /// Why the returned value cannot be used as a password
435        reason: String,
436    },
437
438    /// Resource not found in the cloud platform.
439    #[error(
440        code = "RESOURCE_NOT_FOUND",
441        message = "Resource '{resource_id}' not found",
442        retryable = "false",
443        internal = "false",
444        http_status_code = 404
445    )]
446    ResourceNotFound {
447        /// ID of the resource that was not found
448        resource_id: String,
449    },
450
451    /// The requested remote resource does not exist.
452    #[error(
453        code = "REMOTE_RESOURCE_NOT_FOUND",
454        message = "{operation_context}: {resource_type} '{resource_name}' not found",
455        retryable = "false",
456        internal = "false",
457        http_status_code = 404
458    )]
459    RemoteResourceNotFound {
460        /// Context of the operation that failed (e.g., "Failed to get ECR repository details")
461        operation_context: String,
462        /// Type of the resource that was not found
463        resource_type: String,
464        /// Name of the resource that was not found
465        resource_name: String,
466    },
467
468    /// Operation conflicts with current remote resource state.
469    #[error(
470        code = "REMOTE_RESOURCE_CONFLICT",
471        message = "{operation_context}: Conflict with {resource_type} '{resource_name}' - {conflict_reason}",
472        retryable = "true",
473        internal = "false",
474        http_status_code = 409
475    )]
476    RemoteResourceConflict {
477        /// Context of the operation that failed
478        operation_context: String,
479        /// Type of the resource that has a conflict
480        resource_type: String,
481        /// Name of the resource that has a conflict
482        resource_name: String,
483        /// Specific reason for the conflict
484        conflict_reason: String,
485    },
486
487    /// Access denied due to insufficient permissions.
488    #[error(
489        code = "REMOTE_ACCESS_DENIED",
490        message = "{operation_context}: Access denied to {resource_type} '{resource_name}'",
491        retryable = "true",
492        internal = "false",
493        http_status_code = 403
494    )]
495    RemoteAccessDenied {
496        /// Context of the operation that failed
497        operation_context: String,
498        /// Type of the resource access was denied to
499        resource_type: String,
500        /// Name of the resource access was denied to
501        resource_name: String,
502    },
503
504    /// Request rate limit exceeded.
505    #[error(
506        code = "RATE_LIMIT_EXCEEDED",
507        message = "{operation_context}: Rate limit exceeded - {details}",
508        retryable = "true",
509        internal = "false",
510        http_status_code = 429
511    )]
512    RateLimitExceeded {
513        /// Context of the operation that failed
514        operation_context: String,
515        /// Additional details about the rate limit
516        details: String,
517    },
518
519    /// Operation exceeded the allowed timeout.
520    #[error(
521        code = "TIMEOUT",
522        message = "{operation_context}: Operation timed out - {details}",
523        retryable = "true",
524        internal = "false",
525        http_status_code = 408
526    )]
527    Timeout {
528        /// Context of the operation that failed
529        operation_context: String,
530        /// Additional details about the timeout
531        details: String,
532    },
533
534    /// Remote service is temporarily unavailable.
535    #[error(
536        code = "REMOTE_SERVICE_UNAVAILABLE",
537        message = "{operation_context}: Service unavailable - {details}",
538        retryable = "true",
539        internal = "false",
540        http_status_code = 503
541    )]
542    RemoteServiceUnavailable {
543        /// Context of the operation that failed
544        operation_context: String,
545        /// Additional details about the service unavailability
546        details: String,
547    },
548
549    /// Quota or resource limits have been exceeded.
550    #[error(
551        code = "QUOTA_EXCEEDED",
552        message = "{operation_context}: Quota exceeded - {details}",
553        retryable = "true",
554        internal = "false",
555        http_status_code = 429
556    )]
557    QuotaExceeded {
558        /// Context of the operation that failed
559        operation_context: String,
560        /// Additional details about the quota violation
561        details: String,
562    },
563
564    /// Invalid or malformed input parameters provided to the operation.
565    #[error(
566        code = "INVALID_INPUT",
567        message = "{operation_context}: Invalid input - {details}",
568        retryable = "false",
569        internal = "false",
570        http_status_code = 400
571    )]
572    InvalidInput {
573        /// Context of the operation that failed
574        operation_context: String,
575        /// Details about what input was invalid
576        details: String,
577        /// Optional field name that was invalid
578        field_name: Option<String>,
579    },
580
581    /// Authentication with cloud provider failed.
582    #[error(
583        code = "AUTHENTICATION_ERROR",
584        message = "{operation_context}: Authentication failed - {details}",
585        retryable = "true",
586        internal = "false",
587        http_status_code = 401
588    )]
589    AuthenticationError {
590        /// Context of the operation that failed
591        operation_context: String,
592        /// Details about the authentication failure
593        details: String,
594    },
595
596    /// Generic bindings error for uncommon cases.
597    #[error(
598        code = "BINDINGS_ERROR",
599        message = "Bindings error: {message}",
600        retryable = "true",
601        internal = "true",
602        http_status_code = 500
603    )]
604    Other {
605        /// Human-readable description of the error
606        message: String,
607    },
608
609    /// Presigned request has expired and can no longer be used.
610    #[error(
611        code = "PRESIGNED_REQUEST_EXPIRED",
612        message = "Presigned request for path '{path}' expired at {expired_at}",
613        retryable = "false",
614        internal = "false",
615        http_status_code = 403
616    )]
617    PresignedRequestExpired {
618        /// Path that the presigned request was for
619        path: String,
620        /// When the request expired
621        expired_at: chrono::DateTime<chrono::Utc>,
622    },
623
624    /// HTTP request to external service failed.
625    #[error(
626        code = "HTTP_REQUEST_FAILED",
627        message = "HTTP {method} request to '{url}' failed",
628        retryable = "true",
629        internal = "false",
630        http_status_code = 502
631    )]
632    HttpRequestFailed {
633        /// URL that was requested
634        url: String,
635        /// HTTP method that was used
636        method: String,
637    },
638
639    /// Local filesystem operation failed.
640    #[error(
641        code = "LOCAL_FILESYSTEM_ERROR",
642        message = "Local filesystem operation '{operation}' failed for path '{path}'",
643        retryable = "true",
644        internal = "false",
645        http_status_code = 500
646    )]
647    LocalFilesystemError {
648        /// Path that the operation was performed on
649        path: String,
650        /// Operation that failed
651        operation: String,
652    },
653
654    /// Failed to load platform configuration for the provider.
655    #[error(
656        code = "client_config_LOAD_FAILED",
657        message = "Failed to load platform configuration for provider '{provider}'",
658        retryable = "false",
659        internal = "false",
660        http_status_code = 400
661    )]
662    ClientConfigLoadFailed {
663        /// Name of the provider (aws, gcp, azure, etc.)
664        provider: String,
665    },
666
667    /// Binding setup failed during initialization.
668    #[error(
669        code = "BINDING_SETUP_FAILED",
670        message = "Binding setup failed for type '{binding_type}': {reason}",
671        retryable = "false",
672        internal = "false",
673        http_status_code = 500
674    )]
675    BindingSetupFailed {
676        /// Type of binding being set up
677        binding_type: String,
678        /// Reason for the setup failure
679        reason: String,
680    },
681
682    /// KV operation failed.
683    #[error(
684        code = "KV_OPERATION_FAILED",
685        message = "KV operation '{operation}' failed for key '{key}': {reason}",
686        retryable = "true",
687        internal = "false",
688        http_status_code = 502
689    )]
690    KvOperationFailed {
691        /// The KV operation that failed
692        operation: String,
693        /// The key involved in the operation
694        key: String,
695        /// Reason for the operation failure
696        reason: String,
697    },
698
699    /// Queue operation failed.
700    #[error(
701        code = "QUEUE_OPERATION_FAILED",
702        message = "Queue operation '{operation}' failed: {reason}",
703        retryable = "true",
704        internal = "false",
705        http_status_code = 502
706    )]
707    QueueOperationFailed {
708        /// The queue operation that failed
709        operation: String,
710        /// Reason for the operation failure
711        reason: String,
712    },
713
714    /// Remote access to deployment resources failed.
715    #[error(
716        code = "REMOTE_ACCESS_FAILED",
717        message = "Remote access failed during operation: {operation}",
718        retryable = "true",
719        internal = "false",
720        http_status_code = 502
721    )]
722    RemoteAccessFailed {
723        /// Description of the operation that failed
724        operation: String,
725    },
726
727    /// Client configuration is invalid or missing for the platform.
728    #[error(
729        code = "CLIENT_CONFIG_INVALID",
730        message = "Client configuration invalid for platform '{platform}': {message}",
731        retryable = "false",
732        internal = "false",
733        http_status_code = 400
734    )]
735    ClientConfigInvalid {
736        /// The platform that was expected
737        platform: alien_core::Platform,
738        /// Description of the configuration issue
739        message: String,
740    },
741}
742
743impl ErrorData {
744    /// Construct a [`ErrorData::BindingConfigInvalid`] for `binding_name`,
745    /// deriving the exact `ALIEN_<NAME>_BINDING` env var name internally so call
746    /// sites cannot forget it (the omission that repeatedly broke bindings).
747    pub fn config_invalid(binding_name: &str, reason: impl Into<String>) -> Self {
748        ErrorData::BindingConfigInvalid {
749            env_var: binding_env_var(binding_name),
750            binding_name: binding_name.to_string(),
751            reason: reason.into(),
752        }
753    }
754
755    /// Construct a [`ErrorData::BindingNotConfigured`] for `binding_name`,
756    /// deriving the exact `ALIEN_<NAME>_BINDING` env var name internally.
757    pub fn not_configured(binding_name: &str) -> Self {
758        ErrorData::BindingNotConfigured {
759            binding_name: binding_name.to_string(),
760            env_var: binding_env_var(binding_name),
761        }
762    }
763}
764
765/// Convenient alias with default error type `ErrorData`.
766pub type Result<T, E = ErrorData> = alien_error::Result<T, E>;
767
768/// Convenience alias representing a constructed AlienError with our `ErrorData` payload.
769pub type Error = AlienError<ErrorData>;
770
771/// Maps an `alien_client_core::Error` to an appropriate `alien_bindings::Error`.
772///
773/// Important error types (like resource not found, access denied, etc.) are mapped
774/// to their corresponding variants in alien-bindings while preserving the operation context.
775/// Less important errors are wrapped in `CloudPlatformError`.
776///
777/// # Arguments
778/// * `cloud_error` - The error from cloud client crates
779/// * `operation_context` - Description of the operation that failed (e.g., "Failed to get ECR repository details")
780/// * `resource_id` - Optional resource ID for fallback error context
781///
782/// # Example
783/// ```rust
784/// use alien_bindings::error::map_cloud_client_error;
785///
786/// async fn example() {
787///     // This would be an actual cloud client operation
788///     let result = some_cloud_operation().await
789///         .map_err(|e| map_cloud_client_error(e, "Failed to get ECR repository details".to_string(), Some("my-repo".to_string())));
790/// }
791///
792/// async fn some_cloud_operation() -> Result<(), alien_client_core::Error> {
793///     // Mock implementation
794///     Ok(())
795/// }
796/// ```
797pub fn map_cloud_client_error(
798    cloud_error: alien_client_core::Error,
799    operation_context: String,
800    resource_id: Option<String>,
801) -> Error {
802    use alien_client_core::ErrorData as CloudErrorData;
803
804    // Check the error type first to determine the right context to add
805    let error_data = match cloud_error.error.as_ref() {
806        Some(CloudErrorData::RemoteResourceNotFound {
807            resource_type,
808            resource_name,
809        }) => ErrorData::RemoteResourceNotFound {
810            operation_context,
811            resource_type: resource_type.clone(),
812            resource_name: resource_name.clone(),
813        },
814        Some(CloudErrorData::RemoteResourceConflict {
815            resource_type,
816            resource_name,
817            message,
818        }) => ErrorData::RemoteResourceConflict {
819            operation_context,
820            resource_type: resource_type.clone(),
821            resource_name: resource_name.clone(),
822            conflict_reason: message.clone(),
823        },
824        Some(CloudErrorData::RemoteAccessDenied {
825            resource_type,
826            resource_name,
827        }) => ErrorData::RemoteAccessDenied {
828            operation_context,
829            resource_type: resource_type.clone(),
830            resource_name: resource_name.clone(),
831        },
832        Some(CloudErrorData::RateLimitExceeded { message }) => ErrorData::RateLimitExceeded {
833            operation_context,
834            details: message.clone(),
835        },
836        Some(CloudErrorData::Timeout { message }) => ErrorData::Timeout {
837            operation_context,
838            details: message.clone(),
839        },
840        Some(CloudErrorData::RemoteServiceUnavailable { message }) => {
841            ErrorData::RemoteServiceUnavailable {
842                operation_context,
843                details: message.clone(),
844            }
845        }
846        Some(CloudErrorData::QuotaExceeded { message }) => ErrorData::QuotaExceeded {
847            operation_context,
848            details: message.clone(),
849        },
850        Some(CloudErrorData::InvalidInput {
851            message,
852            field_name,
853        }) => ErrorData::InvalidInput {
854            operation_context,
855            details: message.clone(),
856            field_name: field_name.clone(),
857        },
858        Some(CloudErrorData::AuthenticationError { message }) => ErrorData::AuthenticationError {
859            operation_context,
860            details: message.clone(),
861        },
862        // For other error types or None, wrap in CloudPlatformError
863        _ => ErrorData::CloudPlatformError {
864            message: operation_context,
865            resource_id,
866        },
867    };
868
869    // Now add the context to the cloud error
870    cloud_error.context(error_data)
871}