s3 0.1.23

A lean, modern, unofficial S3-compatible client for Rust.
#[cfg(all(feature = "async", feature = "multipart"))]
use std::{
    env,
    time::{SystemTime, UNIX_EPOCH},
};

#[cfg(all(feature = "async", feature = "multipart"))]
use s3::{Auth, Client, Error};

#[cfg(all(feature = "async", feature = "multipart"))]
#[allow(clippy::result_large_err)]
#[tokio::main]
async fn main() -> Result<(), s3::Error> {
    let endpoint = match env::var("S3_TEST_ENDPOINT") {
        Ok(v) => v,
        Err(_) => {
            eprintln!("Set S3_TEST_ENDPOINT to run this example (e.g. http://127.0.0.1:9000).");
            return Ok(());
        }
    };

    let bucket = match env::var("S3_TEST_BUCKET") {
        Ok(v) => v,
        Err(_) => {
            eprintln!("Set S3_TEST_BUCKET to a bucket you can write to.");
            return Ok(());
        }
    };

    let region = env::var("S3_TEST_REGION").unwrap_or_else(|_| "us-east-1".to_string());
    let auth = match Auth::from_env() {
        Ok(v) => v,
        Err(err) => {
            eprintln!("Set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY: {err}");
            return Ok(());
        }
    };

    let client = Client::builder(&endpoint)?
        .region(region)
        .auth(auth)
        .build()?;

    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis();
    let key = format!("examples/multipart-{now}.bin");

    let created = client
        .objects()
        .create_multipart_upload(&bucket, &key)
        .send()
        .await?;
    let upload_id = created.upload_id.clone();

    let upload = async {
        let part1 = vec![b'a'; 5 * 1024 * 1024];
        let out1 = client
            .objects()
            .upload_part(&bucket, &key, &upload_id, 1)
            .body_bytes(part1)
            .send()
            .await?;
        let etag1 = out1
            .etag
            .ok_or_else(|| Error::decode("missing etag for part 1", None))?;

        let out2 = client
            .objects()
            .upload_part(&bucket, &key, &upload_id, 2)
            .body_bytes(b"done\n".to_vec())
            .send()
            .await?;
        let etag2 = out2
            .etag
            .ok_or_else(|| Error::decode("missing etag for part 2", None))?;

        client
            .objects()
            .complete_multipart_upload(&bucket, &key, &upload_id)
            .part(1, etag1)
            .part(2, etag2)
            .send()
            .await?;
        Ok::<(), s3::Error>(())
    }
    .await;

    if let Err(err) = upload {
        let _ = client
            .objects()
            .abort_multipart_upload(&bucket, &key, &upload_id)
            .send()
            .await;
        return Err(err);
    }

    let got = client.objects().get(&bucket, &key).send().await?;
    let bytes = got.bytes().await?;
    println!("uploaded {} bytes via multipart upload", bytes.len());

    client.objects().delete(&bucket, &key).send().await?;
    Ok(())
}

#[cfg(not(all(feature = "async", feature = "multipart")))]
fn main() {
    eprintln!("This example requires `async` and `multipart`.");
    eprintln!("Try: cargo run --example async_multipart_upload --features multipart");
}