use thiserror::Error;
#[derive(Error, Debug)]
pub enum CloudError {
#[error("Failed to initialize cloud client: {0}")]
ClientInit(String),
#[error("Cloud provider '{0}' not enabled. Enable with feature flag (cloud-aws, cloud-gcp, cloud-azure)")]
ProviderNotEnabled(String),
#[error("Failed to fetch secret '{name}': {error}")]
SecretFetch {
name: String,
error: String,
},
#[error("Failed to create secret '{name}': {error}")]
SecretCreate {
name: String,
error: String,
},
#[error("Failed to update secret '{name}': {error}")]
SecretUpdate {
name: String,
error: String,
},
#[error("Failed to delete secret '{name}': {error}")]
SecretDelete {
name: String,
error: String,
},
#[error("Invalid secret format for '{name}': {reason}")]
SecretFormat {
name: String,
reason: String,
},
#[error("Failed to list secrets: {0}")]
SecretList(String),
#[error("Secret not found: '{0}'")]
SecretNotFound(String),
#[error("Failed to fetch object '{key}' from storage: {error}")]
StorageFetch {
key: String,
error: String,
},
#[error("Failed to read storage object '{key}': {error}")]
StorageRead {
key: String,
error: String,
},
#[error("Failed to put object '{key}' to storage: {error}")]
StoragePut {
key: String,
error: String,
},
#[error("Failed to delete object '{key}' from storage: {error}")]
StorageDelete {
key: String,
error: String,
},
#[error("Failed to list storage objects with prefix '{prefix}': {error}")]
StorageList {
prefix: String,
error: String,
},
#[error("Storage object not found: '{0}'")]
StorageObjectNotFound(String),
#[error("Failed to export metrics: {0}")]
MetricsExport(String),
#[error("Failed to write log entry: {0}")]
LogWrite(String),
#[error("Failed to export logs: {0}")]
LogExport(String),
#[error("Failed to create trace span: {0}")]
TraceSpanCreate(String),
#[error("Failed to export trace: {0}")]
TraceExport(String),
#[error("Authentication failed: {0}")]
AuthFailed(String),
#[error("Authorization failed: {0}")]
AuthorizationFailed(String),
#[error("Invalid credentials: {0}")]
InvalidCredentials(String),
#[error("Configuration error: {0}")]
ConfigError(String),
#[error("Missing required configuration: {0}")]
MissingConfig(String),
#[error("Invalid configuration value for '{key}': {reason}")]
InvalidConfig {
key: String,
reason: String,
},
#[error("Network error: {0}")]
Network(String),
#[error("Connection error: {0}")]
Connection(String),
#[error("Connection timeout: {0}")]
Timeout(String),
#[error("Authentication error: {0}")]
Authentication(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Deserialization error: {0}")]
Deserialization(String),
#[error("Cloud operation failed: {0}")]
OperationFailed(String),
#[error("Internal error: {0}")]
Internal(String),
}
pub type Result<T> = std::result::Result<T, CloudError>;
impl CloudError {
pub fn secret_fetch(name: impl Into<String>, error: impl Into<String>) -> Self {
Self::SecretFetch {
name: name.into(),
error: error.into(),
}
}
pub fn secret_create(name: impl Into<String>, error: impl Into<String>) -> Self {
Self::SecretCreate {
name: name.into(),
error: error.into(),
}
}
pub fn secret_update(name: impl Into<String>, error: impl Into<String>) -> Self {
Self::SecretUpdate {
name: name.into(),
error: error.into(),
}
}
pub fn storage_fetch(key: impl Into<String>, error: impl Into<String>) -> Self {
Self::StorageFetch {
key: key.into(),
error: error.into(),
}
}
pub fn storage_put(key: impl Into<String>, error: impl Into<String>) -> Self {
Self::StoragePut {
key: key.into(),
error: error.into(),
}
}
pub fn secret_delete(name: impl Into<String>, error: impl Into<String>) -> Self {
Self::SecretDelete {
name: name.into(),
error: error.into(),
}
}
pub fn storage_delete(key: impl Into<String>, error: impl Into<String>) -> Self {
Self::StorageDelete {
key: key.into(),
error: error.into(),
}
}
pub fn storage_read(key: impl Into<String>, error: impl Into<String>) -> Self {
Self::StorageRead {
key: key.into(),
error: error.into(),
}
}
pub fn storage_get(key: impl Into<String>, error: impl Into<String>) -> Self {
Self::StorageFetch {
key: key.into(),
error: error.into(),
}
}
pub fn storage_list(prefix: impl Into<String>, error: impl Into<String>) -> Self {
Self::StorageList {
prefix: prefix.into(),
error: error.into(),
}
}
}
impl From<anyhow::Error> for CloudError {
fn from(err: anyhow::Error) -> Self {
CloudError::Internal(err.to_string())
}
}
impl From<serde_json::Error> for CloudError {
fn from(err: serde_json::Error) -> Self {
CloudError::Serialization(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = CloudError::secret_fetch("my-secret", "connection refused");
assert_eq!(
err.to_string(),
"Failed to fetch secret 'my-secret': connection refused"
);
}
#[test]
fn test_secret_not_found() {
let err = CloudError::SecretNotFound("test-secret".to_string());
assert!(err.to_string().contains("Secret not found"));
}
#[test]
fn test_provider_not_enabled() {
let err = CloudError::ProviderNotEnabled("AWS".to_string());
assert!(err.to_string().contains("not enabled"));
assert!(err.to_string().contains("feature flag"));
}
#[test]
fn test_from_serde_error() {
let json_err = serde_json::from_str::<serde_json::Value>("{invalid}")
.unwrap_err();
let cloud_err: CloudError = json_err.into();
assert!(matches!(cloud_err, CloudError::Serialization(_)));
}
}