From 25723d3cc6505706907e7cc2b1a57e4c511d0507 Mon Sep 17 00:00:00 2001 From: ultraman <1394466835@qq.com> Date: Wed, 1 Jan 2025 14:44:33 +0800 Subject: [PATCH] feat: Add if-match & if-none-match support for reader --- 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 6c9e2aa42ea9..f3dceb2d67b4 100644 --- a/core/src/types/operator/operator_futures.rs +++ b/core/src/types/operator/operator_futures.rs @@ -248,6 +248,16 @@ impl>> FutureRead { pub type FutureReader = OperatorFuture<(OpRead, OpReader), Reader, F>; impl>> FutureReader { + /// 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 d691165e5fba..883c7dae4d0f 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) { 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) { 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 {