use bytes::Bytes;
use std::time::Duration;
use crate::auth::Credentials;
use crate::error::{CloudError, GcsError, Result};
use crate::retry::{RetryConfig, RetryExecutor};
use super::CloudStorageBackend;
#[derive(Debug, Clone, Copy)]
pub enum GcsStorageClass {
Standard,
Nearline,
Coldline,
Archive,
}
#[derive(Debug, Clone)]
pub struct GcsBackend {
pub bucket: String,
pub prefix: String,
pub project_id: Option<String>,
pub storage_class: GcsStorageClass,
pub timeout: Duration,
pub retry_config: RetryConfig,
pub credentials: Option<Credentials>,
}
impl GcsBackend {
#[must_use]
pub fn new(bucket: impl Into<String>) -> Self {
Self {
bucket: bucket.into(),
prefix: String::new(),
project_id: None,
storage_class: GcsStorageClass::Standard,
timeout: Duration::from_secs(300),
retry_config: RetryConfig::default(),
credentials: None,
}
}
#[must_use]
pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = prefix.into();
self
}
#[must_use]
pub fn with_project_id(mut self, project_id: impl Into<String>) -> Self {
self.project_id = Some(project_id.into());
self
}
#[must_use]
pub fn with_storage_class(mut self, storage_class: GcsStorageClass) -> Self {
self.storage_class = storage_class;
self
}
#[must_use]
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[must_use]
pub fn with_retry_config(mut self, config: RetryConfig) -> Self {
self.retry_config = config;
self
}
#[must_use]
pub fn with_credentials(mut self, credentials: Credentials) -> Self {
self.credentials = Some(credentials);
self
}
fn full_object_name(&self, key: &str) -> String {
if self.prefix.is_empty() {
key.to_string()
} else {
format!("{}/{}", self.prefix, key)
}
}
}
#[cfg(all(feature = "gcs", feature = "async"))]
#[async_trait::async_trait]
impl CloudStorageBackend for GcsBackend {
async fn get(&self, key: &str) -> Result<Bytes> {
let mut executor = RetryExecutor::new(self.retry_config.clone());
executor
.execute(|| async {
let object_name = self.full_object_name(key);
tracing::debug!("Getting GCS object: {}/{}", self.bucket, object_name);
Err(CloudError::Gcs(GcsError::Sdk {
message: "GCS SDK integration pending - requires google-cloud-storage setup"
.to_string(),
}))
})
.await
}
async fn put(&self, key: &str, data: &[u8]) -> Result<()> {
let mut executor = RetryExecutor::new(self.retry_config.clone());
executor
.execute(|| async {
let object_name = self.full_object_name(key);
tracing::debug!(
"Putting GCS object: {}/{} ({} bytes)",
self.bucket,
object_name,
data.len()
);
Err(CloudError::Gcs(GcsError::Sdk {
message: "GCS SDK integration pending - requires google-cloud-storage setup"
.to_string(),
}))
})
.await
}
async fn delete(&self, key: &str) -> Result<()> {
let mut executor = RetryExecutor::new(self.retry_config.clone());
executor
.execute(|| async {
let object_name = self.full_object_name(key);
tracing::debug!("Deleting GCS object: {}/{}", self.bucket, object_name);
Err(CloudError::Gcs(GcsError::Sdk {
message: "GCS SDK integration pending - requires google-cloud-storage setup"
.to_string(),
}))
})
.await
}
async fn exists(&self, key: &str) -> Result<bool> {
let object_name = self.full_object_name(key);
tracing::debug!(
"Checking GCS object exists: {}/{}",
self.bucket,
object_name
);
Err(CloudError::Gcs(GcsError::Sdk {
message: "GCS SDK integration pending - requires google-cloud-storage setup"
.to_string(),
}))
}
async fn list_prefix(&self, prefix: &str) -> Result<Vec<String>> {
let full_prefix = self.full_object_name(prefix);
tracing::debug!(
"Listing GCS objects: {} with prefix {}",
self.bucket,
full_prefix
);
Err(CloudError::Gcs(GcsError::Sdk {
message: "GCS SDK integration pending - requires google-cloud-storage setup"
.to_string(),
}))
}
fn is_readonly(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gcs_backend_new() {
let backend = GcsBackend::new("my-bucket");
assert_eq!(backend.bucket, "my-bucket");
assert_eq!(backend.prefix, "");
}
#[test]
fn test_gcs_backend_builder() {
let backend = GcsBackend::new("my-bucket")
.with_prefix("data/objects")
.with_project_id("my-project-123")
.with_storage_class(GcsStorageClass::Coldline)
.with_timeout(Duration::from_secs(600));
assert_eq!(backend.prefix, "data/objects");
assert_eq!(backend.project_id, Some("my-project-123".to_string()));
assert!(matches!(backend.storage_class, GcsStorageClass::Coldline));
assert_eq!(backend.timeout, Duration::from_secs(600));
}
#[test]
fn test_gcs_backend_full_object_name() {
let backend = GcsBackend::new("bucket").with_prefix("prefix");
assert_eq!(backend.full_object_name("file.txt"), "prefix/file.txt");
let backend_no_prefix = GcsBackend::new("bucket");
assert_eq!(backend_no_prefix.full_object_name("file.txt"), "file.txt");
}
}