diff --git a/core/src/raw/ops.rs b/core/src/raw/ops.rs index 7cdac88a588e..3b196d7b20ee 100644 --- a/core/src/raw/ops.rs +++ b/core/src/raw/ops.rs @@ -541,6 +541,7 @@ pub struct OpWrite { concurrent: usize, content_type: Option, content_disposition: Option, + content_encoding: Option, cache_control: Option, executor: Option, if_match: Option, @@ -598,6 +599,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 8ffd6b7ab7b7..eb9a4370b089 100644 --- a/core/src/services/s3/backend.rs +++ b/core/src/services/s3/backend.rs @@ -944,6 +944,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 b69ef327ff69..19bdbfdf7209 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; @@ -452,6 +453,10 @@ impl S3Core { req = req.header(CONTENT_DISPOSITION, pos) } + if let Some(encoding) = args.content_encoding() { + req = req.header(CONTENT_ENCODING, encoding); + } + if let Some(cache_control) = args.cache_control() { req = req.header(CACHE_CONTROL, cache_control) } diff --git a/core/src/types/capability.rs b/core/src/types/capability.rs index 42c064368180..76bbc09611a5 100644 --- a/core/src/types/capability.rs +++ b/core/src/types/capability.rs @@ -128,6 +128,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 24bba50d266c..de4fffe66822 100644 --- a/core/src/types/operator/operator.rs +++ b/core/src/types/operator/operator.rs @@ -1407,6 +1407,38 @@ impl Operator { /// # } /// ``` /// + /// ## `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 + /// + /// ```no_run + /// # use opendal::Result; + /// # use opendal::Operator; + /// use bytes::Bytes; + /// # async fn test(op: Operator) -> Result<()> { + /// let bs = b"hello, world!".to_vec(); + /// let _ = op + /// .write_with("path/to/file", bs) + /// .content_encoding("br") + /// .await?; + /// # Ok(()) + /// # } + /// ``` + /// /// ## `if_none_match` /// /// Sets an `if none match` condition with specified ETag for this write request. diff --git a/core/src/types/operator/operator_futures.rs b/core/src/types/operator/operator_futures.rs index de8f277ff284..2d119055794f 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..8e577450f232 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,28 @@ 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, _) = TEST_FIXTURE.new_file(op.clone()); + + let target_content_encoding = "gzip"; + op.write_with(&path, content) + .content_encoding(target_content_encoding) + .await?; + + let meta = op.stat(&path).await.expect("stat must succeed"); + assert_eq!( + meta.content_encoding() + .expect("content encoding must exist"), + target_content_encoding + ); + 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 {