wasmio 0.0.13

WasmIO a S3 Implementation on steroid
Documentation
use axum::async_trait;
use axum::body::Body;
use axum::extract::Query;
use axum::http::{Method, StatusCode};
use axum::response::Response;
use if_chain::if_chain;
use tracing::{info, warn};
use wasmio_aws_types::types::ListObjectsV2Request;

use crate::application::s3::axum::{header_string_opt, RequestExt};
use crate::application::s3::context::{Context, S3Handler};
use crate::application::s3::errors::{S3Error, S3ErrorCodeKind};
use crate::application::s3::headers::{self};
use crate::application::s3::state::S3State;
use crate::domain::storage::errors::BucketStorageError;
use crate::domain::storage::BackendDriver;
use crate::infrastructure::storage::BackendStorage;

#[derive(Clone, Copy)]
pub struct ObjectListHandlerV2;

#[derive(serde::Deserialize)]
pub struct ObjectListV2QS {
    #[serde(rename = "list-type")]
    list_type: i8,
    #[serde(rename = "continuation-token")]
    continuation_token: Option<String>,
    delimiter: Option<String>,
    #[serde(rename = "encoding-type")]
    encoding_type: Option<String>,
    #[serde(rename = "fetch-owner")]
    fetch_owner: Option<bool>,
    #[serde(rename = "max-keys")]
    max_keys: Option<i64>,
    #[serde(rename = "prefix")]
    prefix: Option<String>,
    #[serde(rename = "start-after")]
    start_after: Option<String>,
}

#[async_trait]
impl S3Handler for ObjectListHandlerV2 {
    #[inline]
    fn is_match(&self, ctx: &Context) -> bool {
        if_chain! {
            if ctx.method() == Method::GET;
            if ctx.path().is_bucket();
            if let Ok(Query(qs)) = Query::<ObjectListV2QS>::try_from_uri(&ctx.parts().uri);
            if qs.list_type == 2;
            then {
                true
            } else {
                false
            }
        }
    }

    async fn handle<T: BackendDriver>(
        &self,
        ctx: Context,
        state: S3State<T>,
    ) -> Result<Response, S3Error>
    where
        BucketStorageError: From<<T as BackendStorage>::Error>,
    {
        let bucket_name = ctx.expect_bucket()?;
        let Query(ObjectListV2QS {
            list_type: _,
            continuation_token,
            delimiter,
            encoding_type,
            fetch_owner,
            max_keys,
            prefix,
            start_after,
        }) = Query::<ObjectListV2QS>::try_from_uri(&ctx.parts().uri)
            .expect("Can't fail as we already checked.");

        info!(
            message = "Trying to list elements",
            bucket = %bucket_name,
        );
        let map = &ctx.parts().headers;

        let request = ListObjectsV2Request {
            bucket: bucket_name.into(),
            encoding_type,
            fetch_owner,
            max_keys,
            prefix,
            start_after,
            continuation_token,
            delimiter,
            expected_bucket_owner: header_string_opt(
                headers::X_AMZ_EXPECTED_BUCKET_OWNER,
                map,
            ),
            request_payer: header_string_opt(headers::X_AMZ_REQUEST_PAYER, map),
        };

        let result = state.bucket_loader.list_object_v2(request).await?;

        let xml = quick_xml::se::to_string(&result).map_err(|err| {
            warn!("{err}");
            S3Error::from(S3ErrorCodeKind::MalformedXML)
        })?;

        let body = format!(
            r###"<?xml version="1.0" encoding="UTF-8"?>
{xml}
"###,
            xml = xml
        );

        Ok(Response::builder()
            .status(StatusCode::OK)
            .header_opt(headers::X_AMZ_REQUEST_CHARGED, Some("unimplemented"))
            .body(Body::new(body))
            .unwrap())
    }
}