use super::{wrap_internal_error, ReqContext, S3Handler};
use crate::dto::{
Delete, DeleteObjectsError, DeleteObjectsOutput, DeleteObjectsRequest, ObjectIdentifier,
};
use crate::errors::{S3Error, S3Result};
use crate::headers::{
X_AMZ_BYPASS_GOVERNANCE_RETENTION, X_AMZ_MFA, X_AMZ_REQUEST_CHARGED, X_AMZ_REQUEST_PAYER,
};
use crate::output::S3Output;
use crate::storage::S3Storage;
use crate::utils::body::deserialize_xml_body;
use crate::utils::{ResponseExt, XmlWriterExt};
use crate::{async_trait, Method, Response};
pub struct Handler;
#[async_trait]
impl S3Handler for Handler {
fn is_match(&self, ctx: &'_ ReqContext<'_>) -> bool {
bool_try!(ctx.req.method() == Method::POST);
bool_try!(ctx.path.is_bucket());
let qs = bool_try_some!(ctx.query_strings.as_ref());
qs.get("delete").is_some()
}
async fn handle(
&self,
ctx: &mut ReqContext<'_>,
storage: &(dyn S3Storage + Send + Sync),
) -> S3Result<Response> {
let input = extract(ctx).await?;
let output = storage.delete_objects(input).await;
output.try_into_response()
}
}
pub async fn extract(ctx: &mut ReqContext<'_>) -> S3Result<DeleteObjectsRequest> {
let bucket = ctx.unwrap_bucket_path();
let delete: self::xml::Delete = deserialize_xml_body(ctx.take_body())
.await
.map_err(|err| invalid_request!("Invalid xml format", err))?;
let mut input: DeleteObjectsRequest = DeleteObjectsRequest {
delete: delete.into(),
bucket: bucket.into(),
..DeleteObjectsRequest::default()
};
let h = &ctx.headers;
h.assign_str(&*X_AMZ_MFA, &mut input.mfa);
h.assign_str(&*X_AMZ_REQUEST_PAYER, &mut input.request_payer);
h.assign(
&*X_AMZ_BYPASS_GOVERNANCE_RETENTION,
&mut input.bypass_governance_retention,
)
.map_err(|err| invalid_request!("Invalid header: x-amz-bypass-governance-retention", err))?;
Ok(input)
}
impl S3Output for DeleteObjectsOutput {
#[allow(clippy::shadow_unrelated)]
fn try_into_response(self) -> S3Result<Response> {
wrap_internal_error(|res| {
res.set_optional_header(&*X_AMZ_REQUEST_CHARGED, self.request_charged)?;
let deleted = self.deleted;
let errors = self.errors;
res.set_xml_body(4096, |w| {
w.stack("DeleteResult", |w| {
if let Some(deleted) = deleted {
w.iter_element(deleted.into_iter(), |w, deleted_object| {
w.stack("Deleted", |w| {
w.opt_element(
"DeleteMarker",
deleted_object.delete_marker.map(|b| b.to_string()),
)?;
w.opt_element(
"DeleteMarkerVersionId",
deleted_object.delete_marker_version_id,
)?;
w.opt_element("Key", deleted_object.key)?;
w.opt_element("VersionId", deleted_object.version_id)?;
Ok(())
})
})?;
}
if let Some(errors) = errors {
w.iter_element(errors.into_iter(), |w, error| {
w.stack("Error", |w| {
w.opt_element("Code", error.code)?;
w.opt_element("Key", error.key)?;
w.opt_element("Message", error.message)?;
w.opt_element("VersionId", error.version_id)?;
Ok(())
})
})?;
}
Ok(())
})
})?;
Ok(())
})
}
}
impl From<DeleteObjectsError> for S3Error {
fn from(e: DeleteObjectsError) -> Self {
match e {}
}
}
mod xml {
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct ObjectIdentifier {
#[serde(rename = "Key")]
pub key: String,
#[serde(rename = "VersionId")]
pub version_id: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct Delete {
#[serde(rename = "Object")]
pub objects: Vec<ObjectIdentifier>,
#[serde(rename = "Quiet")]
pub quiet: Option<bool>,
}
impl From<ObjectIdentifier> for super::ObjectIdentifier {
fn from(ObjectIdentifier { key, version_id }: ObjectIdentifier) -> Self {
Self { key, version_id }
}
}
impl From<Delete> for super::Delete {
fn from(delete: Delete) -> Self {
Self {
quiet: delete.quiet,
objects: delete.objects.into_iter().map(Into::into).collect(),
}
}
}
}