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