use oxigdal_core::error::{IoError, OxiGdalError};
pub type Result<T> = core::result::Result<T, CloudError>;
#[derive(Debug, thiserror::Error)]
pub enum CloudError {
#[error("I/O error: {0}")]
Io(#[from] IoError),
#[error("S3 error: {0}")]
S3(#[from] S3Error),
#[error("Azure error: {0}")]
Azure(#[from] AzureError),
#[error("GCS error: {0}")]
Gcs(#[from] GcsError),
#[error("HTTP error: {0}")]
Http(#[from] HttpError),
#[error("Authentication error: {0}")]
Auth(#[from] AuthError),
#[error("Retry error: {0}")]
Retry(#[from] RetryError),
#[error("Cache error: {0}")]
Cache(#[from] CacheError),
#[error("Invalid URL: {url}")]
InvalidUrl {
url: String,
},
#[error("Unsupported protocol: {protocol}")]
UnsupportedProtocol {
protocol: String,
},
#[error("Object not found: {key}")]
NotFound {
key: String,
},
#[error("Permission denied: {message}")]
PermissionDenied {
message: String,
},
#[error("Operation timeout: {message}")]
Timeout {
message: String,
},
#[error("Rate limit exceeded: {message}")]
RateLimitExceeded {
message: String,
},
#[error("Invalid configuration: {message}")]
InvalidConfiguration {
message: String,
},
#[error("Operation not supported: {operation}")]
NotSupported {
operation: String,
},
#[error("Internal error: {message}")]
Internal {
message: String,
},
}
#[derive(Debug, thiserror::Error)]
pub enum S3Error {
#[error("S3 SDK error: {message}")]
Sdk {
message: String,
},
#[error("Bucket not found: {bucket}")]
BucketNotFound {
bucket: String,
},
#[error("Access denied to bucket '{bucket}': {message}")]
AccessDenied {
bucket: String,
message: String,
},
#[error("Invalid bucket name: {bucket}")]
InvalidBucketName {
bucket: String,
},
#[error("Object too large: {size} bytes (max: {max_size})")]
ObjectTooLarge {
size: u64,
max_size: u64,
},
#[error("Multipart upload error: {message}")]
MultipartUpload {
message: String,
},
#[error("STS assume role error: {message}")]
StsAssumeRole {
message: String,
},
#[error("Region error: {message}")]
Region {
message: String,
},
}
#[derive(Debug, thiserror::Error)]
pub enum AzureError {
#[error("Azure SDK error: {message}")]
Sdk {
message: String,
},
#[error("Container not found: {container}")]
ContainerNotFound {
container: String,
},
#[error("Blob not found: {blob}")]
BlobNotFound {
blob: String,
},
#[error("Access denied to container '{container}': {message}")]
AccessDenied {
container: String,
message: String,
},
#[error("Invalid SAS token: {message}")]
InvalidSasToken {
message: String,
},
#[error("Account error: {message}")]
Account {
message: String,
},
#[error("Lease error: {message}")]
Lease {
message: String,
},
}
#[derive(Debug, thiserror::Error)]
pub enum GcsError {
#[error("GCS SDK error: {message}")]
Sdk {
message: String,
},
#[error("Bucket not found: {bucket}")]
BucketNotFound {
bucket: String,
},
#[error("Object not found: {object}")]
ObjectNotFound {
object: String,
},
#[error("Access denied to bucket '{bucket}': {message}")]
AccessDenied {
bucket: String,
message: String,
},
#[error("Invalid project ID: {project_id}")]
InvalidProjectId {
project_id: String,
},
#[error("Service account error: {message}")]
ServiceAccount {
message: String,
},
#[error("Signed URL error: {message}")]
SignedUrl {
message: String,
},
}
#[derive(Debug, thiserror::Error)]
pub enum HttpError {
#[error("Network error: {message}")]
Network {
message: String,
},
#[error("HTTP {status}: {message}")]
Status {
status: u16,
message: String,
},
#[error("Invalid header '{name}': {message}")]
InvalidHeader {
name: String,
message: String,
},
#[error("Request build error: {message}")]
RequestBuild {
message: String,
},
#[error("Response parse error: {message}")]
ResponseParse {
message: String,
},
#[error("TLS error: {message}")]
Tls {
message: String,
},
}
#[derive(Debug, thiserror::Error)]
pub enum AuthError {
#[error("Credentials not found: {message}")]
CredentialsNotFound {
message: String,
},
#[error("Invalid credentials: {message}")]
InvalidCredentials {
message: String,
},
#[error("Token expired: {message}")]
TokenExpired {
message: String,
},
#[error("OAuth2 error: {message}")]
OAuth2 {
message: String,
},
#[error("Service account key error: {message}")]
ServiceAccountKey {
message: String,
},
#[error("API key error: {message}")]
ApiKey {
message: String,
},
#[error("SAS token error: {message}")]
SasToken {
message: String,
},
#[error("IAM role error: {message}")]
IamRole {
message: String,
},
}
#[derive(Debug, thiserror::Error)]
pub enum RetryError {
#[error("Maximum retries exceeded: {attempts} attempts")]
MaxRetriesExceeded {
attempts: usize,
},
#[error("Circuit breaker open: {message}")]
CircuitBreakerOpen {
message: String,
},
#[error("Retry budget exhausted: {message}")]
BudgetExhausted {
message: String,
},
#[error("Non-retryable error: {message}")]
NonRetryable {
message: String,
},
}
#[derive(Debug, thiserror::Error)]
pub enum CacheError {
#[error("Cache miss for key: {key}")]
Miss {
key: String,
},
#[error("Cache write error: {message}")]
WriteError {
message: String,
},
#[error("Cache read error: {message}")]
ReadError {
message: String,
},
#[error("Cache invalidation error: {message}")]
InvalidationError {
message: String,
},
#[error("Cache full: {message}")]
Full {
message: String,
},
#[error("Compression error: {message}")]
Compression {
message: String,
},
#[error("Decompression error: {message}")]
Decompression {
message: String,
},
}
impl From<OxiGdalError> for CloudError {
fn from(err: OxiGdalError) -> Self {
match err {
OxiGdalError::Io(e) => Self::Io(e),
OxiGdalError::NotSupported { operation } => Self::NotSupported { operation },
OxiGdalError::Internal { message } => Self::Internal { message },
_ => Self::Internal {
message: format!("{err}"),
},
}
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for CloudError {
fn from(err: std::io::Error) -> Self {
Self::Io(err.into())
}
}
impl From<url::ParseError> for CloudError {
fn from(err: url::ParseError) -> Self {
Self::InvalidUrl {
url: err.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = CloudError::NotFound {
key: "test/file.txt".to_string(),
};
assert!(err.to_string().contains("test/file.txt"));
}
#[test]
fn test_s3_error() {
let err = S3Error::BucketNotFound {
bucket: "my-bucket".to_string(),
};
assert!(err.to_string().contains("my-bucket"));
}
#[test]
fn test_azure_error() {
let err = AzureError::ContainerNotFound {
container: "my-container".to_string(),
};
assert!(err.to_string().contains("my-container"));
}
#[test]
fn test_gcs_error() {
let err = GcsError::BucketNotFound {
bucket: "my-bucket".to_string(),
};
assert!(err.to_string().contains("my-bucket"));
}
#[test]
fn test_auth_error() {
let err = AuthError::TokenExpired {
message: "Token expired at 2026-01-25".to_string(),
};
assert!(err.to_string().contains("expired"));
}
#[test]
fn test_retry_error() {
let err = RetryError::MaxRetriesExceeded { attempts: 5 };
assert!(err.to_string().contains("5"));
}
#[test]
fn test_cache_error() {
let err = CacheError::Miss {
key: "cache-key".to_string(),
};
assert!(err.to_string().contains("cache-key"));
}
}