Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add if-match & if-none-match support for reader #5492

Merged
merged 1 commit into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions core/src/types/operator/operator_futures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
133 changes: 132 additions & 1 deletion core/tests/behavior/async_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
))
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Loading