use std::future::Future;
use http::header::HeaderName;
use http::{HeaderMap, Method};
use serde::Deserialize;
use crate::body::ZeroBody;
use crate::error::Result;
use crate::response::BodyResponseProcessor;
use crate::utils::escape_path;
use crate::{Client, Ops, Prepared, Request};
#[derive(Debug, Clone, Default)]
pub struct CopyObjectOptions {
pub forbid_overwrite: Option<bool>,
pub copy_source_if_match: Option<String>,
pub copy_source_if_none_match: Option<String>,
pub copy_source_if_modified_since: Option<String>,
pub copy_source_if_unmodified_since: Option<String>,
pub metadata_directive: Option<String>,
pub cache_control: Option<String>,
pub content_disposition: Option<String>,
pub content_encoding: Option<String>,
pub content_language: Option<String>,
pub content_type: Option<String>,
pub expires: Option<String>,
pub server_side_encryption: Option<String>,
pub server_side_encryption_key_id: Option<String>,
pub storage_class: Option<String>,
pub tagging: Option<String>,
pub tagging_directive: Option<String>,
}
impl CopyObjectOptions {
pub fn new() -> Self {
Default::default()
}
pub fn forbid_overwrite(mut self, forbid_overwrite: bool) -> Self {
self.forbid_overwrite = Some(forbid_overwrite);
self
}
pub fn copy_source_if_match(mut self, etag: impl Into<String>) -> Self {
self.copy_source_if_match = Some(etag.into());
self
}
pub fn copy_source_if_none_match(mut self, etag: impl Into<String>) -> Self {
self.copy_source_if_none_match = Some(etag.into());
self
}
pub fn copy_source_if_modified_since(mut self, time: impl Into<String>) -> Self {
self.copy_source_if_modified_since = Some(time.into());
self
}
pub fn copy_source_if_unmodified_since(mut self, time: impl Into<String>) -> Self {
self.copy_source_if_unmodified_since = Some(time.into());
self
}
pub fn metadata_directive(mut self, directive: impl Into<String>) -> Self {
self.metadata_directive = Some(directive.into());
self
}
pub fn cache_control(mut self, cache_control: impl Into<String>) -> Self {
self.cache_control = Some(cache_control.into());
self
}
pub fn content_disposition(mut self, content_disposition: impl Into<String>) -> Self {
self.content_disposition = Some(content_disposition.into());
self
}
pub fn content_encoding(mut self, content_encoding: impl Into<String>) -> Self {
self.content_encoding = Some(content_encoding.into());
self
}
pub fn content_language(mut self, content_language: impl Into<String>) -> Self {
self.content_language = Some(content_language.into());
self
}
pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = Some(content_type.into());
self
}
pub fn expires(mut self, expires: impl Into<String>) -> Self {
self.expires = Some(expires.into());
self
}
pub fn server_side_encryption(mut self, encryption: impl Into<String>) -> Self {
self.server_side_encryption = Some(encryption.into());
self
}
pub fn server_side_encryption_key_id(mut self, key_id: impl Into<String>) -> Self {
self.server_side_encryption_key_id = Some(key_id.into());
self
}
pub fn storage_class(mut self, storage_class: impl Into<String>) -> Self {
self.storage_class = Some(storage_class.into());
self
}
pub fn tagging(mut self, tagging: impl Into<String>) -> Self {
self.tagging = Some(tagging.into());
self
}
pub fn tagging_directive(mut self, directive: impl Into<String>) -> Self {
self.tagging_directive = Some(directive.into());
self
}
}
impl CopyObjectOptions {
fn into_headers(
self,
source_bucket: String,
source_key: String,
source_version_id: Option<String>,
) -> Result<HeaderMap> {
let mut headers = HeaderMap::new();
let source_key = escape_path(&source_key);
let mut copy_source = format!("/{source_bucket}/{source_key}");
if let Some(source_version_id) = source_version_id {
copy_source.push_str(&format!("?versionId={source_version_id}"));
}
headers.insert(HeaderName::from_static("x-oss-copy-source"), copy_source.parse()?);
if let Some(forbid_overwrite) = &self.forbid_overwrite {
headers.insert(
HeaderName::from_static("x-oss-forbid-overwrite"),
forbid_overwrite.to_string().parse()?,
);
}
if let Some(copy_source_if_match) = &self.copy_source_if_match {
headers.insert(
HeaderName::from_static("x-oss-copy-source-if-match"),
copy_source_if_match.parse()?,
);
}
if let Some(copy_source_if_none_match) = &self.copy_source_if_none_match {
headers.insert(
HeaderName::from_static("x-oss-copy-source-if-none-match"),
copy_source_if_none_match.parse()?,
);
}
if let Some(copy_source_if_modified_since) = &self.copy_source_if_modified_since {
headers.insert(
HeaderName::from_static("x-oss-copy-source-if-modified-since"),
copy_source_if_modified_since.parse()?,
);
}
if let Some(copy_source_if_unmodified_since) = &self.copy_source_if_unmodified_since {
headers.insert(
HeaderName::from_static("x-oss-copy-source-if-unmodified-since"),
copy_source_if_unmodified_since.parse()?,
);
}
if let Some(metadata_directive) = &self.metadata_directive {
headers.insert(HeaderName::from_static("x-oss-metadata-directive"), metadata_directive.parse()?);
}
if let Some(cache_control) = &self.cache_control {
headers.insert(HeaderName::from_static("cache-control"), cache_control.parse()?);
}
if let Some(content_disposition) = &self.content_disposition {
headers.insert(HeaderName::from_static("content-disposition"), content_disposition.parse()?);
}
if let Some(content_encoding) = &self.content_encoding {
headers.insert(HeaderName::from_static("content-encoding"), content_encoding.parse()?);
}
if let Some(content_language) = &self.content_language {
headers.insert(HeaderName::from_static("content-language"), content_language.parse()?);
}
if let Some(content_type) = &self.content_type {
headers.insert(HeaderName::from_static("content-type"), content_type.parse()?);
}
if let Some(expires) = &self.expires {
headers.insert(HeaderName::from_static("expires"), expires.parse()?);
}
if let Some(server_side_encryption) = &self.server_side_encryption {
headers.insert(
HeaderName::from_static("x-oss-server-side-encryption"),
server_side_encryption.parse()?,
);
}
if let Some(server_side_encryption_key_id) = &self.server_side_encryption_key_id {
headers.insert(
HeaderName::from_static("x-oss-server-side-encryption-key-id"),
server_side_encryption_key_id.parse()?,
);
}
if let Some(storage_class) = &self.storage_class {
headers.insert(HeaderName::from_static("x-oss-storage-class"), storage_class.parse()?);
}
if let Some(tagging) = &self.tagging {
headers.insert(HeaderName::from_static("x-oss-tagging"), tagging.parse()?);
}
if let Some(tagging_directive) = &self.tagging_directive {
headers.insert(HeaderName::from_static("x-oss-tagging-directive"), tagging_directive.parse()?);
}
Ok(headers)
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct CopyObjectResult {
#[serde(rename = "ETag")]
pub etag: String,
pub last_modified: String,
}
pub struct CopyObject {
pub source_bucket: String,
pub source_key: String,
pub source_version_id: Option<String>,
pub target_key: String,
pub options: CopyObjectOptions,
}
impl Ops for CopyObject {
type Response = BodyResponseProcessor<CopyObjectResult>;
type Body = ZeroBody;
type Query = ();
fn prepare(self) -> Result<Prepared> {
Ok(Prepared {
method: Method::PUT,
key: Some(self.target_key),
headers: Some(self.options.into_headers(
self.source_bucket,
self.source_key,
self.source_version_id,
)?),
body: Some(()),
..Default::default()
})
}
}
pub trait CopyObjectOperations {
fn copy_object(
&self,
source_bucket: impl Into<String>,
source_key: impl Into<String>,
target_key: impl Into<String>,
options: Option<CopyObjectOptions>,
) -> impl Future<Output = Result<CopyObjectResult>>;
fn copy_object_with_version_id(
&self,
source_bucket: impl Into<String>,
source_key: impl Into<String>,
source_version_id: impl Into<String>,
target_key: impl Into<String>,
options: Option<CopyObjectOptions>,
) -> impl Future<Output = Result<CopyObjectResult>>;
}
impl CopyObjectOperations for Client {
async fn copy_object(
&self,
source_bucket: impl Into<String>,
source_key: impl Into<String>,
target_key: impl Into<String>,
options: Option<CopyObjectOptions>,
) -> Result<CopyObjectResult> {
let source_bucket = source_bucket.into();
let source_key = source_key.into();
let target_key = target_key.into();
let ops = CopyObject {
source_bucket,
source_key,
source_version_id: None,
target_key,
options: options.unwrap_or_default(),
};
self.request(ops).await
}
async fn copy_object_with_version_id(
&self,
source_bucket: impl Into<String>,
source_key: impl Into<String>,
source_version_id: impl Into<String>,
target_key: impl Into<String>,
options: Option<CopyObjectOptions>,
) -> Result<CopyObjectResult> {
let source_bucket = source_bucket.into();
let source_key = source_key.into();
let source_version_id = source_version_id.into();
let target_key = target_key.into();
let options = options.unwrap_or_default();
let ops = CopyObject {
source_bucket,
source_key,
source_version_id: Some(source_version_id),
target_key,
options,
};
self.request(ops).await
}
}