use alien_error::{AlienError, AlienErrorData, ContextError};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, AlienErrorData, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ErrorData {
#[error(
code = "BINDING_CONFIG_INVALID",
message = "Binding configuration invalid for binding '{binding_name}': {reason}",
retryable = "false",
internal = "false",
http_status_code = 400
)]
BindingConfigInvalid {
binding_name: String,
reason: String,
},
#[error(
code = "STORAGE_OPERATION_FAILED",
message = "Storage operation failed for binding '{binding_name}': {operation}",
retryable = "true",
internal = "false",
http_status_code = 502
)]
StorageOperationFailed {
binding_name: String,
operation: String,
},
#[error(
code = "BUILD_OPERATION_FAILED",
message = "Build operation failed for binding '{binding_name}': {operation}",
retryable = "true",
internal = "false",
http_status_code = 502
)]
BuildOperationFailed {
binding_name: String,
operation: String,
},
#[error(
code = "ENVIRONMENT_VARIABLE_MISSING",
message = "Required environment variable '{variable_name}' is missing",
retryable = "false",
internal = "false",
http_status_code = 500
)]
EnvironmentVariableMissing {
variable_name: String,
},
#[error(
code = "INVALID_ENVIRONMENT_VARIABLE",
message = "Environment variable '{variable_name}' has invalid value '{value}': {reason}",
retryable = "false",
internal = "false",
http_status_code = 500
)]
InvalidEnvironmentVariable {
variable_name: String,
value: String,
reason: String,
},
#[error(
code = "INVALID_CONFIGURATION_URL",
message = "Invalid configuration URL '{url}': {reason}",
retryable = "false",
internal = "false",
http_status_code = 400
)]
InvalidConfigurationUrl {
url: String,
reason: String,
},
#[error(
code = "EVENT_PROCESSING_FAILED",
message = "Event processing failed for type '{event_type}': {reason}",
retryable = "true",
internal = "false",
http_status_code = 400
)]
EventProcessingFailed {
event_type: String,
reason: String,
},
#[error(
code = "GRPC_CONNECTION_FAILED",
message = "gRPC connection failed to endpoint '{endpoint}': {reason}",
retryable = "true",
internal = "false",
http_status_code = 502
)]
GrpcConnectionFailed {
endpoint: String,
reason: String,
},
#[error(
code = "GRPC_SERVICE_UNAVAILABLE",
message = "gRPC service '{service}' unavailable at endpoint '{endpoint}': {reason}",
retryable = "true",
internal = "false",
http_status_code = 503
)]
GrpcServiceUnavailable {
service: String,
endpoint: String,
reason: String,
},
#[error(
code = "GRPC_REQUEST_FAILED",
message = "gRPC request to service '{service}' method '{method}' failed: {details}",
retryable = "true",
internal = "false",
http_status_code = 502
)]
GrpcRequestFailed {
service: String,
method: String,
details: String,
},
#[error(
code = "SERVER_BIND_FAILED",
message = "Failed to bind server to address '{address}': {reason}",
retryable = "true",
internal = "true",
http_status_code = 500
)]
ServerBindFailed {
address: String,
reason: String,
},
#[error(
code = "AUTHENTICATION_FAILED",
message = "Authentication failed for provider '{provider}' and binding '{binding_name}': {reason}",
retryable = "true",
internal = "false",
http_status_code = 401
)]
AuthenticationFailed {
provider: String,
binding_name: String,
reason: String,
},
#[error(
code = "OPERATION_NOT_SUPPORTED",
message = "Operation '{operation}' not supported: {reason}",
retryable = "false",
internal = "false",
http_status_code = 501
)]
OperationNotSupported {
operation: String,
reason: String,
},
#[error(
code = "FEATURE_NOT_ENABLED",
message = "Feature '{feature}' is not enabled in this build",
retryable = "false",
internal = "false",
http_status_code = 501
)]
FeatureNotEnabled {
feature: String,
},
#[error(
code = "GRPC_CALL_FAILED",
message = "gRPC call to service '{service}' method '{method}' failed: {reason}",
retryable = "true",
internal = "false",
http_status_code = 502
)]
GrpcCallFailed {
service: String,
method: String,
reason: String,
},
#[error(
code = "DESERIALIZATION_FAILED",
message = "Failed to deserialize {type_name}: {message}",
retryable = "false",
internal = "false",
http_status_code = 400
)]
DeserializationFailed {
message: String,
type_name: String,
},
#[error(
code = "SERIALIZATION_FAILED",
message = "Failed to serialize data: {message}",
retryable = "false",
internal = "false",
http_status_code = 500
)]
SerializationFailed {
message: String,
},
#[error(
code = "NOT_IMPLEMENTED",
message = "Operation '{operation}' is not implemented: {reason}",
retryable = "false",
internal = "false",
http_status_code = 501
)]
NotImplemented {
operation: String,
reason: String,
},
#[error(
code = "UNEXPECTED_RESPONSE_FORMAT",
message = "Unexpected response format from '{provider}' for binding '{binding_name}': missing field '{field}'. Response: {response_json}",
retryable = "false",
internal = "false",
http_status_code = 502
)]
UnexpectedResponseFormat {
provider: String,
binding_name: String,
field: String,
response_json: String,
},
#[error(
code = "CLOUD_PLATFORM_ERROR",
message = "Cloud platform error: {message}",
retryable = "true",
internal = "false",
http_status_code = 502
)]
CloudPlatformError {
message: String,
resource_id: Option<String>,
},
#[error(
code = "RESOURCE_NOT_FOUND",
message = "Resource '{resource_id}' not found",
retryable = "false",
internal = "false",
http_status_code = 404
)]
ResourceNotFound {
resource_id: String,
},
#[error(
code = "REMOTE_RESOURCE_NOT_FOUND",
message = "{operation_context}: {resource_type} '{resource_name}' not found",
retryable = "false",
internal = "false",
http_status_code = 404
)]
RemoteResourceNotFound {
operation_context: String,
resource_type: String,
resource_name: String,
},
#[error(
code = "REMOTE_RESOURCE_CONFLICT",
message = "{operation_context}: Conflict with {resource_type} '{resource_name}' - {conflict_reason}",
retryable = "true",
internal = "false",
http_status_code = 409
)]
RemoteResourceConflict {
operation_context: String,
resource_type: String,
resource_name: String,
conflict_reason: String,
},
#[error(
code = "REMOTE_ACCESS_DENIED",
message = "{operation_context}: Access denied to {resource_type} '{resource_name}'",
retryable = "true",
internal = "false",
http_status_code = 403
)]
RemoteAccessDenied {
operation_context: String,
resource_type: String,
resource_name: String,
},
#[error(
code = "RATE_LIMIT_EXCEEDED",
message = "{operation_context}: Rate limit exceeded - {details}",
retryable = "true",
internal = "false",
http_status_code = 429
)]
RateLimitExceeded {
operation_context: String,
details: String,
},
#[error(
code = "TIMEOUT",
message = "{operation_context}: Operation timed out - {details}",
retryable = "true",
internal = "false",
http_status_code = 408
)]
Timeout {
operation_context: String,
details: String,
},
#[error(
code = "REMOTE_SERVICE_UNAVAILABLE",
message = "{operation_context}: Service unavailable - {details}",
retryable = "true",
internal = "false",
http_status_code = 503
)]
RemoteServiceUnavailable {
operation_context: String,
details: String,
},
#[error(
code = "QUOTA_EXCEEDED",
message = "{operation_context}: Quota exceeded - {details}",
retryable = "true",
internal = "false",
http_status_code = 429
)]
QuotaExceeded {
operation_context: String,
details: String,
},
#[error(
code = "INVALID_INPUT",
message = "{operation_context}: Invalid input - {details}",
retryable = "false",
internal = "false",
http_status_code = 400
)]
InvalidInput {
operation_context: String,
details: String,
field_name: Option<String>,
},
#[error(
code = "AUTHENTICATION_ERROR",
message = "{operation_context}: Authentication failed - {details}",
retryable = "true",
internal = "false",
http_status_code = 401
)]
AuthenticationError {
operation_context: String,
details: String,
},
#[error(
code = "BINDINGS_ERROR",
message = "Bindings error: {message}",
retryable = "true",
internal = "true",
http_status_code = 500
)]
Other {
message: String,
},
#[error(
code = "PRESIGNED_REQUEST_EXPIRED",
message = "Presigned request for path '{path}' expired at {expired_at}",
retryable = "false",
internal = "false",
http_status_code = 403
)]
PresignedRequestExpired {
path: String,
expired_at: chrono::DateTime<chrono::Utc>,
},
#[error(
code = "HTTP_REQUEST_FAILED",
message = "HTTP {method} request to '{url}' failed",
retryable = "true",
internal = "false",
http_status_code = 502
)]
HttpRequestFailed {
url: String,
method: String,
},
#[error(
code = "LOCAL_FILESYSTEM_ERROR",
message = "Local filesystem operation '{operation}' failed for path '{path}'",
retryable = "true",
internal = "false",
http_status_code = 500
)]
LocalFilesystemError {
path: String,
operation: String,
},
#[error(
code = "client_config_LOAD_FAILED",
message = "Failed to load platform configuration for provider '{provider}'",
retryable = "false",
internal = "false",
http_status_code = 400
)]
ClientConfigLoadFailed {
provider: String,
},
#[error(
code = "BINDING_SETUP_FAILED",
message = "Binding setup failed for type '{binding_type}': {reason}",
retryable = "false",
internal = "false",
http_status_code = 500
)]
BindingSetupFailed {
binding_type: String,
reason: String,
},
#[error(
code = "KV_OPERATION_FAILED",
message = "KV operation '{operation}' failed for key '{key}': {reason}",
retryable = "true",
internal = "false",
http_status_code = 502
)]
KvOperationFailed {
operation: String,
key: String,
reason: String,
},
#[error(
code = "QUEUE_OPERATION_FAILED",
message = "Queue operation '{operation}' failed: {reason}",
retryable = "true",
internal = "false",
http_status_code = 502
)]
QueueOperationFailed {
operation: String,
reason: String,
},
#[error(
code = "REMOTE_ACCESS_FAILED",
message = "Remote access failed during operation: {operation}",
retryable = "true",
internal = "false",
http_status_code = 502
)]
RemoteAccessFailed {
operation: String,
},
#[error(
code = "CLIENT_CONFIG_INVALID",
message = "Client configuration invalid for platform '{platform}': {message}",
retryable = "false",
internal = "false",
http_status_code = 400
)]
ClientConfigInvalid {
platform: alien_core::Platform,
message: String,
},
}
pub type Result<T, E = ErrorData> = alien_error::Result<T, E>;
pub type Error = AlienError<ErrorData>;
pub fn map_cloud_client_error(
cloud_error: alien_client_core::Error,
operation_context: String,
resource_id: Option<String>,
) -> Error {
use alien_client_core::ErrorData as CloudErrorData;
let error_data = match cloud_error.error.as_ref() {
Some(CloudErrorData::RemoteResourceNotFound {
resource_type,
resource_name,
}) => ErrorData::RemoteResourceNotFound {
operation_context,
resource_type: resource_type.clone(),
resource_name: resource_name.clone(),
},
Some(CloudErrorData::RemoteResourceConflict {
resource_type,
resource_name,
message,
}) => ErrorData::RemoteResourceConflict {
operation_context,
resource_type: resource_type.clone(),
resource_name: resource_name.clone(),
conflict_reason: message.clone(),
},
Some(CloudErrorData::RemoteAccessDenied {
resource_type,
resource_name,
}) => ErrorData::RemoteAccessDenied {
operation_context,
resource_type: resource_type.clone(),
resource_name: resource_name.clone(),
},
Some(CloudErrorData::RateLimitExceeded { message }) => ErrorData::RateLimitExceeded {
operation_context,
details: message.clone(),
},
Some(CloudErrorData::Timeout { message }) => ErrorData::Timeout {
operation_context,
details: message.clone(),
},
Some(CloudErrorData::RemoteServiceUnavailable { message }) => {
ErrorData::RemoteServiceUnavailable {
operation_context,
details: message.clone(),
}
}
Some(CloudErrorData::QuotaExceeded { message }) => ErrorData::QuotaExceeded {
operation_context,
details: message.clone(),
},
Some(CloudErrorData::InvalidInput {
message,
field_name,
}) => ErrorData::InvalidInput {
operation_context,
details: message.clone(),
field_name: field_name.clone(),
},
Some(CloudErrorData::AuthenticationError { message }) => ErrorData::AuthenticationError {
operation_context,
details: message.clone(),
},
_ => ErrorData::CloudPlatformError {
message: operation_context,
resource_id,
},
};
cloud_error.context(error_data)
}