diff --git a/core/src/raw/ops.rs b/core/src/raw/ops.rs index 60a3b619ae04..fbc5020f7062 100644 --- a/core/src/raw/ops.rs +++ b/core/src/raw/ops.rs @@ -575,6 +575,7 @@ pub struct OpWrite { concurrent: usize, content_type: Option, content_disposition: Option, + content_encoding: Option, cache_control: Option, executor: Option, if_match: Option, @@ -632,6 +633,17 @@ impl OpWrite { self } + /// Get the content encoding from option + pub fn content_encoding(&self) -> Option<&str> { + self.content_encoding.as_deref() + } + + /// Set the content encoding of option + pub fn with_content_encoding(mut self, content_encoding: &str) -> Self { + self.content_encoding = Some(content_encoding.to_string()); + self + } + /// Get the cache control from option pub fn cache_control(&self) -> Option<&str> { self.cache_control.as_deref() diff --git a/core/src/services/s3/backend.rs b/core/src/services/s3/backend.rs index 663bb2ff770c..0049a29986b6 100644 --- a/core/src/services/s3/backend.rs +++ b/core/src/services/s3/backend.rs @@ -931,6 +931,7 @@ impl Access for S3Backend { write_can_multi: true, write_with_cache_control: true, write_with_content_type: true, + write_with_content_encoding: true, write_with_if_match: !self.core.disable_write_with_if_match, write_with_if_not_exists: true, write_with_user_metadata: true, diff --git a/core/src/services/s3/core.rs b/core/src/services/s3/core.rs index 6cf689da01f9..a49b0dab8251 100644 --- a/core/src/services/s3/core.rs +++ b/core/src/services/s3/core.rs @@ -31,6 +31,7 @@ use constants::X_AMZ_META_PREFIX; use http::header::HeaderName; use http::header::CACHE_CONTROL; use http::header::CONTENT_DISPOSITION; +use http::header::CONTENT_ENCODING; use http::header::CONTENT_LENGTH; use http::header::CONTENT_TYPE; use http::header::HOST; @@ -448,6 +449,10 @@ impl S3Core { req = req.header(CONTENT_TYPE, mime) } + if let Some(encoding) = args.content_encoding() { + req = req.header(CONTENT_ENCODING, encoding); + } + if let Some(pos) = args.content_disposition() { req = req.header(CONTENT_DISPOSITION, pos) } diff --git a/core/src/types/capability.rs b/core/src/types/capability.rs index 405ff66e0712..ee9adcdfeed0 100644 --- a/core/src/types/capability.rs +++ b/core/src/types/capability.rs @@ -126,6 +126,8 @@ pub struct Capability { pub write_with_content_type: bool, /// Indicates if Content-Disposition can be specified during write operations. pub write_with_content_disposition: bool, + /// Indicates if Content-Encoding can be specified during write operations. + pub write_with_content_encoding: bool, /// Indicates if Cache-Control can be specified during write operations. pub write_with_cache_control: bool, /// Indicates if conditional write operations using If-Match are supported. diff --git a/core/src/types/operator/operator.rs b/core/src/types/operator/operator.rs index 7c4240630049..8046967b21b9 100644 --- a/core/src/types/operator/operator.rs +++ b/core/src/types/operator/operator.rs @@ -1414,9 +1414,38 @@ impl Operator { /// # } /// ``` /// - /// ## `if_none_match` + /// ## `content_encoding` + /// + /// Sets Content-Encoding header for this write request. + /// + /// ### Capability + /// + /// Check [`Capability::write_with_content_encoding`] before using this feature. + /// + /// ### Behavior + /// + /// - If supported, sets Content-Encoding as system metadata on the target file + /// - The value should follow HTTP Content-Encoding header format + /// - If not supported, the value will be ignored + /// + /// This operation allows specifying the content encoding for the written content. + /// + /// ## Example /// - /// Sets an `if none match` condition with specified ETag for this write request. + /// ```no_run + /// # use opendal::Result; + /// # use opendal::Operator; + /// use bytes::Bytes; + /// # async fn test(op: Operator) -> Result<()> { + /// let _ = op + /// .write_with("path/to/file", bs) + /// .content_encoding("br") + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// ## `if_none_match` /// /// ### Capability /// diff --git a/core/src/types/operator/operator_futures.rs b/core/src/types/operator/operator_futures.rs index 4ce296f748ff..65d7fd6e8343 100644 --- a/core/src/types/operator/operator_futures.rs +++ b/core/src/types/operator/operator_futures.rs @@ -318,6 +318,11 @@ impl>> FutureWrite { self.map(|(args, options, bs)| (args.with_content_disposition(v), options, bs)) } + /// Set the content encoding of option + pub fn content_encoding(self, v: &str) -> Self { + self.map(|(args, options, bs)| (args.with_content_encoding(v), options, bs)) + } + /// Set the executor for this operation. pub fn executor(self, executor: Executor) -> Self { self.map(|(args, options, bs)| (args.with_executor(executor), options, bs)) diff --git a/core/tests/behavior/async_write.rs b/core/tests/behavior/async_write.rs index 4540925018d0..e2375e69ed5a 100644 --- a/core/tests/behavior/async_write.rs +++ b/core/tests/behavior/async_write.rs @@ -44,6 +44,7 @@ pub fn tests(op: &Operator, tests: &mut Vec) { test_write_with_cache_control, test_write_with_content_type, test_write_with_content_disposition, + test_write_with_content_encoding, test_write_with_if_none_match, test_write_with_if_not_exists, test_write_with_if_match, @@ -211,6 +212,24 @@ pub async fn test_write_with_content_disposition(op: Operator) -> Result<()> { Ok(()) } +/// Write a single file with content encoding should succeed. +pub async fn test_write_with_content_encoding(op: Operator) -> Result<()> { + if !op.info().full_capability().write_with_content_encoding { + return Ok(()); + } + + let (path, content, size) = TEST_FIXTURE.new_file(op.clone()); + + let target_content_encoding = "gzip"; + op.write_with(&path, content) + .content_encoding(target_content_encoding) + .await?; + + // TODO: check the content encoding in the stat op response? + + Ok(()) +} + /// write a single file with user defined metadata should succeed. pub async fn test_write_with_user_metadata(op: Operator) -> Result<()> { if !op.info().full_capability().write_with_user_metadata {