use crate::s3::{
S3,
error::{ApiError, Error, Result},
responses::ErrorResponse,
signature::Signature,
};
use quick_xml::de::from_str;
use reqwest::{Method, Response};
use std::{collections::BTreeMap, fmt::Write};
use url::Url;
mod listbuckets;
pub use self::listbuckets::ListBuckets;
mod listobjectsv2;
pub use self::listobjectsv2::ListObjectsV2;
mod listobjectversions;
pub use self::listobjectversions::ListObjectVersions;
mod headobject;
pub use self::headobject::HeadObject;
mod getobject;
pub use self::getobject::GetObject;
mod getobjectacl;
pub use self::getobjectacl::GetObjectAcl;
mod getobjectattributes;
pub use self::getobjectattributes::GetObjectAttributes;
mod putobject;
pub use self::putobject::PutObject;
mod putobjectacl;
pub use self::putobjectacl::PutObjectAcl;
mod createmultipartupload;
pub use self::createmultipartupload::CreateMultipartUpload;
mod uploadpart;
pub use self::uploadpart::UploadPart;
mod streampart;
pub use self::streampart::StreamPart;
mod completemultipartupload;
pub use self::completemultipartupload::{CompleteMultipartUpload, Part};
mod listmultipartuploads;
pub use self::listmultipartuploads::ListMultipartUploads;
mod abortmultipartupload;
pub use self::abortmultipartupload::AbortMultipartUpload;
mod deleteobject;
pub use self::deleteobject::DeleteObject;
mod deleteobjects;
pub use self::deleteobjects::{DeleteObjects, ObjectIdentifier};
mod createbucket;
pub use self::createbucket::CreateBucket;
mod deletebucket;
pub use self::deletebucket::DeleteBucket;
pub trait Action {
fn headers(&self) -> Option<BTreeMap<&str, &str>>;
fn http_method(&self) -> Result<Method>;
fn query_pairs(&self) -> Option<BTreeMap<&str, &str>>;
fn path(&self) -> Option<Vec<&str>>;
fn sign(
&self,
s3: &S3,
hash_payload: &[u8],
md5: Option<&[u8]>,
content_length: Option<usize>,
) -> Result<(Url, BTreeMap<String, String>)> {
let mut url = s3.endpoint()?;
if let Some(path) = self.path() {
for p in path {
url.path_segments_mut()
.map_err(|e| Error::Other(format!("cannot be base: {e:#?}")))?
.push(p);
}
}
if let Some(pairs) = &self.query_pairs() {
for (k, v) in pairs {
url.query_pairs_mut().append_pair(k, v);
}
}
if s3.no_sign_request {
return Ok((url, BTreeMap::new()));
}
log::info!("URL to sign: {url}");
let mut signature = Signature::new(s3, "s3", self.http_method()?)?;
let headers = signature.sign(&url, hash_payload, md5, content_length, self.headers());
Ok((url, headers?))
}
}
pub async fn response_error(response: Response) -> Error {
let status = response.status();
let status_code = status.as_u16();
let mut detail: BTreeMap<&str, String> = BTreeMap::new();
detail.insert("HTTP Status Code", status.to_string());
if let Some(v) = response
.headers()
.get("x-amz-id-2")
.and_then(|h| h.to_str().ok())
{
detail.insert("x-amz-id-2", v.to_string());
}
if let Some(v) = response
.headers()
.get("x-amz-request-id")
.and_then(|h| h.to_str().ok())
{
detail.insert("Request ID", v.to_string());
}
let mut code = None;
let mut message = None;
match response.text().await {
Ok(body) => {
if let Ok(e) = from_str::<ErrorResponse>(&body) {
code = Some(e.code.clone());
message = Some(e.message.clone());
detail.insert("Code", e.code);
detail.insert("Message", e.message);
} else if !body.is_empty() {
detail.insert("Response", body);
}
}
Err(e) => {
log::warn!("Failed to read error response body: {e}");
detail.insert(
"Note",
"Connection closed before response body could be read".to_string(),
);
}
}
let details = detail.iter().fold(String::new(), |mut output, (k, v)| {
let _ = writeln!(output, "{k}: {v}");
output
});
Error::Api(ApiError {
status: status_code,
code,
message,
details,
})
}