use crate::s3::error::ValidationErr;
use crate::s3::header_constants::*;
use crate::s3::types::{BucketName, ETag, ObjectKey, Region, S3Request, VersionId};
use crate::s3::utils::{ChecksumAlgorithm, get_text_result, parse_bool, trim_quotes};
use bytes::{Buf, Bytes};
use http::HeaderMap;
use std::collections::HashMap;
use xmltree::Element;
#[macro_export]
macro_rules! impl_from_s3response {
($($ty:ty),* $(,)?) => {
$(
#[async_trait::async_trait]
impl $crate::s3::types::FromS3Response for $ty {
async fn from_s3response(
request: $crate::s3::types::S3Request,
response: Result<reqwest::Response, $crate::s3::error::Error>,
) -> Result<Self, $crate::s3::error::Error> {
let mut resp: reqwest::Response = response?;
Ok(Self {
request,
headers: std::mem::take(resp.headers_mut()),
body: resp.bytes().await.map_err($crate::s3::error::ValidationErr::from)?,
})
}
}
)*
};
}
#[macro_export]
macro_rules! impl_from_s3response_with_size {
($($ty:ty),* $(,)?) => {
$(
#[async_trait::async_trait]
impl $crate::s3::types::FromS3Response for $ty {
async fn from_s3response(
request: $crate::s3::types::S3Request,
response: Result<reqwest::Response, $crate::s3::error::Error>,
) -> Result<Self, $crate::s3::error::Error> {
let mut resp: reqwest::Response = response?;
Ok(Self {
request,
headers: std::mem::take(resp.headers_mut()),
body: resp.bytes().await.map_err($crate::s3::error::ValidationErr::from)?,
object_size: 0, })
}
}
)*
};
}
#[macro_export]
macro_rules! impl_has_s3fields {
($($ty:ty),* $(,)?) => {
$(
impl $crate::s3::response_traits::HasS3Fields for $ty {
#[inline]
fn request(&self) -> &$crate::s3::types::S3Request {
&self.request
}
#[inline]
fn headers(&self) -> &http::HeaderMap {
&self.headers
}
#[inline]
fn body(&self) -> &bytes::Bytes {
&self.body
}
}
)*
};
}
pub trait HasS3Fields {
fn request(&self) -> &S3Request;
fn headers(&self) -> &HeaderMap;
fn body(&self) -> &Bytes;
}
pub trait HasBucket: HasS3Fields {
#[inline]
fn bucket(&self) -> Option<&BucketName> {
self.request().bucket.as_ref()
}
}
pub trait HasObject: HasS3Fields {
#[inline]
fn object(&self) -> Option<&ObjectKey> {
self.request().object.as_ref()
}
}
pub trait HasRegion: HasS3Fields {
#[inline]
fn region(&self) -> &Region {
&self.request().inner_region
}
}
pub trait HasVersion: HasS3Fields {
#[inline]
fn version_id(&self) -> Option<VersionId> {
self.headers()
.get(X_AMZ_VERSION_ID)
.and_then(|v| v.to_str().ok())
.and_then(|s| VersionId::new(s).ok())
}
}
pub trait HasEtagFromHeaders: HasS3Fields {
#[inline]
fn etag(&self) -> Result<ETag, ValidationErr> {
let etag_str = self
.headers()
.get("etag")
.and_then(|v| v.to_str().ok())
.map(|s| s.trim_matches('"'))
.unwrap_or_default();
ETag::new(etag_str)
}
}
pub trait HasEtagFromBody: HasS3Fields {
fn etag(&self) -> Result<ETag, ValidationErr> {
let root = xmltree::Element::parse(self.body().clone().reader())?;
let etag_str: String = get_text_result(&root, "ETag")?;
ETag::new(trim_quotes(etag_str))
}
}
pub trait HasObjectSize: HasS3Fields {
#[inline]
fn object_size(&self) -> u64 {
self.headers()
.get(X_AMZ_OBJECT_SIZE)
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0)
}
}
pub trait HasIsDeleteMarker: HasS3Fields {
#[inline]
fn is_delete_marker(&self) -> Result<bool, ValidationErr> {
self.headers()
.get(X_AMZ_DELETE_MARKER)
.map_or(Ok(false), |v| parse_bool(v.to_str()?))
}
}
pub trait HasTagging: HasS3Fields {
#[inline]
fn tags(&self) -> Result<HashMap<String, String>, ValidationErr> {
let mut tags = HashMap::new();
if self.body().is_empty() {
return Ok(tags);
}
let mut root = Element::parse(self.body().clone().reader())?;
let element = root
.get_mut_child("TagSet")
.ok_or(ValidationErr::xml_error("<TagSet> tag not found"))?;
while let Some(v) = element.take_child("Tag") {
tags.insert(get_text_result(&v, "Key")?, get_text_result(&v, "Value")?);
}
Ok(tags)
}
}
pub trait HasChecksumHeaders: HasS3Fields {
#[inline]
fn get_checksum(&self, algorithm: ChecksumAlgorithm) -> Option<String> {
let header_name = match algorithm {
ChecksumAlgorithm::CRC32 => X_AMZ_CHECKSUM_CRC32,
ChecksumAlgorithm::CRC32C => X_AMZ_CHECKSUM_CRC32C,
ChecksumAlgorithm::SHA1 => X_AMZ_CHECKSUM_SHA1,
ChecksumAlgorithm::SHA256 => X_AMZ_CHECKSUM_SHA256,
ChecksumAlgorithm::CRC64NVME => X_AMZ_CHECKSUM_CRC64NVME,
};
self.headers()
.get(header_name)
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string())
}
#[inline]
fn checksum_type(&self) -> Option<String> {
self.headers()
.get(X_AMZ_CHECKSUM_TYPE)
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string())
}
#[inline]
fn detect_checksum_algorithm(&self) -> Option<ChecksumAlgorithm> {
if self.headers().contains_key(X_AMZ_CHECKSUM_CRC32) {
Some(ChecksumAlgorithm::CRC32)
} else if self.headers().contains_key(X_AMZ_CHECKSUM_CRC32C) {
Some(ChecksumAlgorithm::CRC32C)
} else if self.headers().contains_key(X_AMZ_CHECKSUM_CRC64NVME) {
Some(ChecksumAlgorithm::CRC64NVME)
} else if self.headers().contains_key(X_AMZ_CHECKSUM_SHA1) {
Some(ChecksumAlgorithm::SHA1)
} else if self.headers().contains_key(X_AMZ_CHECKSUM_SHA256) {
Some(ChecksumAlgorithm::SHA256)
} else {
None
}
}
}