From 653b30836df8344f13e486d44a22167eab99db66 Mon Sep 17 00:00:00 2001 From: Gabriela Aragundi Date: Tue, 3 Jun 2025 11:42:50 -0700 Subject: [PATCH 01/12] read me change --- sdk/cosmos/azure_data_cosmos/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/sdk/cosmos/azure_data_cosmos/README.md b/sdk/cosmos/azure_data_cosmos/README.md index a9447c04ef..4443752da4 100644 --- a/sdk/cosmos/azure_data_cosmos/README.md +++ b/sdk/cosmos/azure_data_cosmos/README.md @@ -130,8 +130,6 @@ async fn example(cosmos_client: CosmosClient) -> Result<(), Box Date: Tue, 10 Jun 2025 18:08:02 -0700 Subject: [PATCH 02/12] if_match_etag changes with tests --- .../src/clients/container_client.rs | 16 +++++++++- .../azure_data_cosmos/src/options/mod.rs | 2 +- .../azure_data_cosmos/tests/cosmos_items.rs | 30 +++++++++++++++++-- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs b/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs index 68af26fb28..5a5e5c0242 100644 --- a/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs +++ b/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs @@ -35,7 +35,7 @@ impl ContainerClient { database_link: &ResourceLink, container_id: &str, ) -> Self { - let link = database_link + let link: ResourceLink = database_link .feed(ResourceType::Containers) .item(container_id); let items_link = link.feed(ResourceType::Items); @@ -355,9 +355,14 @@ impl ContainerClient { let link = self.items_link.item(item_id); let url = self.pipeline.url(&link); let mut req = Request::new(url, Method::Put); + println!("Inside replace_item"); if !options.enable_content_response_on_write { req.insert_header(headers::PREFER, constants::PREFER_MINIMAL); } + if let Some(etag) = options.if_match_etag { + req.insert_header(headers::IF_MATCH, etag); + println!("Inserted header"); + } req.insert_headers(&partition_key.into())?; req.insert_headers(&ContentType::APPLICATION_JSON)?; req.set_json(&item)?; @@ -447,6 +452,9 @@ impl ContainerClient { if !options.enable_content_response_on_write { req.insert_header(headers::PREFER, constants::PREFER_MINIMAL); } + if let Some(etag) = options.if_match_etag { + req.insert_header(headers::IF_MATCH, etag); + } req.insert_header(constants::IS_UPSERT, "true"); req.insert_headers(&partition_key.into())?; req.insert_headers(&ContentType::APPLICATION_JSON)?; @@ -539,6 +547,9 @@ impl ContainerClient { let link = self.items_link.item(item_id); let url = self.pipeline.url(&link); let mut req = Request::new(url, Method::Delete); + if let Some(etag) = options.if_match_etag { + req.insert_header(headers::IF_MATCH, etag); + } req.insert_headers(&partition_key.into())?; self.pipeline .send(options.method_options.context, &mut req, link) @@ -615,6 +626,9 @@ impl ContainerClient { if !options.enable_content_response_on_write { req.insert_header(headers::PREFER, constants::PREFER_MINIMAL); } + if let Some(etag) = options.if_match_etag { + req.insert_header(headers::IF_MATCH, etag); + } req.insert_headers(&partition_key.into())?; req.insert_headers(&ContentType::APPLICATION_JSON)?; req.set_json(&patch)?; diff --git a/sdk/cosmos/azure_data_cosmos/src/options/mod.rs b/sdk/cosmos/azure_data_cosmos/src/options/mod.rs index e2afe25633..a222ebb9c3 100644 --- a/sdk/cosmos/azure_data_cosmos/src/options/mod.rs +++ b/sdk/cosmos/azure_data_cosmos/src/options/mod.rs @@ -47,7 +47,7 @@ pub struct DeleteDatabaseOptions<'a> { #[derive(Clone, Default)] pub struct ItemOptions<'a> { pub method_options: ClientMethodOptions<'a>, - + pub if_match_etag: Option, /// When this value is true, write operations will respond with the new value of the resource being written. /// /// The default for this is `false`, which reduces the network and CPU burden that comes from serializing and deserializing the response. diff --git a/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs b/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs index 3c2c02b1f6..9d7cec5ac5 100644 --- a/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs +++ b/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs @@ -2,6 +2,7 @@ mod framework; +use azure_core::http::headers::HeaderName; use azure_core_test::{recorded, TestContext}; use azure_data_cosmos::{ clients::ContainerClient, @@ -31,6 +32,7 @@ async fn create_container( cosmos_client: &CosmosClient, ) -> azure_core::Result { // Create a database and a container + println!("Attempting to create client"); let db_client = test_data::create_database(account, cosmos_client).await?; db_client .create_container( @@ -42,6 +44,7 @@ async fn create_container( None, ) .await?; + println!("Created database and container"); let container_client = db_client.container_client("Container"); Ok(container_client) @@ -49,6 +52,7 @@ async fn create_container( #[recorded::test] pub async fn item_create_read_replace_delete(context: TestContext) -> Result<(), Box> { + use azure_core::http::headers::HeaderName; let account = TestAccount::from_env(context, None).await?; let cosmos_client = account.connect_with_key(None)?; let container_client = create_container(&account, &cosmos_client).await?; @@ -67,8 +71,14 @@ pub async fn item_create_read_replace_delete(context: TestContext) -> Result<(), let response = container_client .create_item("Partition1", &item, None) .await?; - let body = response.into_raw_body().collect_string().await?; - assert_eq!("", body); + //let body = response.into_raw_body().collect_string().await?; + + const ETAG: HeaderName = HeaderName::from_static("etag"); + println!( + "Body response: {}", + response.headers().get_str(&ETAG).unwrap_or("No ETag") + ); + //assert_eq!("", body); // Try to read the item let read_item: TestItem = container_client @@ -82,9 +92,23 @@ pub async fn item_create_read_replace_delete(context: TestContext) -> Result<(), item.value = 24; item.nested.nested_value = "Updated".into(); + println!("Attempting to replace item"); let response = container_client - .replace_item("Partition1", "Item1", &item, None) + .replace_item( + "Partition1", + "Item1", + &item, + Some(ItemOptions { + if_match_etag: response + .headers() + .get_str(&ETAG) + .ok() + .map(|s| s.to_string()), + ..Default::default() + }), + ) .await?; + println!("Item upserted successfully"); let body = response.into_raw_body().collect_string().await?; assert_eq!("", body); From 41f2acf3664c8dd36f2b5c41474f490e9d6856d0 Mon Sep 17 00:00:00 2001 From: Gabriela Aragundi Date: Thu, 12 Jun 2025 10:29:48 -0700 Subject: [PATCH 03/12] if_match_etag changes with tests --- .../src/clients/container_client.rs | 2 - .../azure_data_cosmos/tests/cosmos_items.rs | 370 ++++++++++++++++-- .../tests/framework/test_account.rs | 4 +- 3 files changed, 346 insertions(+), 30 deletions(-) diff --git a/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs b/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs index 5a5e5c0242..197e09ea70 100644 --- a/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs +++ b/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs @@ -355,13 +355,11 @@ impl ContainerClient { let link = self.items_link.item(item_id); let url = self.pipeline.url(&link); let mut req = Request::new(url, Method::Put); - println!("Inside replace_item"); if !options.enable_content_response_on_write { req.insert_header(headers::PREFER, constants::PREFER_MINIMAL); } if let Some(etag) = options.if_match_etag { req.insert_header(headers::IF_MATCH, etag); - println!("Inserted header"); } req.insert_headers(&partition_key.into())?; req.insert_headers(&ContentType::APPLICATION_JSON)?; diff --git a/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs b/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs index 9d7cec5ac5..741362a991 100644 --- a/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs +++ b/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs @@ -2,7 +2,7 @@ mod framework; -use azure_core::http::headers::HeaderName; +use azure_core::http::{headers::HeaderName, response}; use azure_core_test::{recorded, TestContext}; use azure_data_cosmos::{ clients::ContainerClient, @@ -32,7 +32,6 @@ async fn create_container( cosmos_client: &CosmosClient, ) -> azure_core::Result { // Create a database and a container - println!("Attempting to create client"); let db_client = test_data::create_database(account, cosmos_client).await?; db_client .create_container( @@ -44,7 +43,6 @@ async fn create_container( None, ) .await?; - println!("Created database and container"); let container_client = db_client.container_client("Container"); Ok(container_client) @@ -52,7 +50,6 @@ async fn create_container( #[recorded::test] pub async fn item_create_read_replace_delete(context: TestContext) -> Result<(), Box> { - use azure_core::http::headers::HeaderName; let account = TestAccount::from_env(context, None).await?; let cosmos_client = account.connect_with_key(None)?; let container_client = create_container(&account, &cosmos_client).await?; @@ -71,14 +68,8 @@ pub async fn item_create_read_replace_delete(context: TestContext) -> Result<(), let response = container_client .create_item("Partition1", &item, None) .await?; - //let body = response.into_raw_body().collect_string().await?; - - const ETAG: HeaderName = HeaderName::from_static("etag"); - println!( - "Body response: {}", - response.headers().get_str(&ETAG).unwrap_or("No ETag") - ); - //assert_eq!("", body); + let body = response.into_raw_body().collect_string().await?; + assert_eq!("", body); // Try to read the item let read_item: TestItem = container_client @@ -92,23 +83,9 @@ pub async fn item_create_read_replace_delete(context: TestContext) -> Result<(), item.value = 24; item.nested.nested_value = "Updated".into(); - println!("Attempting to replace item"); let response = container_client - .replace_item( - "Partition1", - "Item1", - &item, - Some(ItemOptions { - if_match_etag: response - .headers() - .get_str(&ETAG) - .ok() - .map(|s| s.to_string()), - ..Default::default() - }), - ) + .replace_item("Partition1", "Item1", &item, None) .await?; - println!("Item upserted successfully"); let body = response.into_raw_body().collect_string().await?; assert_eq!("", body); @@ -223,6 +200,7 @@ pub async fn item_read_system_properties(context: TestContext) -> Result<(), Box read_item.get("_rid").is_some(), "expected _rid to be present" ); + assert!( read_item.get("_etag").is_some(), "expected _etag to be present" @@ -425,3 +403,341 @@ pub async fn item_null_partition_key(context: TestContext) -> Result<(), Box Result<(), Box> { + let account = TestAccount::from_env(context, None).await?; + let cosmos_client = account.connect_with_key(None)?; + let container_client = create_container(&account, &cosmos_client).await?; + + //Create an item + let mut item = TestItem { + id: "Item1".into(), + partition_key: Some("Partition1".into()), + value: 42, + nested: NestedItem { + nested_value: "Nested".into(), + }, + bool_value: true, + }; + + let response = container_client + .create_item("Partition1", &item, None) + .await?; + + //Store Etag from response + const ETAG: HeaderName = HeaderName::from_static("etag"); + let etag = response + .headers() + .get_str(&ETAG) + .ok() + .map(|s| s.to_string()); + + //Replace item with correct Etag + item.value = 24; + item.nested.nested_value = "Updated".into(); + + //could change response name to replaced item for better understanding + let response = container_client + .replace_item( + "Partition1", + "Item1", + &item, + Some(ItemOptions { + if_match_etag: etag, + enable_content_response_on_write: false, + ..Default::default() + }), + ) + .await?; + + let body = response.into_raw_body().collect_string().await?; + assert_eq!("", body); + + //Replace item with incorrect Etag + item.value = 52; + item.nested.nested_value = "UpdatedAgain".into(); + + let response = container_client + .replace_item( + "Partition1", + "Item1", + &item, + Some(ItemOptions { + if_match_etag: Some("incorrectEtag".into()), + enable_content_response_on_write: true, + ..Default::default() + }), + ) + .await; + + match response { + Ok(_) => { + return Err( + "expected a 412 Precondition Failed error when using an incorrect ETag".into(), + ); + } + Err(err) => { + assert_eq!( + Some(azure_core::http::StatusCode::PreconditionFailed), + err.http_status() + ); + } + } + + account.cleanup().await?; + Ok(()) +} + +#[recorded::test] +pub async fn item_upsert_if_match_etag(context: TestContext) -> Result<(), Box> { + let account = TestAccount::from_env(context, None).await?; + let cosmos_client = account.connect_with_key(None)?; + let container_client = create_container(&account, &cosmos_client).await?; + + //Create an item + let mut item = TestItem { + id: "Item1".into(), + partition_key: Some("Partition1".into()), + value: 42, + nested: NestedItem { + nested_value: "Nested".into(), + }, + bool_value: true, + }; + + let response = container_client + .create_item("Partition1", &item, None) + .await?; + + //Store Etag from response + const ETAG: HeaderName = HeaderName::from_static("etag"); + let etag = response + .headers() + .get_str(&ETAG) + .ok() + .map(|s| s.to_string()); + + //Upsert item with correct Etag + item.value = 24; + item.nested.nested_value = "Updated".into(); + + let response = container_client + .upsert_item( + "Partition1", + &item, + Some(ItemOptions { + if_match_etag: etag, + enable_content_response_on_write: false, + ..Default::default() + }), + ) + .await?; + + let body = response.into_raw_body().collect_string().await?; + assert_eq!("", body); + + //Upsert item with incorrect Etag + item.value = 52; + item.nested.nested_value = "UpdatedAgain".into(); + + let response = container_client + .upsert_item( + "Partition1", + &item, + Some(ItemOptions { + if_match_etag: Some("incorrectEtag".into()), + enable_content_response_on_write: false, + ..Default::default() + }), + ) + .await; + + match response { + Ok(_) => { + return Err( + "expected a 412 Precondition Failed error when using an incorrect ETag".into(), + ); + } + Err(err) => { + assert_eq!( + Some(azure_core::http::StatusCode::PreconditionFailed), + err.http_status() + ); + } + } + + account.cleanup().await?; + Ok(()) +} + +#[recorded::test] +pub async fn item_delete_if_match_etag(context: TestContext) -> Result<(), Box> { + let account = TestAccount::from_env(context, None).await?; + let cosmos_client = account.connect_with_key(None)?; + let container_client = create_container(&account, &cosmos_client).await?; + + //Create an item + let item = TestItem { + id: "Item1".into(), + partition_key: Some("Partition1".into()), + value: 42, + nested: NestedItem { + nested_value: "Nested".into(), + }, + bool_value: true, + }; + + let response = container_client + .create_item("Partition1", &item, None) + .await?; + + //Store Etag from response + const ETAG: HeaderName = HeaderName::from_static("etag"); + let etag = response + .headers() + .get_str(&ETAG) + .ok() + .map(|s| s.to_string()); + + //Delete item with correct Etag + let response = container_client + .delete_item( + "Partition1", + "Item1", + Some(ItemOptions { + if_match_etag: etag, + enable_content_response_on_write: false, + ..Default::default() + }), + ) + .await?; + + let body = response.into_raw_body().collect_string().await?; + assert_eq!("", body); + + //Add item again for second delete test + let response = container_client + .create_item("Partition1", &item, None) + .await?; + + //Delete item with incorrect Etag + let response = container_client + .delete_item( + "Partition1", + "Item1", + Some(ItemOptions { + if_match_etag: Some("incorrectEtag".into()), + enable_content_response_on_write: false, + ..Default::default() + }), + ) + .await; + match response { + Ok(_) => { + return Err( + "expected a 412 Precondition Failed error when using an incorrect ETag".into(), + ); + } + Err(err) => { + assert_eq!( + Some(azure_core::http::StatusCode::PreconditionFailed), + err.http_status() + ); + } + } + + account.cleanup().await?; + Ok(()) +} + +#[recorded::test] +pub async fn item_patch_if_match_etag(context: TestContext) -> Result<(), Box> { + let account = TestAccount::from_env(context, None).await?; + let cosmos_client = account.connect_with_key(None)?; + let container_client = create_container(&account, &cosmos_client).await?; + + //Create an item + let item = TestItem { + id: "Item1".into(), + partition_key: Some("Partition1".into()), + value: 42, + nested: NestedItem { + nested_value: "Nested".into(), + }, + bool_value: true, + }; + + let response = container_client + .create_item("Partition1", &item, None) + .await?; + + //Store Etag from response + const ETAG: HeaderName = HeaderName::from_static("etag"); + let etag = response + .headers() + .get_str(&ETAG) + .ok() + .map(|s| s.to_string()); + + //Patch item with correct Etag + let patch = PatchDocument::default() + .with_replace("/nested/nested_value", "Patched")? + .with_increment("/value", 10)?; + + container_client + .patch_item( + "Partition1", + "Item1", + patch, + Some(ItemOptions { + if_match_etag: etag, + enable_content_response_on_write: false, + ..Default::default() + }), + ) + .await?; + + let patched_item: TestItem = container_client + .read_item("Partition1", "Item1", None) + .await? + .into_json_body() + .await?; + assert_eq!("Patched", patched_item.nested.nested_value); + assert_eq!(52, patched_item.value); + + //Patch item with incorrect Etag + let patch = PatchDocument::default() + .with_replace("/nested/nested_value", "Patched_Incorrect")? + .with_increment("/value", 15)?; + + let response = container_client + .patch_item( + "Partition1", + "Item1", + patch, + Some(ItemOptions { + if_match_etag: Some("incorrectEtag".into()), + enable_content_response_on_write: false, + ..Default::default() + }), + ) + .await; + + match response { + Ok(_) => { + return Err( + "expected a 412 Precondition Failed error when using an incorrect ETag".into(), + ); + } + Err(err) => { + assert_eq!( + Some(azure_core::http::StatusCode::PreconditionFailed), + err.http_status() + ); + } + } + + account.cleanup().await?; + Ok(()) +} diff --git a/sdk/cosmos/azure_data_cosmos/tests/framework/test_account.rs b/sdk/cosmos/azure_data_cosmos/tests/framework/test_account.rs index 2a8d2aee5b..1de6d0cc94 100644 --- a/sdk/cosmos/azure_data_cosmos/tests/framework/test_account.rs +++ b/sdk/cosmos/azure_data_cosmos/tests/framework/test_account.rs @@ -119,7 +119,9 @@ impl TestAccount { ) -> Result> { let allow_invalid_certificates = match self.options.allow_invalid_certificates { Some(b) => b, - None => std::env::var(ALLOW_INVALID_CERTS_ENV_VAR).map(|s| s.parse())??, + None => std::env::var(ALLOW_INVALID_CERTS_ENV_VAR) + .unwrap_or("false".to_string()) + .parse()?, }; let mut options = options.unwrap_or_default(); From a23d49c59d45d99354fcc6f3ec9b102fc8e3843b Mon Sep 17 00:00:00 2001 From: Gabriela Aragundi Date: Thu, 12 Jun 2025 12:27:27 -0700 Subject: [PATCH 04/12] test changes --- sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs b/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs index 93e27cb6c9..983f36ab30 100644 --- a/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs +++ b/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs @@ -705,7 +705,7 @@ pub async fn item_patch_if_match_etag(context: TestContext) -> Result<(), Box Date: Thu, 12 Jun 2025 17:34:31 -0700 Subject: [PATCH 05/12] if_match_etag added with tests --- sdk/cosmos/azure_data_cosmos/CHANGELOG.md | 1 + .../azure_data_cosmos/src/clients/container_client.rs | 4 ++-- sdk/cosmos/azure_data_cosmos/src/options/mod.rs | 4 ++-- sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs | 10 +++++----- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/sdk/cosmos/azure_data_cosmos/CHANGELOG.md b/sdk/cosmos/azure_data_cosmos/CHANGELOG.md index 5be47ed21c..a17f619be3 100644 --- a/sdk/cosmos/azure_data_cosmos/CHANGELOG.md +++ b/sdk/cosmos/azure_data_cosmos/CHANGELOG.md @@ -17,6 +17,7 @@ * Added a function `CosmosClient::with_connection_string` to enable `CosmosClient` creation via connection string. ([#2641](https://github.com/Azure/azure-sdk-for-rust/pull/2641)) * Added support for executing limited cross-partition queries through the Gateway. See for more details on these limitations. ([#2577](https://github.com/Azure/azure-sdk-for-rust/pull/2577)) * Added a preview feature (behind `preview_query_engine` feature flag) to allow the Rust SDK to integrate with an external query engine for performing cross-partition queries. ([#2577](https://github.com/Azure/azure-sdk-for-rust/pull/2577)) +* Added 'if_match_etag' to ItemOptions and necessary functions (tbd) ### Breaking Changes diff --git a/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs b/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs index 030e620ecb..0f7ffe5cb2 100644 --- a/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs +++ b/sdk/cosmos/azure_data_cosmos/src/clients/container_client.rs @@ -12,10 +12,10 @@ use crate::{ }; use azure_core::http::{ - headers, + headers::{self, ETAG}, request::{options::ContentType, Request}, response::Response, - Method, + Etag, Method, }; use serde::{de::DeserializeOwned, Serialize}; diff --git a/sdk/cosmos/azure_data_cosmos/src/options/mod.rs b/sdk/cosmos/azure_data_cosmos/src/options/mod.rs index a222ebb9c3..44f4137802 100644 --- a/sdk/cosmos/azure_data_cosmos/src/options/mod.rs +++ b/sdk/cosmos/azure_data_cosmos/src/options/mod.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use azure_core::http::{ClientMethodOptions, ClientOptions}; +use azure_core::http::{ClientMethodOptions, ClientOptions, Etag}; use crate::models::ThroughputProperties; @@ -47,7 +47,7 @@ pub struct DeleteDatabaseOptions<'a> { #[derive(Clone, Default)] pub struct ItemOptions<'a> { pub method_options: ClientMethodOptions<'a>, - pub if_match_etag: Option, + pub if_match_etag: Option, /// When this value is true, write operations will respond with the new value of the resource being written. /// /// The default for this is `false`, which reduces the network and CPU burden that comes from serializing and deserializing the response. diff --git a/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs b/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs index 983f36ab30..d4f891350f 100644 --- a/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs +++ b/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs @@ -2,7 +2,7 @@ mod framework; -use azure_core::http::{headers::HeaderName, response}; +use azure_core::http::{headers::HeaderName, response, Etag}; use azure_core_test::{recorded, TestContext}; use azure_data_cosmos::{ clients::ContainerClient, @@ -435,7 +435,7 @@ pub async fn item_replace_if_match_etag(context: TestContext) -> Result<(), Box< .headers() .get_str(&ETAG) .ok() - .map(|s| s.to_string()); + .map(|s| Etag::from(s.to_string())); //Replace item with correct Etag item.value = 24; @@ -520,7 +520,7 @@ pub async fn item_upsert_if_match_etag(context: TestContext) -> Result<(), Box Result<(), Box Result<(), Box Date: Fri, 13 Jun 2025 11:03:21 -0700 Subject: [PATCH 06/12] changing back test_accounts file --- sdk/cosmos/azure_data_cosmos/tests/framework/test_account.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sdk/cosmos/azure_data_cosmos/tests/framework/test_account.rs b/sdk/cosmos/azure_data_cosmos/tests/framework/test_account.rs index 08278de2bb..eb3e4b52ba 100644 --- a/sdk/cosmos/azure_data_cosmos/tests/framework/test_account.rs +++ b/sdk/cosmos/azure_data_cosmos/tests/framework/test_account.rs @@ -96,9 +96,7 @@ impl TestAccount { ) -> Result> { let allow_invalid_certificates = match self.options.allow_invalid_certificates { Some(b) => b, - None => std::env::var(ALLOW_INVALID_CERTS_ENV_VAR) - .unwrap_or("false".to_string()) - .parse()?, + None => std::env::var(ALLOW_INVALID_CERTS_ENV_VAR).map(|s| s.parse())??, }; let mut options = options.unwrap_or_default(); From 82af0d35ff05d48018aaedfbe7124f34e998988e Mon Sep 17 00:00:00 2001 From: Gabriela Aragundi Date: Sun, 15 Jun 2025 18:03:35 -0700 Subject: [PATCH 07/12] test changes and small fixes --- sdk/cosmos/azure_data_cosmos/CHANGELOG.md | 2 +- sdk/cosmos/azure_data_cosmos/README.md | 2 + .../src/clients/container_client.rs | 6 +- .../azure_data_cosmos/src/options/mod.rs | 3 + .../azure_data_cosmos/tests/cosmos_items.rs | 152 ++++++------------ 5 files changed, 61 insertions(+), 104 deletions(-) diff --git a/sdk/cosmos/azure_data_cosmos/CHANGELOG.md b/sdk/cosmos/azure_data_cosmos/CHANGELOG.md index a17f619be3..843a0050b8 100644 --- a/sdk/cosmos/azure_data_cosmos/CHANGELOG.md +++ b/sdk/cosmos/azure_data_cosmos/CHANGELOG.md @@ -17,7 +17,7 @@ * Added a function `CosmosClient::with_connection_string` to enable `CosmosClient` creation via connection string. ([#2641](https://github.com/Azure/azure-sdk-for-rust/pull/2641)) * Added support for executing limited cross-partition queries through the Gateway. See for more details on these limitations. ([#2577](https://github.com/Azure/azure-sdk-for-rust/pull/2577)) * Added a preview feature (behind `preview_query_engine` feature flag) to allow the Rust SDK to integrate with an external query engine for performing cross-partition queries. ([#2577](https://github.com/Azure/azure-sdk-for-rust/pull/2577)) -* Added 'if_match_etag' to ItemOptions and necessary functions (tbd) +* Added 'if_match_etag' to ItemOptions and necessary functions ([#2705](https://github.com/Azure/azure-sdk-for-rust/pull/2705)) ### Breaking Changes diff --git a/sdk/cosmos/azure_data_cosmos/README.md b/sdk/cosmos/azure_data_cosmos/README.md index 2b96d4fa1e..fdf8cf175b 100644 --- a/sdk/cosmos/azure_data_cosmos/README.md +++ b/sdk/cosmos/azure_data_cosmos/README.md @@ -131,6 +131,8 @@ async fn example(cosmos_client: CosmosClient) -> Result<(), Box Self { - let link: ResourceLink = database_link + let link = database_link .feed(ResourceType::Containers) .item(container_id); let items_link = link.feed(ResourceType::Items); diff --git a/sdk/cosmos/azure_data_cosmos/src/options/mod.rs b/sdk/cosmos/azure_data_cosmos/src/options/mod.rs index 44f4137802..749a9bf28a 100644 --- a/sdk/cosmos/azure_data_cosmos/src/options/mod.rs +++ b/sdk/cosmos/azure_data_cosmos/src/options/mod.rs @@ -47,6 +47,9 @@ pub struct DeleteDatabaseOptions<'a> { #[derive(Clone, Default)] pub struct ItemOptions<'a> { pub method_options: ClientMethodOptions<'a>, + /// IfMatchEtag is used to ensure optimistic concurrency control, it helps prevent accidental overwrites and maintains data integrity. + /// + /// https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/database-transactions-optimistic-concurrency#optimistic-concurrency-control pub if_match_etag: Option, /// When this value is true, write operations will respond with the new value of the resource being written. /// diff --git a/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs b/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs index d4f891350f..187642b43c 100644 --- a/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs +++ b/sdk/cosmos/azure_data_cosmos/tests/cosmos_items.rs @@ -2,7 +2,7 @@ mod framework; -use azure_core::http::{headers::HeaderName, response, Etag}; +use azure_core::http::Etag; use azure_core_test::{recorded, TestContext}; use azure_data_cosmos::{ clients::ContainerClient, @@ -430,34 +430,28 @@ pub async fn item_replace_if_match_etag(context: TestContext) -> Result<(), Box< .await?; //Store Etag from response - const ETAG: HeaderName = HeaderName::from_static("etag"); - let etag = response + let etag: Etag = response .headers() - .get_str(&ETAG) - .ok() - .map(|s| Etag::from(s.to_string())); + .get_str(&azure_core::http::headers::ETAG) + .expect("expected the etag to be returned") + .into(); //Replace item with correct Etag item.value = 24; item.nested.nested_value = "Updated".into(); - //could change response name to replaced item for better understanding - let response = container_client + container_client .replace_item( "Partition1", "Item1", &item, Some(ItemOptions { - if_match_etag: etag, - enable_content_response_on_write: false, + if_match_etag: Some(etag), ..Default::default() }), ) .await?; - let body = response.into_raw_body().collect_string().await?; - assert_eq!("", body); - //Replace item with incorrect Etag item.value = 52; item.nested.nested_value = "UpdatedAgain".into(); @@ -469,25 +463,17 @@ pub async fn item_replace_if_match_etag(context: TestContext) -> Result<(), Box< &item, Some(ItemOptions { if_match_etag: Some("incorrectEtag".into()), - enable_content_response_on_write: true, ..Default::default() }), ) .await; - match response { - Ok(_) => { - return Err( - "expected a 412 Precondition Failed error when using an incorrect ETag".into(), - ); - } - Err(err) => { - assert_eq!( - Some(azure_core::http::StatusCode::PreconditionFailed), - err.http_status() - ); - } - } + assert_eq!( + Some(azure_core::http::StatusCode::PreconditionFailed), + response + .expect_err("expected the server to return an error") + .http_status() + ); account.cleanup().await?; Ok(()) @@ -515,32 +501,27 @@ pub async fn item_upsert_if_match_etag(context: TestContext) -> Result<(), Box Result<(), Box { - return Err( - "expected a 412 Precondition Failed error when using an incorrect ETag".into(), - ); - } - Err(err) => { - assert_eq!( - Some(azure_core::http::StatusCode::PreconditionFailed), - err.http_status() - ); - } - } + assert_eq!( + Some(azure_core::http::StatusCode::PreconditionFailed), + response + .expect_err("expected the server to return an error") + .http_status() + ); account.cleanup().await?; Ok(()) @@ -597,31 +570,26 @@ pub async fn item_delete_if_match_etag(context: TestContext) -> Result<(), Box Result<(), Box { - return Err( - "expected a 412 Precondition Failed error when using an incorrect ETag".into(), - ); - } - Err(err) => { - assert_eq!( - Some(azure_core::http::StatusCode::PreconditionFailed), - err.http_status() - ); - } - } + + assert_eq!( + Some(azure_core::http::StatusCode::PreconditionFailed), + response + .expect_err("expected the server to return an error") + .http_status() + ); account.cleanup().await?; Ok(()) @@ -677,12 +638,11 @@ pub async fn item_patch_if_match_etag(context: TestContext) -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box { - return Err( - "expected a 412 Precondition Failed error when using an incorrect ETag".into(), - ); - } - Err(err) => { - assert_eq!( - Some(azure_core::http::StatusCode::PreconditionFailed), - err.http_status() - ); - } - } + assert_eq!( + Some(azure_core::http::StatusCode::PreconditionFailed), + response + .expect_err("expected the server to return an error") + .http_status() + ); account.cleanup().await?; Ok(()) From de2146d18f840d89a4333f09b9efa51709aba7bb Mon Sep 17 00:00:00 2001 From: Gabriela Aragundi Date: Sun, 15 Jun 2025 21:20:07 -0700 Subject: [PATCH 08/12] change log and url fixes --- sdk/cosmos/azure_data_cosmos/CHANGELOG.md | 2 +- sdk/cosmos/azure_data_cosmos/src/options/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/cosmos/azure_data_cosmos/CHANGELOG.md b/sdk/cosmos/azure_data_cosmos/CHANGELOG.md index 843a0050b8..dd246357ec 100644 --- a/sdk/cosmos/azure_data_cosmos/CHANGELOG.md +++ b/sdk/cosmos/azure_data_cosmos/CHANGELOG.md @@ -3,6 +3,7 @@ ## 0.25.0 (Unreleased) ### Features Added +* Added 'if_match_etag' to ItemOptions and necessary functions ([#2705](https://github.com/Azure/azure-sdk-for-rust/pull/2705)) ### Breaking Changes @@ -17,7 +18,6 @@ * Added a function `CosmosClient::with_connection_string` to enable `CosmosClient` creation via connection string. ([#2641](https://github.com/Azure/azure-sdk-for-rust/pull/2641)) * Added support for executing limited cross-partition queries through the Gateway. See for more details on these limitations. ([#2577](https://github.com/Azure/azure-sdk-for-rust/pull/2577)) * Added a preview feature (behind `preview_query_engine` feature flag) to allow the Rust SDK to integrate with an external query engine for performing cross-partition queries. ([#2577](https://github.com/Azure/azure-sdk-for-rust/pull/2577)) -* Added 'if_match_etag' to ItemOptions and necessary functions ([#2705](https://github.com/Azure/azure-sdk-for-rust/pull/2705)) ### Breaking Changes diff --git a/sdk/cosmos/azure_data_cosmos/src/options/mod.rs b/sdk/cosmos/azure_data_cosmos/src/options/mod.rs index 749a9bf28a..333f9dd512 100644 --- a/sdk/cosmos/azure_data_cosmos/src/options/mod.rs +++ b/sdk/cosmos/azure_data_cosmos/src/options/mod.rs @@ -49,7 +49,7 @@ pub struct ItemOptions<'a> { pub method_options: ClientMethodOptions<'a>, /// IfMatchEtag is used to ensure optimistic concurrency control, it helps prevent accidental overwrites and maintains data integrity. /// - /// https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/database-transactions-optimistic-concurrency#optimistic-concurrency-control + /// ``https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/database-transactions-optimistic-concurrency#optimistic-concurrency-control`` pub if_match_etag: Option, /// When this value is true, write operations will respond with the new value of the resource being written. /// From 89c861418131f6ecf2b40820b1f733bc5ff1751a Mon Sep 17 00:00:00 2001 From: Gabriela Aragundi Date: Mon, 16 Jun 2025 12:43:01 -0700 Subject: [PATCH 09/12] comment changes --- sdk/cosmos/azure_data_cosmos/README.md | 2 -- sdk/cosmos/azure_data_cosmos/src/options/mod.rs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk/cosmos/azure_data_cosmos/README.md b/sdk/cosmos/azure_data_cosmos/README.md index fdf8cf175b..2b96d4fa1e 100644 --- a/sdk/cosmos/azure_data_cosmos/README.md +++ b/sdk/cosmos/azure_data_cosmos/README.md @@ -131,8 +131,6 @@ async fn example(cosmos_client: CosmosClient) -> Result<(), Box { #[derive(Clone, Default)] pub struct ItemOptions<'a> { pub method_options: ClientMethodOptions<'a>, - /// IfMatchEtag is used to ensure optimistic concurrency control, it helps prevent accidental overwrites and maintains data integrity. + /// If specified, the operation will only be performed if the item matches the provided Etag. /// - /// ``https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/database-transactions-optimistic-concurrency#optimistic-concurrency-control`` + /// See [Optimistic Concurrency Control](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/database-transactions-optimistic-concurrency#optimistic-concurrency-control) for more. pub if_match_etag: Option, /// When this value is true, write operations will respond with the new value of the resource being written. /// From 4c85a4c3a7865db4ed4678e65e79527b67da4228 Mon Sep 17 00:00:00 2001 From: Gabriela Aragundi Date: Tue, 17 Jun 2025 18:36:18 -0700 Subject: [PATCH 10/12] added test recordings --- sdk/cosmos/assets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/cosmos/assets.json b/sdk/cosmos/assets.json index 7708dc8a42..0e743ffd6e 100644 --- a/sdk/cosmos/assets.json +++ b/sdk/cosmos/assets.json @@ -1,6 +1,6 @@ { "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "rust", - "Tag": "rust/azure_data_cosmos_ff23846344", + "Tag": "rust/azure_data_cosmos_a39b424a5b", "TagPrefix": "rust/azure_data_cosmos" } \ No newline at end of file From ea54eec1e97daa924a8552b4dd6207cca13d0b52 Mon Sep 17 00:00:00 2001 From: Gabriela Aragundi Date: Wed, 18 Jun 2025 12:49:59 -0700 Subject: [PATCH 11/12] changed changelog --- sdk/cosmos/azure_data_cosmos/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/cosmos/azure_data_cosmos/CHANGELOG.md b/sdk/cosmos/azure_data_cosmos/CHANGELOG.md index dd246357ec..37e4ad5a35 100644 --- a/sdk/cosmos/azure_data_cosmos/CHANGELOG.md +++ b/sdk/cosmos/azure_data_cosmos/CHANGELOG.md @@ -3,7 +3,7 @@ ## 0.25.0 (Unreleased) ### Features Added -* Added 'if_match_etag' to ItemOptions and necessary functions ([#2705](https://github.com/Azure/azure-sdk-for-rust/pull/2705)) +* Added 'if_match_etag' to ItemOptions ([#2705](https://github.com/Azure/azure-sdk-for-rust/pull/2705)) ### Breaking Changes From 67ef54fad03a01621ecb3d3f5198397b1e6c01c2 Mon Sep 17 00:00:00 2001 From: gsa9989 <117786401+gsa9989@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:57:57 -0700 Subject: [PATCH 12/12] Update sdk/cosmos/azure_data_cosmos/CHANGELOG.md Co-authored-by: Heath Stewart --- sdk/cosmos/azure_data_cosmos/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/cosmos/azure_data_cosmos/CHANGELOG.md b/sdk/cosmos/azure_data_cosmos/CHANGELOG.md index 37e4ad5a35..44adcf0acd 100644 --- a/sdk/cosmos/azure_data_cosmos/CHANGELOG.md +++ b/sdk/cosmos/azure_data_cosmos/CHANGELOG.md @@ -3,7 +3,7 @@ ## 0.25.0 (Unreleased) ### Features Added -* Added 'if_match_etag' to ItemOptions ([#2705](https://github.com/Azure/azure-sdk-for-rust/pull/2705)) +* Added `if_match_etag` to `ItemOptions` ([#2705](https://github.com/Azure/azure-sdk-for-rust/pull/2705)) ### Breaking Changes