s3-wire 0.3.1

An async, streaming S3-compatible client for Rust
Documentation
use serde::Deserialize;
use time::{OffsetDateTime, format_description::well_known::Rfc3339};

use super::common::root_is_error;
use crate::operation::{Checksum, CopyObjectOutput};
use crate::protocol::xml::{expect_root, parse_bounded};
use crate::protocol::{ParsedS3Error, ProtocolError, parse_s3_error};

pub(crate) enum CopyObjectResponse {
    Complete(CopyObjectOutput),
    EmbeddedError(ParsedS3Error),
}

pub(crate) enum UploadPartCopyResponse {
    Complete(CopyPartResult),
    EmbeddedError(ParsedS3Error),
}

pub(crate) struct CopyPartResult {
    pub e_tag: String,
    pub last_modified: Option<OffsetDateTime>,
    pub checksum: Checksum,
}

#[derive(Deserialize)]
#[serde(rename = "CopyObjectResult")]
struct CopyResultDocument {
    #[serde(rename = "ETag")]
    e_tag: Option<String>,
    #[serde(rename = "LastModified")]
    last_modified: Option<String>,
    #[serde(rename = "ChecksumCRC32")]
    checksum_crc32: Option<String>,
    #[serde(rename = "ChecksumCRC32C")]
    checksum_crc32c: Option<String>,
    #[serde(rename = "ChecksumCRC64NVME")]
    checksum_crc64_nvme: Option<String>,
    #[serde(rename = "ChecksumSHA1")]
    checksum_sha1: Option<String>,
    #[serde(rename = "ChecksumSHA256")]
    checksum_sha256: Option<String>,
}

pub(crate) fn parse_copy_object(
    body: &[u8],
    maximum: usize,
) -> Result<CopyObjectResponse, ProtocolError> {
    if root_is_error(body, maximum)? {
        return parse_s3_error(body, maximum).map(CopyObjectResponse::EmbeddedError);
    }
    expect_root(body, maximum, &[b"CopyObjectResult"])?;
    let document: CopyResultDocument = parse_bounded(body, maximum)?;
    let last_modified = document
        .last_modified
        .map(|value| {
            OffsetDateTime::parse(&value, &Rfc3339).map_err(|error| ProtocolError::InvalidField {
                field: "LastModified",
                reason: error.to_string(),
            })
        })
        .transpose()?;
    Ok(CopyObjectResponse::Complete(CopyObjectOutput {
        e_tag: document.e_tag,
        last_modified,
        version_id: None,
        checksum: Checksum {
            crc32: document.checksum_crc32,
            crc32c: document.checksum_crc32c,
            crc64_nvme: document.checksum_crc64_nvme,
            sha1: document.checksum_sha1,
            sha256: document.checksum_sha256,
        },
        request_ids: Default::default(),
    }))
}

pub(crate) fn parse_upload_part_copy(
    body: &[u8],
    maximum: usize,
) -> Result<UploadPartCopyResponse, ProtocolError> {
    if root_is_error(body, maximum)? {
        return parse_s3_error(body, maximum).map(UploadPartCopyResponse::EmbeddedError);
    }
    expect_root(body, maximum, &[b"CopyPartResult"])?;
    let document: CopyResultDocument = parse_bounded(body, maximum)?;
    let checksum = document.checksum();
    let e_tag = document
        .e_tag
        .filter(|value| !value.trim().is_empty())
        .ok_or_else(|| ProtocolError::InvalidField {
            field: "ETag",
            reason: "copied part has no usable ETag".to_owned(),
        })?;
    let last_modified = parse_last_modified(document.last_modified)?;
    Ok(UploadPartCopyResponse::Complete(CopyPartResult {
        e_tag,
        last_modified,
        checksum,
    }))
}

fn parse_last_modified(value: Option<String>) -> Result<Option<OffsetDateTime>, ProtocolError> {
    value
        .map(|value| {
            OffsetDateTime::parse(&value, &Rfc3339).map_err(|error| ProtocolError::InvalidField {
                field: "LastModified",
                reason: error.to_string(),
            })
        })
        .transpose()
}

impl CopyResultDocument {
    fn checksum(&self) -> Checksum {
        Checksum {
            crc32: self.checksum_crc32.clone(),
            crc32c: self.checksum_crc32c.clone(),
            crc64_nvme: self.checksum_crc64_nvme.clone(),
            sha1: self.checksum_sha1.clone(),
            sha256: self.checksum_sha256.clone(),
        }
    }
}