Skip to content

[Storage] get_account_info all clients, set/get_tags for BlobClient, set_properties for BlobServiceClient #2795

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

Draft
wants to merge 28 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b5bed2e
start_copy_from_url, set_properties
vincenttran-msft Jul 15, 2025
7e7f9a9
get_account_info, set/get tags
vincenttran-msft Jul 15, 2025
2c40e86
Regenerate against .tsp
vincenttran-msft Jul 15, 2025
d133283
Test recordings
vincenttran-msft Jul 15, 2025
c47b704
Resolve rustdoc error bare-urls
vincenttran-msft Jul 15, 2025
4fc6dba
Recordings update
vincenttran-msft Jul 15, 2025
6e36e3a
nit
vincenttran-msft Jul 18, 2025
b8a375b
Regen after omitting start_copy_from_url
vincenttran-msft Jul 21, 2025
2633e4d
Refactor that any cx facing is HashMap
vincenttran-msft Jul 21, 2025
3c3d716
nit
vincenttran-msft Jul 21, 2025
b7ca0f6
Moved asset file
vincenttran-msft Jul 23, 2025
e67a13c
nit
vincenttran-msft Jul 23, 2025
0715c07
Merge branch 'main' into vincenttran/post_v3_pr
vincenttran-msft Jul 29, 2025
7fc3b5c
Merge branch 'main' into vincenttran/post_v3_pr
vincenttran-msft Jul 30, 2025
1c1a166
Use BTree for fixed-ordering, re-record
vincenttran-msft Jul 30, 2025
87e9d4e
nit
vincenttran-msft Jul 30, 2025
1085fe0
Merge branch 'main' into vincenttran/post_v3_pr
vincenttran-msft Aug 4, 2025
f24aaed
Refactor to new azure-core code, now hitting error case
vincenttran-msft Aug 4, 2025
ab7e12b
Merge branch 'main' into vincenttran/post_v3_pr
vincenttran-msft Aug 5, 2025
9fa4395
Refactor for v20 emitter changes
vincenttran-msft Aug 5, 2025
8c390ef
Added TryFrom BlobTags -> HashMap
vincenttran-msft Aug 14, 2025
c30c1b3
Merge branch 'main' into vincenttran/post_v3_pr
vincenttran-msft Aug 14, 2025
aa92db4
Re-record tests, add commutativity
vincenttran-msft Aug 14, 2025
61def99
nit
vincenttran-msft Aug 14, 2025
790e3fa
Regen against new feature/blob-tsp-rust
vincenttran-msft Aug 15, 2025
010c18b
Regen against feature/blob-tsp-rust
vincenttran-msft Aug 15, 2025
c243588
Add parser that is context aware, remove sort in prod code
vincenttran-msft Aug 18, 2025
5f057e9
Revert, unit test flag
vincenttran-msft Aug 18, 2025
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
2 changes: 1 addition & 1 deletion sdk/storage/azure_storage_blob/assets.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "rust",
"Tag": "rust/azure_storage_blob_49079e88a1",
"Tag": "rust/azure_storage_blob_cb9b90a35f",
"TagPrefix": "rust/azure_storage_blob"
}
10 changes: 4 additions & 6 deletions sdk/storage/azure_storage_blob/src/clients/blob_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.

