garage-sdk 0.1.1

Async Rust SDK for Garage S3-compatible storage with uploads and public URL generation
Documentation
use aws_sdk_s3::{Client, primitives::ByteStream};
use std::error::Error as StdError;
use std::future::Future;

/// Input payload for storage uploads.
#[derive(Debug)]
pub struct StorageInput {
    /// Object key in the bucket.
    pub key: String,
    /// Content type for the object.
    pub content_type: String,
    /// Object body.
    pub body: ByteStream,
}

/// Storage upload result.
#[derive(Debug)]
pub struct StorageResult {
    /// ETag returned by storage.
    pub etag: Option<String>,
}

/// Storage client abstraction for uploading content.
pub trait StorageClient {
    /// Error type for storage operations.
    type Error: StdError + Send + Sync + 'static;
    /// Future returned by storage operations.
    type Future<'a>: Future<Output = std::result::Result<StorageResult, Self::Error>> + Send
    where
        Self: 'a;

    /// Upload an object to storage.
    fn put_object<'a>(&'a self, input: StorageInput) -> Self::Future<'a>;
}

/// Default storage client using aws-sdk-s3.
#[derive(Clone, Debug)]
pub struct S3Storage {
    client: Client,
    bucket: String,
}

impl S3Storage {
    /// Create a new S3 storage client.
    pub fn new(client: Client, bucket: impl Into<String>) -> Self {
        Self {
            client,
            bucket: bucket.into(),
        }
    }
}

impl StorageClient for S3Storage {
    type Error = aws_sdk_s3::Error;
    type Future<'a> = std::pin::Pin<
        Box<dyn Future<Output = std::result::Result<StorageResult, Self::Error>> + Send + 'a>,
    >;

    fn put_object<'a>(&'a self, input: StorageInput) -> Self::Future<'a> {
        Box::pin(async move {
            let result = self
                .client
                .put_object()
                .bucket(&self.bucket)
                .key(&input.key)
                .content_type(&input.content_type)
                .body(input.body)
                .send()
                .await?;

            Ok(StorageResult {
                etag: result.e_tag().map(|s| s.trim_matches('"').to_string()),
            })
        })
    }
}