You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2025/01/02 04:31:34 UTC
(opendal) branch main updated: feat: Add if-match & if-none-match support for reader (#5492)
This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git
The following commit(s) were added to refs/heads/main by this push:
new 977fd2d30 feat: Add if-match & if-none-match support for reader (#5492)
977fd2d30 is described below
commit 977fd2d30cf1ceca0b849fb1194bdfd6555a1d8b
Author: xmchx <13...@qq.com>
AuthorDate: Thu Jan 2 12:31:28 2025 +0800
feat: Add if-match & if-none-match support for reader (#5492)
---
core/src/types/operator/operator_futures.rs | 10 +++
core/tests/behavior/async_read.rs | 133 +++++++++++++++++++++++++++-
2 files changed, 142 insertions(+), 1 deletion(-)
diff --git a/core/src/types/operator/operator_futures.rs b/core/src/types/operator/operator_futures.rs
index 6c9e2aa42..f3dceb2d6 100644
--- a/core/src/types/operator/operator_futures.rs
+++ b/core/src/types/operator/operator_futures.rs
@@ -248,6 +248,16 @@ impl<F: Future<Output = Result<Buffer>>> FutureRead<F> {
pub type FutureReader<F> = OperatorFuture<(OpRead, OpReader), Reader, F>;
impl<F: Future<Output = Result<Reader>>> FutureReader<F> {
+ /// Set the If-Match for this operation.
+ pub fn if_match(self, etag: &str) -> Self {
+ self.map(|(op_read, op_reader)| (op_read.with_if_match(etag), op_reader))
+ }
+
+ /// Set the If-None-Match for this operation.
+ pub fn if_none_match(self, etag: &str) -> Self {
+ self.map(|(op_read, op_reader)| (op_read.with_if_none_match(etag), op_reader))
+ }
+
/// Set the version for this operation.
pub fn version(self, v: &str) -> Self {
self.map(|(op_read, op_reader)| (op_read.with_version(v), op_reader))
diff --git a/core/tests/behavior/async_read.rs b/core/tests/behavior/async_read.rs
index d691165e5..883c7dae4 100644
--- a/core/tests/behavior/async_read.rs
+++ b/core/tests/behavior/async_read.rs
@@ -37,6 +37,8 @@ pub fn tests(op: &Operator, tests: &mut Vec<Trial>) {
test_read_full,
test_read_range,
test_reader,
+ test_reader_with_if_match,
+ test_reader_with_if_none_match,
test_read_not_exist,
test_read_with_if_match,
test_read_with_if_none_match,
@@ -59,7 +61,9 @@ pub fn tests(op: &Operator, tests: &mut Vec<Trial>) {
test_read_only_read_not_exist,
test_read_only_read_with_dir_path,
test_read_only_read_with_if_match,
- test_read_only_read_with_if_none_match
+ test_read_only_read_with_if_none_match,
+ test_reader_only_read_with_if_match,
+ test_reader_only_read_with_if_none_match
))
}
}
@@ -174,6 +178,36 @@ pub async fn test_read_not_exist(op: Operator) -> anyhow::Result<()> {
Ok(())
}
+/// Reader with if_match should match, else get a ConditionNotMatch error.
+pub async fn test_reader_with_if_match(op: Operator) -> anyhow::Result<()> {
+ if !op.info().full_capability().read_with_if_match {
+ return Ok(());
+ }
+
+ let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
+
+ op.write(&path, content.clone())
+ .await
+ .expect("write must succeed");
+
+ let meta = op.stat(&path).await?;
+
+ let reader = op.reader_with(&path).if_match("\"invalid_etag\"").await?;
+ let res = reader.read(..).await;
+ assert!(res.is_err());
+ assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
+
+ let reader = op
+ .reader_with(&path)
+ .if_match(meta.etag().expect("etag must exist"))
+ .await?;
+
+ let bs = reader.read(..).await.expect("read must succeed").to_bytes();
+ assert_eq!(bs, content);
+
+ Ok(())
+}
+
/// Read with if_match should match, else get a ConditionNotMatch error.
pub async fn test_read_with_if_match(op: Operator) -> anyhow::Result<()> {
if !op.info().full_capability().read_with_if_match {
@@ -203,6 +237,39 @@ pub async fn test_read_with_if_match(op: Operator) -> anyhow::Result<()> {
Ok(())
}
+/// Reader with if_none_match should match, else get a ConditionNotMatch error.
+pub async fn test_reader_with_if_none_match(op: Operator) -> anyhow::Result<()> {
+ if !op.info().full_capability().read_with_if_none_match {
+ return Ok(());
+ }
+
+ let (path, content, _) = TEST_FIXTURE.new_file(op.clone());
+
+ op.write(&path, content.clone())
+ .await
+ .expect("write must succeed");
+
+ let meta = op.stat(&path).await?;
+
+ let reader = op
+ .reader_with(&path)
+ .if_none_match(meta.etag().expect("etag must exist"))
+ .await?;
+ let res = reader.read(..).await;
+
+ assert!(res.is_err());
+ assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
+
+ let reader = op
+ .reader_with(&path)
+ .if_none_match("\"invalid_etag\"")
+ .await?;
+ let bs = reader.read(..).await.expect("read must succeed").to_bytes();
+ assert_eq!(bs, content);
+
+ Ok(())
+}
+
/// Read with if_none_match should match, else get a ConditionNotMatch error.
pub async fn test_read_with_if_none_match(op: Operator) -> anyhow::Result<()> {
if !op.info().full_capability().read_with_if_none_match {
@@ -493,6 +560,38 @@ pub async fn test_read_only_read_with_dir_path(op: Operator) -> anyhow::Result<(
Ok(())
}
+/// Reader with if_match should match, else get a ConditionNotMatch error.
+pub async fn test_reader_only_read_with_if_match(op: Operator) -> anyhow::Result<()> {
+ if !op.info().full_capability().read_with_if_match {
+ return Ok(());
+ }
+
+ let path = "normal_file.txt";
+
+ let meta = op.stat(path).await?;
+
+ let reader = op.reader_with(path).if_match("invalid_etag").await?;
+ let res = reader.read(..).await;
+ assert!(res.is_err());
+ assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
+
+ let reader = op
+ .reader_with(path)
+ .if_match(meta.etag().expect("etag must exist"))
+ .await?;
+
+ let bs = reader.read(..).await.expect("read must succeed").to_bytes();
+
+ assert_eq!(bs.len(), 30482, "read size");
+ assert_eq!(
+ format!("{:x}", Sha256::digest(&bs)),
+ "943048ba817cdcd786db07d1f42d5500da7d10541c2f9353352cd2d3f66617e5",
+ "read content"
+ );
+
+ Ok(())
+}
+
/// Read with if_match should match, else get a ConditionNotMatch error.
pub async fn test_read_only_read_with_if_match(op: Operator) -> anyhow::Result<()> {
if !op.info().full_capability().read_with_if_match {
@@ -523,6 +622,38 @@ pub async fn test_read_only_read_with_if_match(op: Operator) -> anyhow::Result<(
Ok(())
}
+/// Reader with if_none_match should match, else get a ConditionNotMatch error.
+pub async fn test_reader_only_read_with_if_none_match(op: Operator) -> anyhow::Result<()> {
+ if !op.info().full_capability().read_with_if_none_match {
+ return Ok(());
+ }
+
+ let path = "normal_file.txt";
+
+ let meta = op.stat(path).await?;
+
+ let reader = op
+ .reader_with(path)
+ .if_none_match(meta.etag().expect("etag must exist"))
+ .await?;
+
+ let res = reader.read(..).await;
+ assert!(res.is_err());
+ assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch);
+
+ let reader = op.reader_with(path).if_none_match("invalid_etag").await?;
+ let bs = reader.read(..).await.expect("read must succeed").to_bytes();
+
+ assert_eq!(bs.len(), 30482, "read size");
+ assert_eq!(
+ format!("{:x}", Sha256::digest(&bs)),
+ "943048ba817cdcd786db07d1f42d5500da7d10541c2f9353352cd2d3f66617e5",
+ "read content"
+ );
+
+ Ok(())
+}
+
/// Read with if_none_match should match, else get a ConditionNotMatch error.
pub async fn test_read_only_read_with_if_none_match(op: Operator) -> anyhow::Result<()> {
if !op.info().full_capability().read_with_if_none_match {