s3-wasi-http 0.2.1

Basic S3 client using WASI HTTP
Documentation
use std::i32;

use anyhow::{Result, anyhow};
use wstd::http::{Body, Method};
use xml::{EventReader, reader::XmlEvent};

use super::{
    ApiObject, S3RequestBuilder, S3RequestData, S3ResponseData, parse_xml_bool, parse_xml_string,
    parse_xml_value,
};

#[derive(Default)]
pub struct ListObjectsV2Request {
    pub token: Option<String>,
    pub delimiter: Option<char>,
    pub encoding_type: Option<String>,
    pub fetch_owner: bool,
    pub max_keys: Option<i32>,
    pub start_after: Option<String>,
}


impl S3RequestData for ListObjectsV2Request {
    type ResponseType = ListObjectsV2Response;

    fn into_builder(
        &self,
        access_key: &str,
        secret_key: &str,
        region: &str,
        endpoint: &str,
    ) -> Result<S3RequestBuilder<Self::ResponseType>> {
        let mut builder =
            S3RequestBuilder::new(Method::GET, "/", access_key, secret_key, region, endpoint);
        builder.query("list-type", Some("2"));

        if let Some(token) = &self.token {
            builder.query("continuation-token", Some(token));
        }
        if let Some(delimiter) = &self.delimiter {
            builder.query("delimiter", Some(&delimiter.to_string()));
        }
        if let Some(encoding_type) = &self.encoding_type {
            builder.query("encoding-type", Some(encoding_type));
        }
        if self.fetch_owner {
            builder.query("fetch-owner", Some("true"));
        }
        if let Some(max_keys) = self.max_keys {
            builder.query("max-keys", Some(&max_keys.to_string()));
        }
        if let Some(start_after) = &self.start_after {
            builder.query("start-after", Some(start_after));
        }

        Ok(builder)
    }
}

pub struct ListObjectsV2Response {
    pub common_prefixes: Vec<String>,
    pub contents: Vec<ApiObject>,
    pub encoding_type: Option<String>,
    pub is_truncated: bool,
    pub key_count: i32,
    pub max_keys: i32,
    pub name: String,
    pub continuation_token: Option<String>,
    pub next_continuation_token: Option<String>,
    pub prefix: Option<String>,
    pub delimiter: Option<String>,
    pub start_after: Option<String>,
}

impl S3ResponseData for ListObjectsV2Response {
    async fn parse_body(response: &mut Body) -> Result<Self>
    where
        Self: Sized,
    {
        let data = response.contents().await?;
        let mut parser = EventReader::new(data);

        let mut list_objects_response = ListObjectsV2Response {
            common_prefixes: Vec::new(),
            contents: Vec::new(),
            encoding_type: None,
            is_truncated: false,
            key_count: 0,
            max_keys: 0,
            name: String::new(),
            continuation_token: None,
            next_continuation_token: None,
            prefix: None,
            delimiter: None,
            start_after: None,
        };
        loop {
            let element = parser.next()?;

            match element {
                xml::reader::XmlEvent::EndDocument => break,

                xml::reader::XmlEvent::StartElement { name, .. }
                    if name.local_name == "IsTruncated" =>
                {
                    list_objects_response.is_truncated =
                        parse_xml_bool(&mut parser, "IsTruncated")?;
                }
                xml::reader::XmlEvent::StartElement { name, .. } if name.local_name == "Name" => {
                    list_objects_response.name = parse_xml_string(&mut parser, "Name")?;
                }
                xml::reader::XmlEvent::StartElement { name, .. } if name.local_name == "Prefix" => {
                    if let XmlEvent::Characters(value) = parser.next()? {
                        list_objects_response.prefix = Some(value);
                    }
                }
                xml::reader::XmlEvent::StartElement { name, .. }
                    if name.local_name == "Delimiter" =>
                {
                    list_objects_response.delimiter =
                        Some(parse_xml_string(&mut parser, "Delimiter")?);
                }
                xml::reader::XmlEvent::StartElement { name, .. }
                    if name.local_name == "MaxKeys" =>
                {
                    list_objects_response.max_keys =
                        parse_xml_value::<i32>(&mut parser, "MaxKeys")?;
                }
                xml::reader::XmlEvent::StartElement { name, .. }
                    if name.local_name == "EncodingType" =>
                {
                    list_objects_response.encoding_type =
                        Some(parse_xml_string(&mut parser, "EncodingType")?);
                }
                xml::reader::XmlEvent::StartElement { name, .. }
                    if name.local_name == "KeyCount" =>
                {
                    list_objects_response.key_count =
                        parse_xml_value::<i32>(&mut parser, "KeyCount")?;
                }
                xml::reader::XmlEvent::StartElement { name, .. }
                    if name.local_name == "ContinuationToken" =>
                {
                    list_objects_response.continuation_token =
                        Some(parse_xml_string(&mut parser, "ContinuationToken")?);
                }
                xml::reader::XmlEvent::StartElement { name, .. }
                    if name.local_name == "NextContinuationToken" =>
                {
                    list_objects_response.next_continuation_token =
                        Some(parse_xml_string(&mut parser, "NextContinuationToken")?);
                }
                xml::reader::XmlEvent::StartElement { name, .. }
                    if name.local_name == "StartAfter" =>
                {
                    list_objects_response.next_continuation_token =
                        Some(parse_xml_string(&mut parser, "StartAfter")?);
                }

                xml::reader::XmlEvent::StartElement { name, .. }
                    if name.local_name == "CommonPrefixes" =>
                {
                    if let XmlEvent::StartElement { name, .. } = parser.next()? {
                        if name.local_name == "Prefix" {
                            if let XmlEvent::Characters(value) = parser.next()? {
                                list_objects_response.common_prefixes.push(value);
                            } else {
                                return Err(anyhow!(
                                    "Invalid response object, CommonPrefixes.Prefix has no value"
                                ));
                            }
                        } else {
                            return Err(anyhow!(
                                "Invalid response object, CommonPrefixes has no value"
                            ));
                        }
                    } else {
                        return Err(anyhow!(
                            "Invalid response object, CommonPrefixes has no value"
                        ));
                    }
                }
                xml::reader::XmlEvent::StartElement { name, .. }
                    if name.local_name == "Contents" =>
                {
                    list_objects_response
                        .contents
                        .push(ApiObject::parse(&mut parser)?);
                }

                _ => {}
            }
        }

        Ok(list_objects_response)
    }
}