use crate::error::{CloudError, Result};
use async_trait::async_trait;
use std::time::SystemTime;
#[derive(Debug, Clone)]
pub struct ObjectMetadata {
pub size: u64,
pub last_modified: SystemTime,
pub content_type: Option<String>,
pub etag: Option<String>,
pub storage_class: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct PutObjectOptions {
pub content_type: Option<String>,
pub storage_class: Option<String>,
pub encryption: Option<String>,
pub metadata: Vec<(String, String)>,
}
#[derive(Debug, Clone, Default)]
pub struct GetObjectOptions {
pub range: Option<(u64, u64)>,
pub if_match: Option<String>,
}
#[async_trait]
pub trait CloudStorage: Send + Sync {
async fn get_object(&self, key: &str) -> Result<Vec<u8>>;
async fn get_object_with_options(
&self,
key: &str,
options: &GetObjectOptions,
) -> Result<Vec<u8>> {
self.get_object(key).await
}
async fn put_object(&self, key: &str, data: &[u8]) -> Result<()>;
async fn put_object_with_options(
&self,
key: &str,
data: &[u8],
options: &PutObjectOptions,
) -> Result<()> {
self.put_object(key, data).await
}
async fn delete_object(&self, key: &str) -> Result<()>;
async fn list_objects(&self, prefix: &str) -> Result<Vec<String>>;
async fn list_objects_with_limit(
&self,
prefix: &str,
max_results: usize,
) -> Result<Vec<String>> {
let mut objects = self.list_objects(prefix).await?;
objects.truncate(max_results);
Ok(objects)
}
async fn object_exists(&self, key: &str) -> Result<bool> {
match self.get_object_metadata(key).await {
Ok(_) => Ok(true),
Err(CloudError::StorageObjectNotFound(_)) => Ok(false),
Err(e) => Err(e),
}
}
async fn get_object_metadata(&self, key: &str) -> Result<ObjectMetadata>;
async fn copy_object(&self, from_key: &str, to_key: &str) -> Result<()> {
let data = self.get_object(from_key).await?;
self.put_object(to_key, &data).await
}
async fn move_object(&self, from_key: &str, to_key: &str) -> Result<()> {
self.copy_object(from_key, to_key).await?;
self.delete_object(from_key).await
}
fn provider_name(&self) -> &str {
"unknown"
}
async fn delete_objects(&self, keys: &[String]) -> Result<()> {
for key in keys {
self.delete_object(key).await?;
}
Ok(())
}
async fn list_objects_with_metadata(&self, prefix: &str) -> Result<Vec<ObjectMetadata>> {
Err(CloudError::OperationFailed(
"list_objects_with_metadata not implemented".to_string(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_object_metadata() {
let metadata = ObjectMetadata {
size: 1024,
last_modified: SystemTime::now(),
content_type: Some("application/json".to_string()),
etag: Some("abc123".to_string()),
storage_class: Some("STANDARD".to_string()),
};
assert_eq!(metadata.size, 1024);
assert!(metadata.content_type.is_some());
assert_eq!(metadata.content_type.unwrap(), "application/json");
}
#[test]
fn test_put_object_options_default() {
let options = PutObjectOptions::default();
assert!(options.content_type.is_none());
assert!(options.storage_class.is_none());
assert!(options.encryption.is_none());
assert_eq!(options.metadata.len(), 0);
}
#[test]
fn test_put_object_options_builder() {
let options = PutObjectOptions {
content_type: Some("text/plain".to_string()),
storage_class: Some("GLACIER".to_string()),
encryption: Some("AES256".to_string()),
metadata: vec![
("author".to_string(), "John Doe".to_string()),
("version".to_string(), "1.0".to_string()),
],
};
assert_eq!(options.content_type.unwrap(), "text/plain");
assert_eq!(options.storage_class.unwrap(), "GLACIER");
assert_eq!(options.metadata.len(), 2);
}
#[test]
fn test_get_object_options() {
let options = GetObjectOptions {
range: Some((0, 1023)),
if_match: Some("etag-123".to_string()),
};
assert!(options.range.is_some());
assert_eq!(options.range.unwrap(), (0, 1023));
assert_eq!(options.if_match.unwrap(), "etag-123");
}
}