1use alien_error::{AlienError, AlienErrorData, ContextError};
2use serde::{Deserialize, Serialize};
3
4pub fn binding_env_var(binding_name: &str) -> String {
9 alien_core::bindings::binding_env_var_name(binding_name)
10}
11
12#[derive(Debug, Clone, AlienErrorData, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub enum ErrorData {
16 #[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 binding_name: String,
28 env_var: String,
30 },
31
32 #[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 binding_name: String,
43 env_var: String,
45 reason: String,
47 },
48
49 #[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 binding_name: String,
60 env_var: String,
62 provider: String,
64 },
65
66 #[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 binding_name: String,
77 operation: String,
79 },
80
81 #[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 binding_name: String,
92 operation: String,
94 },
95
96 #[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 variable_name: String,
107 },
108
109 #[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 variable_name: String,
120 value: String,
122 reason: String,
124 },
125
126 #[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 url: String,
137 reason: String,
139 },
140
141 #[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 event_type: String,
152 reason: String,
154 },
155
156 #[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 endpoint: String,
167 reason: String,
169 },
170
171 #[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 service: String,
182 endpoint: String,
184 reason: String,
186 },
187
188 #[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 service: String,
199 method: String,
201 details: String,
203 },
204
205 #[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 address: String,
216 reason: String,
218 },
219
220 #[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 provider: String,
231 binding_name: String,
233 reason: String,
235 },
236
237 #[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 operation: String,
248 reason: String,
250 },
251
252 #[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 feature: String,
263 },
264
265 #[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 service: String,
276 method: String,
278 reason: String,
280 },
281
282 #[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 message: String,
293 type_name: String,
295 },
296
297 #[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 message: String,
308 },
309
310 #[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 provider: String,
321 binding_name: String,
323 field: String,
325 response_json: String,
327 },
328
329 #[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 message: String,
340 resource_id: Option<String>,
342 },
343
344 #[error(
346 code = "RESOURCE_NOT_FOUND",
347 message = "Resource '{resource_id}' not found",
348 retryable = "false",
349 internal = "false",
350 http_status_code = 404
351 )]
352 ResourceNotFound {
353 resource_id: String,
355 },
356
357 #[error(
359 code = "REMOTE_RESOURCE_NOT_FOUND",
360 message = "{operation_context}: {resource_type} '{resource_name}' not found",
361 retryable = "false",
362 internal = "false",
363 http_status_code = 404
364 )]
365 RemoteResourceNotFound {
366 operation_context: String,
368 resource_type: String,
370 resource_name: String,
372 },
373
374 #[error(
376 code = "REMOTE_RESOURCE_CONFLICT",
377 message = "{operation_context}: Conflict with {resource_type} '{resource_name}' - {conflict_reason}",
378 retryable = "true",
379 internal = "false",
380 http_status_code = 409
381 )]
382 RemoteResourceConflict {
383 operation_context: String,
385 resource_type: String,
387 resource_name: String,
389 conflict_reason: String,
391 },
392
393 #[error(
395 code = "REMOTE_ACCESS_DENIED",
396 message = "{operation_context}: Access denied to {resource_type} '{resource_name}'",
397 retryable = "true",
398 internal = "false",
399 http_status_code = 403
400 )]
401 RemoteAccessDenied {
402 operation_context: String,
404 resource_type: String,
406 resource_name: String,
408 },
409
410 #[error(
412 code = "RATE_LIMIT_EXCEEDED",
413 message = "{operation_context}: Rate limit exceeded - {details}",
414 retryable = "true",
415 internal = "false",
416 http_status_code = 429
417 )]
418 RateLimitExceeded {
419 operation_context: String,
421 details: String,
423 },
424
425 #[error(
427 code = "TIMEOUT",
428 message = "{operation_context}: Operation timed out - {details}",
429 retryable = "true",
430 internal = "false",
431 http_status_code = 408
432 )]
433 Timeout {
434 operation_context: String,
436 details: String,
438 },
439
440 #[error(
442 code = "REMOTE_SERVICE_UNAVAILABLE",
443 message = "{operation_context}: Service unavailable - {details}",
444 retryable = "true",
445 internal = "false",
446 http_status_code = 503
447 )]
448 RemoteServiceUnavailable {
449 operation_context: String,
451 details: String,
453 },
454
455 #[error(
457 code = "QUOTA_EXCEEDED",
458 message = "{operation_context}: Quota exceeded - {details}",
459 retryable = "true",
460 internal = "false",
461 http_status_code = 429
462 )]
463 QuotaExceeded {
464 operation_context: String,
466 details: String,
468 },
469
470 #[error(
472 code = "INVALID_INPUT",
473 message = "{operation_context}: Invalid input - {details}",
474 retryable = "false",
475 internal = "false",
476 http_status_code = 400
477 )]
478 InvalidInput {
479 operation_context: String,
481 details: String,
483 field_name: Option<String>,
485 },
486
487 #[error(
489 code = "AUTHENTICATION_ERROR",
490 message = "{operation_context}: Authentication failed - {details}",
491 retryable = "true",
492 internal = "false",
493 http_status_code = 401
494 )]
495 AuthenticationError {
496 operation_context: String,
498 details: String,
500 },
501
502 #[error(
504 code = "BINDINGS_ERROR",
505 message = "Bindings error: {message}",
506 retryable = "true",
507 internal = "true",
508 http_status_code = 500
509 )]
510 Other {
511 message: String,
513 },
514
515 #[error(
517 code = "PRESIGNED_REQUEST_EXPIRED",
518 message = "Presigned request for path '{path}' expired at {expired_at}",
519 retryable = "false",
520 internal = "false",
521 http_status_code = 403
522 )]
523 PresignedRequestExpired {
524 path: String,
526 expired_at: chrono::DateTime<chrono::Utc>,
528 },
529
530 #[error(
532 code = "HTTP_REQUEST_FAILED",
533 message = "HTTP {method} request to '{url}' failed",
534 retryable = "true",
535 internal = "false",
536 http_status_code = 502
537 )]
538 HttpRequestFailed {
539 url: String,
541 method: String,
543 },
544
545 #[error(
547 code = "LOCAL_FILESYSTEM_ERROR",
548 message = "Local filesystem operation '{operation}' failed for path '{path}'",
549 retryable = "true",
550 internal = "false",
551 http_status_code = 500
552 )]
553 LocalFilesystemError {
554 path: String,
556 operation: String,
558 },
559
560 #[error(
562 code = "client_config_LOAD_FAILED",
563 message = "Failed to load platform configuration for provider '{provider}'",
564 retryable = "false",
565 internal = "false",
566 http_status_code = 400
567 )]
568 ClientConfigLoadFailed {
569 provider: String,
571 },
572
573 #[error(
575 code = "BINDING_SETUP_FAILED",
576 message = "Binding setup failed for type '{binding_type}': {reason}",
577 retryable = "false",
578 internal = "false",
579 http_status_code = 500
580 )]
581 BindingSetupFailed {
582 binding_type: String,
584 reason: String,
586 },
587
588 #[error(
590 code = "KV_OPERATION_FAILED",
591 message = "KV operation '{operation}' failed for key '{key}': {reason}",
592 retryable = "true",
593 internal = "false",
594 http_status_code = 502
595 )]
596 KvOperationFailed {
597 operation: String,
599 key: String,
601 reason: String,
603 },
604
605 #[error(
607 code = "QUEUE_OPERATION_FAILED",
608 message = "Queue operation '{operation}' failed: {reason}",
609 retryable = "true",
610 internal = "false",
611 http_status_code = 502
612 )]
613 QueueOperationFailed {
614 operation: String,
616 reason: String,
618 },
619
620 #[error(
622 code = "REMOTE_ACCESS_FAILED",
623 message = "Remote access failed during operation: {operation}",
624 retryable = "true",
625 internal = "false",
626 http_status_code = 502
627 )]
628 RemoteAccessFailed {
629 operation: String,
631 },
632
633 #[error(
635 code = "CLIENT_CONFIG_INVALID",
636 message = "Client configuration invalid for platform '{platform}': {message}",
637 retryable = "false",
638 internal = "false",
639 http_status_code = 400
640 )]
641 ClientConfigInvalid {
642 platform: alien_core::Platform,
644 message: String,
646 },
647}
648
649impl ErrorData {
650 pub fn config_invalid(binding_name: &str, reason: impl Into<String>) -> Self {
654 ErrorData::BindingConfigInvalid {
655 env_var: binding_env_var(binding_name),
656 binding_name: binding_name.to_string(),
657 reason: reason.into(),
658 }
659 }
660
661 pub fn not_configured(binding_name: &str) -> Self {
664 ErrorData::BindingNotConfigured {
665 binding_name: binding_name.to_string(),
666 env_var: binding_env_var(binding_name),
667 }
668 }
669}
670
671pub type Result<T, E = ErrorData> = alien_error::Result<T, E>;
673
674pub type Error = AlienError<ErrorData>;
676
677pub fn map_cloud_client_error(
704 cloud_error: alien_client_core::Error,
705 operation_context: String,
706 resource_id: Option<String>,
707) -> Error {
708 use alien_client_core::ErrorData as CloudErrorData;
709
710 let error_data = match cloud_error.error.as_ref() {
712 Some(CloudErrorData::RemoteResourceNotFound {
713 resource_type,
714 resource_name,
715 }) => ErrorData::RemoteResourceNotFound {
716 operation_context,
717 resource_type: resource_type.clone(),
718 resource_name: resource_name.clone(),
719 },
720 Some(CloudErrorData::RemoteResourceConflict {
721 resource_type,
722 resource_name,
723 message,
724 }) => ErrorData::RemoteResourceConflict {
725 operation_context,
726 resource_type: resource_type.clone(),
727 resource_name: resource_name.clone(),
728 conflict_reason: message.clone(),
729 },
730 Some(CloudErrorData::RemoteAccessDenied {
731 resource_type,
732 resource_name,
733 }) => ErrorData::RemoteAccessDenied {
734 operation_context,
735 resource_type: resource_type.clone(),
736 resource_name: resource_name.clone(),
737 },
738 Some(CloudErrorData::RateLimitExceeded { message }) => ErrorData::RateLimitExceeded {
739 operation_context,
740 details: message.clone(),
741 },
742 Some(CloudErrorData::Timeout { message }) => ErrorData::Timeout {
743 operation_context,
744 details: message.clone(),
745 },
746 Some(CloudErrorData::RemoteServiceUnavailable { message }) => {
747 ErrorData::RemoteServiceUnavailable {
748 operation_context,
749 details: message.clone(),
750 }
751 }
752 Some(CloudErrorData::QuotaExceeded { message }) => ErrorData::QuotaExceeded {
753 operation_context,
754 details: message.clone(),
755 },
756 Some(CloudErrorData::InvalidInput {
757 message,
758 field_name,
759 }) => ErrorData::InvalidInput {
760 operation_context,
761 details: message.clone(),
762 field_name: field_name.clone(),
763 },
764 Some(CloudErrorData::AuthenticationError { message }) => ErrorData::AuthenticationError {
765 operation_context,
766 details: message.clone(),
767 },
768 _ => ErrorData::CloudPlatformError {
770 message: operation_context,
771 resource_id,
772 },
773 };
774
775 cloud_error.context(error_data)
777}