1use alien_error::{AlienError, AlienErrorData, ContextError};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, AlienErrorData, Serialize, Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub enum ErrorData {
8 #[error(
10 code = "BINDING_CONFIG_INVALID",
11 message = "Binding configuration invalid for binding '{binding_name}': {reason}",
12 retryable = "false",
13 internal = "false",
14 http_status_code = 400
15 )]
16 BindingConfigInvalid {
17 binding_name: String,
19 reason: String,
21 },
22
23 #[error(
25 code = "STORAGE_OPERATION_FAILED",
26 message = "Storage operation failed for binding '{binding_name}': {operation}",
27 retryable = "true",
28 internal = "false",
29 http_status_code = 502
30 )]
31 StorageOperationFailed {
32 binding_name: String,
34 operation: String,
36 },
37
38 #[error(
40 code = "BUILD_OPERATION_FAILED",
41 message = "Build operation failed for binding '{binding_name}': {operation}",
42 retryable = "true",
43 internal = "false",
44 http_status_code = 502
45 )]
46 BuildOperationFailed {
47 binding_name: String,
49 operation: String,
51 },
52
53 #[error(
55 code = "ENVIRONMENT_VARIABLE_MISSING",
56 message = "Required environment variable '{variable_name}' is missing",
57 retryable = "false",
58 internal = "false",
59 http_status_code = 500
60 )]
61 EnvironmentVariableMissing {
62 variable_name: String,
64 },
65
66 #[error(
68 code = "INVALID_ENVIRONMENT_VARIABLE",
69 message = "Environment variable '{variable_name}' has invalid value '{value}': {reason}",
70 retryable = "false",
71 internal = "false",
72 http_status_code = 500
73 )]
74 InvalidEnvironmentVariable {
75 variable_name: String,
77 value: String,
79 reason: String,
81 },
82
83 #[error(
85 code = "INVALID_CONFIGURATION_URL",
86 message = "Invalid configuration URL '{url}': {reason}",
87 retryable = "false",
88 internal = "false",
89 http_status_code = 400
90 )]
91 InvalidConfigurationUrl {
92 url: String,
94 reason: String,
96 },
97
98 #[error(
100 code = "EVENT_PROCESSING_FAILED",
101 message = "Event processing failed for type '{event_type}': {reason}",
102 retryable = "true",
103 internal = "false",
104 http_status_code = 400
105 )]
106 EventProcessingFailed {
107 event_type: String,
109 reason: String,
111 },
112
113 #[error(
115 code = "GRPC_CONNECTION_FAILED",
116 message = "gRPC connection failed to endpoint '{endpoint}': {reason}",
117 retryable = "true",
118 internal = "false",
119 http_status_code = 502
120 )]
121 GrpcConnectionFailed {
122 endpoint: String,
124 reason: String,
126 },
127
128 #[error(
130 code = "GRPC_SERVICE_UNAVAILABLE",
131 message = "gRPC service '{service}' unavailable at endpoint '{endpoint}': {reason}",
132 retryable = "true",
133 internal = "false",
134 http_status_code = 503
135 )]
136 GrpcServiceUnavailable {
137 service: String,
139 endpoint: String,
141 reason: String,
143 },
144
145 #[error(
147 code = "GRPC_REQUEST_FAILED",
148 message = "gRPC request to service '{service}' method '{method}' failed: {details}",
149 retryable = "true",
150 internal = "false",
151 http_status_code = 502
152 )]
153 GrpcRequestFailed {
154 service: String,
156 method: String,
158 details: String,
160 },
161
162 #[error(
164 code = "SERVER_BIND_FAILED",
165 message = "Failed to bind server to address '{address}': {reason}",
166 retryable = "true",
167 internal = "true",
168 http_status_code = 500
169 )]
170 ServerBindFailed {
171 address: String,
173 reason: String,
175 },
176
177 #[error(
179 code = "AUTHENTICATION_FAILED",
180 message = "Authentication failed for provider '{provider}' and binding '{binding_name}': {reason}",
181 retryable = "true",
182 internal = "false",
183 http_status_code = 401
184 )]
185 AuthenticationFailed {
186 provider: String,
188 binding_name: String,
190 reason: String,
192 },
193
194 #[error(
196 code = "OPERATION_NOT_SUPPORTED",
197 message = "Operation '{operation}' not supported: {reason}",
198 retryable = "false",
199 internal = "false",
200 http_status_code = 501
201 )]
202 OperationNotSupported {
203 operation: String,
205 reason: String,
207 },
208
209 #[error(
211 code = "FEATURE_NOT_ENABLED",
212 message = "Feature '{feature}' is not enabled in this build",
213 retryable = "false",
214 internal = "false",
215 http_status_code = 501
216 )]
217 FeatureNotEnabled {
218 feature: String,
220 },
221
222 #[error(
224 code = "GRPC_CALL_FAILED",
225 message = "gRPC call to service '{service}' method '{method}' failed: {reason}",
226 retryable = "true",
227 internal = "false",
228 http_status_code = 502
229 )]
230 GrpcCallFailed {
231 service: String,
233 method: String,
235 reason: String,
237 },
238
239 #[error(
241 code = "DESERIALIZATION_FAILED",
242 message = "Failed to deserialize {type_name}: {message}",
243 retryable = "false",
244 internal = "false",
245 http_status_code = 400
246 )]
247 DeserializationFailed {
248 message: String,
250 type_name: String,
252 },
253
254 #[error(
256 code = "SERIALIZATION_FAILED",
257 message = "Failed to serialize data: {message}",
258 retryable = "false",
259 internal = "false",
260 http_status_code = 500
261 )]
262 SerializationFailed {
263 message: String,
265 },
266
267 #[error(
269 code = "NOT_IMPLEMENTED",
270 message = "Operation '{operation}' is not implemented: {reason}",
271 retryable = "false",
272 internal = "false",
273 http_status_code = 501
274 )]
275 NotImplemented {
276 operation: String,
278 reason: String,
280 },
281
282 #[error(
284 code = "UNEXPECTED_RESPONSE_FORMAT",
285 message = "Unexpected response format from '{provider}' for binding '{binding_name}': missing field '{field}'. Response: {response_json}",
286 retryable = "false",
287 internal = "false",
288 http_status_code = 502
289 )]
290 UnexpectedResponseFormat {
291 provider: String,
293 binding_name: String,
295 field: String,
297 response_json: String,
299 },
300
301 #[error(
303 code = "CLOUD_PLATFORM_ERROR",
304 message = "Cloud platform error: {message}",
305 retryable = "true",
306 internal = "false",
307 http_status_code = 502
308 )]
309 CloudPlatformError {
310 message: String,
312 resource_id: Option<String>,
314 },
315
316 #[error(
318 code = "RESOURCE_NOT_FOUND",
319 message = "Resource '{resource_id}' not found",
320 retryable = "false",
321 internal = "false",
322 http_status_code = 404
323 )]
324 ResourceNotFound {
325 resource_id: String,
327 },
328
329 #[error(
331 code = "REMOTE_RESOURCE_NOT_FOUND",
332 message = "{operation_context}: {resource_type} '{resource_name}' not found",
333 retryable = "false",
334 internal = "false",
335 http_status_code = 404
336 )]
337 RemoteResourceNotFound {
338 operation_context: String,
340 resource_type: String,
342 resource_name: String,
344 },
345
346 #[error(
348 code = "REMOTE_RESOURCE_CONFLICT",
349 message = "{operation_context}: Conflict with {resource_type} '{resource_name}' - {conflict_reason}",
350 retryable = "true",
351 internal = "false",
352 http_status_code = 409
353 )]
354 RemoteResourceConflict {
355 operation_context: String,
357 resource_type: String,
359 resource_name: String,
361 conflict_reason: String,
363 },
364
365 #[error(
367 code = "REMOTE_ACCESS_DENIED",
368 message = "{operation_context}: Access denied to {resource_type} '{resource_name}'",
369 retryable = "true",
370 internal = "false",
371 http_status_code = 403
372 )]
373 RemoteAccessDenied {
374 operation_context: String,
376 resource_type: String,
378 resource_name: String,
380 },
381
382 #[error(
384 code = "RATE_LIMIT_EXCEEDED",
385 message = "{operation_context}: Rate limit exceeded - {details}",
386 retryable = "true",
387 internal = "false",
388 http_status_code = 429
389 )]
390 RateLimitExceeded {
391 operation_context: String,
393 details: String,
395 },
396
397 #[error(
399 code = "TIMEOUT",
400 message = "{operation_context}: Operation timed out - {details}",
401 retryable = "true",
402 internal = "false",
403 http_status_code = 408
404 )]
405 Timeout {
406 operation_context: String,
408 details: String,
410 },
411
412 #[error(
414 code = "REMOTE_SERVICE_UNAVAILABLE",
415 message = "{operation_context}: Service unavailable - {details}",
416 retryable = "true",
417 internal = "false",
418 http_status_code = 503
419 )]
420 RemoteServiceUnavailable {
421 operation_context: String,
423 details: String,
425 },
426
427 #[error(
429 code = "QUOTA_EXCEEDED",
430 message = "{operation_context}: Quota exceeded - {details}",
431 retryable = "true",
432 internal = "false",
433 http_status_code = 429
434 )]
435 QuotaExceeded {
436 operation_context: String,
438 details: String,
440 },
441
442 #[error(
444 code = "INVALID_INPUT",
445 message = "{operation_context}: Invalid input - {details}",
446 retryable = "false",
447 internal = "false",
448 http_status_code = 400
449 )]
450 InvalidInput {
451 operation_context: String,
453 details: String,
455 field_name: Option<String>,
457 },
458
459 #[error(
461 code = "AUTHENTICATION_ERROR",
462 message = "{operation_context}: Authentication failed - {details}",
463 retryable = "true",
464 internal = "false",
465 http_status_code = 401
466 )]
467 AuthenticationError {
468 operation_context: String,
470 details: String,
472 },
473
474 #[error(
476 code = "BINDINGS_ERROR",
477 message = "Bindings error: {message}",
478 retryable = "true",
479 internal = "true",
480 http_status_code = 500
481 )]
482 Other {
483 message: String,
485 },
486
487 #[error(
489 code = "PRESIGNED_REQUEST_EXPIRED",
490 message = "Presigned request for path '{path}' expired at {expired_at}",
491 retryable = "false",
492 internal = "false",
493 http_status_code = 403
494 )]
495 PresignedRequestExpired {
496 path: String,
498 expired_at: chrono::DateTime<chrono::Utc>,
500 },
501
502 #[error(
504 code = "HTTP_REQUEST_FAILED",
505 message = "HTTP {method} request to '{url}' failed",
506 retryable = "true",
507 internal = "false",
508 http_status_code = 502
509 )]
510 HttpRequestFailed {
511 url: String,
513 method: String,
515 },
516
517 #[error(
519 code = "LOCAL_FILESYSTEM_ERROR",
520 message = "Local filesystem operation '{operation}' failed for path '{path}'",
521 retryable = "true",
522 internal = "false",
523 http_status_code = 500
524 )]
525 LocalFilesystemError {
526 path: String,
528 operation: String,
530 },
531
532 #[error(
534 code = "client_config_LOAD_FAILED",
535 message = "Failed to load platform configuration for provider '{provider}'",
536 retryable = "false",
537 internal = "false",
538 http_status_code = 400
539 )]
540 ClientConfigLoadFailed {
541 provider: String,
543 },
544
545 #[error(
547 code = "BINDING_SETUP_FAILED",
548 message = "Binding setup failed for type '{binding_type}': {reason}",
549 retryable = "false",
550 internal = "false",
551 http_status_code = 500
552 )]
553 BindingSetupFailed {
554 binding_type: String,
556 reason: String,
558 },
559
560 #[error(
562 code = "KV_OPERATION_FAILED",
563 message = "KV operation '{operation}' failed for key '{key}': {reason}",
564 retryable = "true",
565 internal = "false",
566 http_status_code = 502
567 )]
568 KvOperationFailed {
569 operation: String,
571 key: String,
573 reason: String,
575 },
576
577 #[error(
579 code = "REMOTE_ACCESS_FAILED",
580 message = "Remote access failed during operation: {operation}",
581 retryable = "true",
582 internal = "false",
583 http_status_code = 502
584 )]
585 RemoteAccessFailed {
586 operation: String,
588 },
589
590 #[error(
592 code = "CLIENT_CONFIG_INVALID",
593 message = "Client configuration invalid for platform '{platform}': {message}",
594 retryable = "false",
595 internal = "false",
596 http_status_code = 400
597 )]
598 ClientConfigInvalid {
599 platform: alien_core::Platform,
601 message: String,
603 },
604}
605
606pub type Result<T, E = ErrorData> = alien_error::Result<T, E>;
608
609pub type Error = AlienError<ErrorData>;
611
612pub fn map_cloud_client_error(
639 cloud_error: alien_client_core::Error,
640 operation_context: String,
641 resource_id: Option<String>,
642) -> Error {
643 use alien_client_core::ErrorData as CloudErrorData;
644
645 let error_data = match cloud_error.error.as_ref() {
647 Some(CloudErrorData::RemoteResourceNotFound {
648 resource_type,
649 resource_name,
650 }) => ErrorData::RemoteResourceNotFound {
651 operation_context,
652 resource_type: resource_type.clone(),
653 resource_name: resource_name.clone(),
654 },
655 Some(CloudErrorData::RemoteResourceConflict {
656 resource_type,
657 resource_name,
658 message,
659 }) => ErrorData::RemoteResourceConflict {
660 operation_context,
661 resource_type: resource_type.clone(),
662 resource_name: resource_name.clone(),
663 conflict_reason: message.clone(),
664 },
665 Some(CloudErrorData::RemoteAccessDenied {
666 resource_type,
667 resource_name,
668 }) => ErrorData::RemoteAccessDenied {
669 operation_context,
670 resource_type: resource_type.clone(),
671 resource_name: resource_name.clone(),
672 },
673 Some(CloudErrorData::RateLimitExceeded { message }) => ErrorData::RateLimitExceeded {
674 operation_context,
675 details: message.clone(),
676 },
677 Some(CloudErrorData::Timeout { message }) => ErrorData::Timeout {
678 operation_context,
679 details: message.clone(),
680 },
681 Some(CloudErrorData::RemoteServiceUnavailable { message }) => {
682 ErrorData::RemoteServiceUnavailable {
683 operation_context,
684 details: message.clone(),
685 }
686 }
687 Some(CloudErrorData::QuotaExceeded { message }) => ErrorData::QuotaExceeded {
688 operation_context,
689 details: message.clone(),
690 },
691 Some(CloudErrorData::InvalidInput {
692 message,
693 field_name,
694 }) => ErrorData::InvalidInput {
695 operation_context,
696 details: message.clone(),
697 field_name: field_name.clone(),
698 },
699 Some(CloudErrorData::AuthenticationError { message }) => ErrorData::AuthenticationError {
700 operation_context,
701 details: message.clone(),
702 },
703 _ => ErrorData::CloudPlatformError {
705 message: operation_context,
706 resource_id,
707 },
708 };
709
710 cloud_error.context(error_data)
712}