use crate::{
deserialize_blob_tags,
generated::clients::BlobClient as GeneratedBlobClient,
generated::models::{
BlobClientAcquireLeaseResult, BlobClientBreakLeaseResult, BlobClientChangeLeaseResult,
Expand All @@ -21,7 +20,7 @@ use crate::{
BlockListType, BlockLookupList,
},
pipeline::StorageHeadersPolicy,
serialize_blob_tags, AppendBlobClient, BlobClientOptions, BlockBlobClient, PageBlobClient,
AppendBlobClient, BlobClientOptions, BlockBlobClient, PageBlobClient,
};
use azure_core::{
credentials::TokenCredential,
Expand Down Expand Up @@ -325,7 +324,7 @@ impl BlobClient {
tags: HashMap<String, String>,
options: Option<BlobClientSetTagsOptions<'_>>,
) -> Result<Response<(), NoFormat>> {
let blob_tags = serialize_blob_tags(tags);
let blob_tags: BlobTags = tags.into();
self.client
.set_tags(RequestContent::try_from(blob_tags)?, options)
.await
Expand All @@ -339,9 +338,8 @@ impl BlobClient {
pub async fn get_tags(
&self,
options: Option<BlobClientGetTagsOptions<'_>>,
) -> Result<Response<HashMap<String, String>, JsonFormat>> {
let response = self.client.get_tags(options).await?;
deserialize_blob_tags(response).await
) -> Result<Response<BlobTags, XmlFormat>> {
self.client.get_tags(options).await
}

/// Gets information related to the Storage account in which the blob resides.
Expand Down
44 changes: 43 additions & 1 deletion sdk/storage/azure_storage_blob/src/models/extensions.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

use crate::models::{AppendBlobClientCreateOptions, PageBlobClientCreateOptions};
use crate::models::{
AppendBlobClientCreateOptions, BlobTag, BlobTags, PageBlobClientCreateOptions,
};
use std::collections::{BTreeMap, HashMap};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're still importing BTreeMap unnecessarily, which should cause a build break (lint warning as error).


/// Provides usage helpers for setting the `PageBlobClientCreateOptions` optional configurations.
pub trait PageBlobClientCreateOptionsExt {
Expand Down Expand Up @@ -38,3 +41,42 @@ impl AppendBlobClientCreateOptionsExt for AppendBlobClientCreateOptions<'_> {
}
}
}

/// Converts a `BlobTags` struct into `HashMap<String, String>`.
impl TryFrom<BlobTags> for HashMap<String, String> {
type Error = &'static str;

fn try_from(blob_tags: BlobTags) -> Result<Self, Self::Error> {
let mut map = HashMap::new();

if let Some(tags) = blob_tags.blob_tag_set {
for tag in tags {
match (tag.key, tag.value) {
(Some(k), Some(v)) => {
map.insert(k, v);
}
_ => return Err("BlobTag missing key or value"),
}
}
}

Ok(map)
}
}

/// Converts a `HashMap<String, String>` into a `BlobTags` struct.
impl From<HashMap<String, String>> for BlobTags {
fn from(tags: HashMap<String, String>) -> Self {
let sorted_tags: BTreeMap<_, _> = tags.into_iter().collect();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why use a BTreeMap for this when you can do this more efficiently with a Vec::with_capacity:
    let mut map: HashMap<String, String> = HashMap::new();
    map.insert("foo".to_string(), "1".to_string());
    map.insert("bar".to_string(), "2".to_string());

    let mut v: Vec<(String, String)> = Vec::with_capacity(map.len());
    v.extend(map);
    v.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
  1. Why sort them anyway? If for testing, then only sort in test. This is a waste of time and memory for customers if not otherwise required.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Heath, good callout here, I believed I was using BTreeMap from a previous suggestion you made, but it was asked out of context and agree that sorting here in prod is unnecessary.

However, I am still struggling to figure out how to go about this without disabling body matching. Your above suggestion:

#[cfg(test)]
let mut tags = std::collections::BTreeMap::new();
#[cfg(not(test))]
let mut tags = std::collections::HashMap::new();

I don't believe is applicable to us, since these are integration tests and #[cfg(test)] is used to conditionally compile unit tests.
Thus, I am still unsure how we could conditionally execute different code while keeping the production code intact i.e. a mock or monkey patch of some sort?

let blob_tags = sorted_tags
.into_iter()
.map(|(k, v)| BlobTag {
key: Some(k),
value: Some(v),
})
.collect();
BlobTags {
blob_tag_set: Some(blob_tags),
}
}
}
48 changes: 0 additions & 48 deletions sdk/storage/azure_storage_blob/src/parsers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,51 +40,3 @@ pub fn format_page_range(offset: u64, length: u64) -> Result<String, Error> {
let content_range = format!("bytes={}-{}", offset, end_range);
Ok(content_range)
}

/// Takes in a HashMap of blob tags and serializes them into the `BlobTags` model.
///
/// # Arguments
///
/// * `tags` - A hash map containing the name-value pairs associated with the blob as tags.
pub(crate) fn serialize_blob_tags(tags: HashMap<String, String>) -> BlobTags {
let sorted_tags: BTreeMap<_, _> = tags.into_iter().collect();
let blob_tags = sorted_tags
.into_iter()
.map(|(k, v)| BlobTag {
key: Some(k),
value: Some(v),
})
.collect();
BlobTags {
blob_tag_set: Some(blob_tags),
}
}

/// Takes in a `get_tags()` response and deserializes the `BlobTags` model into a HashMap of blob tags.
///
/// # Arguments
///
/// * `response` - The `get_tags()` response to be deserialized.
pub(crate) async fn deserialize_blob_tags(
response: Response<BlobTags, XmlFormat>,
) -> azure_core::Result<Response<HashMap<String, String>, JsonFormat>>
where
{
let mut blob_tags_map: HashMap<String, String> = HashMap::new();
let status = response.status();
let headers = response.headers().clone();
let blob_tags = response.into_body().await?;

if let Some(blob_tag_set) = blob_tags.blob_tag_set {
for tag in blob_tag_set {
if let (Some(k), Some(v)) = (tag.key, tag.value) {
blob_tags_map.insert(k, v);
}
}
}

let request_content: RequestContent<HashMap<String, String>> =
RequestContent::try_from(blob_tags_map)?;
let raw_response = RawResponse::from_bytes(status, headers, request_content.body());
Ok(raw_response.into())
}
6 changes: 4 additions & 2 deletions sdk/storage/azure_storage_blob/tests/blob_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,14 +442,16 @@ async fn test_blob_tags(ctx: TestContext) -> Result<(), Box<dyn Error>> {

// Assert
let response_tags = blob_client.get_tags(None).await?.into_body().await?;
assert_eq!(blob_tags, response_tags);
let map: HashMap<String, String> = response_tags.try_into()?;
assert_eq!(blob_tags, map);

// Set Tags with No Tags (Clear Tags)
blob_client.set_tags(HashMap::new(), None).await?;

// Assert
let response_tags = blob_client.get_tags(None).await?.into_body().await?;
assert_eq!(HashMap::new(), response_tags);
let map: HashMap<String, String> = response_tags.try_into()?;
assert_eq!(HashMap::new(), map);

container_client.delete_container(None).await?;
Ok(())
Expand Down