From b5bed2e016acdd5f2f6946c044425440fb3dabbb Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 15 Jul 2025 14:00:16 -0700 Subject: [PATCH 01/24] start_copy_from_url, set_properties --- .../src/clients/blob_client.rs | 28 ++++++++++++-- .../src/clients/blob_service_client.rs | 21 ++++++++++- .../azure_storage_blob/src/models/mod.rs | 30 ++++++++------- .../azure_storage_blob/tests/blob_client.rs | 37 ++++++++++++++++++- .../tests/blob_service_client.rs | 26 +++++++++++++ 5 files changed, 120 insertions(+), 22 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index 1c2564d279..72ebd82665 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -6,16 +6,17 @@ use crate::{ generated::models::{ BlobClientAcquireLeaseResult, BlobClientBreakLeaseResult, BlobClientChangeLeaseResult, BlobClientDownloadResult, BlobClientGetPropertiesResult, BlobClientReleaseLeaseResult, - BlobClientRenewLeaseResult, BlockBlobClientCommitBlockListResult, - BlockBlobClientStageBlockResult, BlockBlobClientUploadResult, + BlobClientRenewLeaseResult, BlobClientStartCopyFromUrlResult, + BlockBlobClientCommitBlockListResult, BlockBlobClientStageBlockResult, + BlockBlobClientUploadResult, }, models::{ AccessTier, BlobClientAcquireLeaseOptions, BlobClientBreakLeaseOptions, BlobClientChangeLeaseOptions, BlobClientDeleteOptions, BlobClientDownloadOptions, BlobClientGetPropertiesOptions, BlobClientReleaseLeaseOptions, BlobClientRenewLeaseOptions, BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlobClientSetTierOptions, - BlockBlobClientCommitBlockListOptions, BlockBlobClientUploadOptions, BlockList, - BlockListType, BlockLookupList, + BlobClientStartCopyFromUrlOptions, BlockBlobClientCommitBlockListOptions, + BlockBlobClientUploadOptions, BlockList, BlockListType, BlockLookupList, }, pipeline::StorageHeadersPolicy, AppendBlobClient, BlobClientOptions, BlockBlobClient, PageBlobClient, @@ -313,4 +314,23 @@ impl BlobClient { ) -> Result> { self.client.renew_lease(lease_id, options).await } + + /// Copies a blob or an internet resource to a new blob. + /// + /// # Arguments + /// + /// * `copy_source` - A URL of up to 2 KB in length that specifies a file or blob. + /// The value should be URL-encoded as it would appear in a request URI. + /// If the source is in another account, the source must either be public + /// or must be authenticated via a shared access signature. If the source + /// is public, no authentication is required. + /// Example: https://myaccount.blob.core.windows.net/mycontainer/myblob + /// * `options` - Optional configuration for the request. + pub async fn start_copy_from_url( + &self, + copy_source: String, + options: Option>, + ) -> Result> { + self.client.start_copy_from_url(copy_source, options).await + } } diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs index 1f2c04bec8..60b1d8156b 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs @@ -5,7 +5,8 @@ use crate::{ generated::clients::BlobServiceClient as GeneratedBlobServiceClient, models::{ BlobServiceClientGetPropertiesOptions, BlobServiceClientListContainersSegmentOptions, - ListContainersSegmentResponse, StorageServiceProperties, + BlobServiceClientSetPropertiesOptions, ListContainersSegmentResponse, + StorageServiceProperties, }, pipeline::StorageHeadersPolicy, BlobContainerClient, BlobServiceClientOptions, @@ -14,7 +15,7 @@ use azure_core::{ credentials::TokenCredential, http::{ policies::{BearerTokenCredentialPolicy, Policy}, - PageIterator, Response, Url, XmlFormat, + NoFormat, PageIterator, RequestContent, Response, Url, XmlFormat, }, Result, }; @@ -104,4 +105,20 @@ impl BlobServiceClient { ) -> Result>> { self.client.list_containers_segment(options) } + + /// Sets properties for a Storage account's Blob service endpoint, including properties for Storage Analytics and CORS rules. + /// + /// # Arguments + /// + /// * `storage_service_properties` - The Storage service properties to set. + /// * `options` - Optional configuration for the request. + pub async fn set_properties( + &self, + storage_service_properties: RequestContent, + options: Option>, + ) -> Result> { + self.client + .set_properties(storage_service_properties, options) + .await + } } diff --git a/sdk/storage/azure_storage_blob/src/models/mod.rs b/sdk/storage/azure_storage_blob/src/models/mod.rs index cd90fd3616..9bd29d7540 100644 --- a/sdk/storage/azure_storage_blob/src/models/mod.rs +++ b/sdk/storage/azure_storage_blob/src/models/mod.rs @@ -19,7 +19,8 @@ pub use crate::generated::models::{ BlobClientReleaseLeaseOptions, BlobClientReleaseLeaseResult, BlobClientReleaseLeaseResultHeaders, BlobClientRenewLeaseOptions, BlobClientRenewLeaseResult, BlobClientRenewLeaseResultHeaders, BlobClientSetMetadataOptions, - BlobClientSetPropertiesOptions, BlobClientSetTierOptions, + BlobClientSetPropertiesOptions, BlobClientSetTierOptions, BlobClientStartCopyFromUrlOptions, + BlobClientStartCopyFromUrlResult, BlobClientStartCopyFromUrlResultHeaders, BlobContainerClientAcquireLeaseOptions, BlobContainerClientAcquireLeaseResult, BlobContainerClientAcquireLeaseResultHeaders, BlobContainerClientBreakLeaseOptions, BlobContainerClientBreakLeaseResult, BlobContainerClientBreakLeaseResultHeaders, @@ -31,18 +32,19 @@ pub use crate::generated::models::{ BlobContainerClientReleaseLeaseResult, BlobContainerClientReleaseLeaseResultHeaders, BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, BlobContainerClientSetMetadataOptions, BlobImmutabilityPolicyMode, - BlobServiceClientGetPropertiesOptions, BlobServiceClientListContainersSegmentOptions, BlobType, - BlockBlobClientCommitBlockListOptions, BlockBlobClientCommitBlockListResult, - BlockBlobClientCommitBlockListResultHeaders, BlockBlobClientGetBlockListOptions, - BlockBlobClientStageBlockOptions, BlockBlobClientStageBlockResult, - BlockBlobClientStageBlockResultHeaders, BlockBlobClientUploadOptions, - BlockBlobClientUploadResult, BlockBlobClientUploadResultHeaders, BlockList, BlockListType, - BlockLookupList, CopyStatus, LeaseState, LeaseStatus, ListBlobsFlatSegmentResponse, - ListContainersSegmentResponse, PageBlobClientClearPagesOptions, PageBlobClientClearPagesResult, - PageBlobClientClearPagesResultHeaders, PageBlobClientCreateOptions, PageBlobClientCreateResult, - PageBlobClientCreateResultHeaders, PageBlobClientResizeOptions, PageBlobClientResizeResult, - PageBlobClientResizeResultHeaders, PageBlobClientUploadPagesOptions, - PageBlobClientUploadPagesResult, PageBlobClientUploadPagesResultHeaders, PublicAccessType, - RehydratePriority, StorageServiceProperties, + BlobServiceClientGetPropertiesOptions, BlobServiceClientListContainersSegmentOptions, + BlobServiceClientSetPropertiesOptions, BlobType, BlockBlobClientCommitBlockListOptions, + BlockBlobClientCommitBlockListResult, BlockBlobClientCommitBlockListResultHeaders, + BlockBlobClientGetBlockListOptions, BlockBlobClientStageBlockOptions, + BlockBlobClientStageBlockResult, BlockBlobClientStageBlockResultHeaders, + BlockBlobClientUploadOptions, BlockBlobClientUploadResult, BlockBlobClientUploadResultHeaders, + BlockList, BlockListType, BlockLookupList, CopyStatus, LeaseState, LeaseStatus, + ListBlobsFlatSegmentResponse, ListContainersSegmentResponse, PageBlobClientClearPagesOptions, + PageBlobClientClearPagesResult, PageBlobClientClearPagesResultHeaders, + PageBlobClientCreateOptions, PageBlobClientCreateResult, PageBlobClientCreateResultHeaders, + PageBlobClientResizeOptions, PageBlobClientResizeResult, PageBlobClientResizeResultHeaders, + PageBlobClientUploadPagesOptions, PageBlobClientUploadPagesResult, + PageBlobClientUploadPagesResultHeaders, PublicAccessType, RehydratePriority, + StorageServiceProperties, }; pub use extensions::*; diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index 5d5818b2f9..759a12111a 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -10,8 +10,8 @@ use azure_storage_blob::models::{ AccessTier, BlobClientAcquireLeaseResultHeaders, BlobClientChangeLeaseResultHeaders, BlobClientDownloadOptions, BlobClientDownloadResultHeaders, BlobClientGetPropertiesOptions, BlobClientGetPropertiesResultHeaders, BlobClientSetMetadataOptions, - BlobClientSetPropertiesOptions, BlobClientSetTierOptions, BlockBlobClientUploadOptions, - LeaseState, + BlobClientSetPropertiesOptions, BlobClientSetTierOptions, + BlobClientStartCopyFromUrlResultHeaders, BlockBlobClientUploadOptions, CopyStatus, LeaseState, }; use azure_storage_blob_test::{create_test_blob, get_blob_name, get_container_client}; use std::{collections::HashMap, error::Error, time::Duration}; @@ -423,3 +423,36 @@ async fn test_leased_blob_operations(ctx: TestContext) -> Result<(), Box Result<(), Box> { + // Recording Setup + let recording = ctx.recording(); + let container_client = get_container_client(recording, true).await?; + let source_blob_client = container_client.blob_client(get_blob_name(recording)); + create_test_blob(&source_blob_client).await?; + + let blob_client = container_client.blob_client("destination_blob".to_string()); + let source_url = format!( + "{}{}/{}", + source_blob_client.endpoint().as_str(), + source_blob_client.container_name(), + source_blob_client.blob_name() + ); + let response = blob_client.start_copy_from_url(source_url, None).await?; + let (_, _, source_content) = source_blob_client.download(None).await?.deconstruct(); + let (_, _, copied_content) = blob_client.download(None).await?.deconstruct(); + + // Assert + let copy_status = response.copy_status()?; + let copy_id = response.copy_id()?; + assert_eq!(CopyStatus::Success, copy_status.unwrap()); + assert!(copy_id.is_some()); + assert_eq!( + source_content.collect().await?, + copied_content.collect().await? + ); + + container_client.delete_container(None).await?; + Ok(()) +} diff --git a/sdk/storage/azure_storage_blob/tests/blob_service_client.rs b/sdk/storage/azure_storage_blob/tests/blob_service_client.rs index db5ac45d91..80dc8cb06e 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_service_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_service_client.rs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use azure_core::http::RequestContent; use azure_core_test::{recorded, TestContext}; use azure_storage_blob::models::{ BlobServiceClientGetPropertiesOptions, BlobServiceClientListContainersSegmentOptions, + StorageServiceProperties, }; use azure_storage_blob_test::{get_blob_service_client, get_container_name}; use futures::StreamExt; @@ -122,3 +124,27 @@ async fn test_list_containers_with_continuation(ctx: TestContext) -> Result<(), Ok(()) } + +#[recorded::test] +async fn test_set_service_properties(ctx: TestContext) -> Result<(), Box> { + // Recording Setup + let recording = ctx.recording(); + let service_client = get_blob_service_client(recording)?; + + // Storage Service Properties + let storage_service_properties = StorageServiceProperties { + default_service_version: Some("2022-11-02".to_string()), + ..Default::default() + }; + let request_content: RequestContent = + storage_service_properties.try_into()?; + + service_client.set_properties(request_content, None).await?; + + // Assert + let response = service_client.get_properties(None).await?; + let storage_service_properties = response.into_body().await?; + let default_service_version = storage_service_properties.default_service_version; + assert_eq!("2022-11-02".to_string(), default_service_version.unwrap()); + Ok(()) +} From 7e7f9a90f6525432efa9e19594de862a0b799eff Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 15 Jul 2025 14:21:25 -0700 Subject: [PATCH 02/24] get_account_info, set/get tags --- .../src/clients/blob_client.rs | 65 ++++++++++++++++--- .../src/clients/blob_container_client.rs | 26 ++++++-- .../src/clients/blob_service_client.rs | 20 +++++- .../azure_storage_blob/src/models/mod.rs | 48 +++++++------- sdk/storage/azure_storage_blob/src/parsers.rs | 23 +++++++ .../azure_storage_blob/tests/blob_client.rs | 63 +++++++++++++++++- .../tests/blob_container_client.rs | 22 ++++++- .../tests/blob_service_client.rs | 20 ++++++ .../azure_storage_blob_test/src/lib.rs | 31 ++++++++- 9 files changed, 273 insertions(+), 45 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index 72ebd82665..cb38776779 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -5,21 +5,22 @@ use crate::{ generated::clients::BlobClient as GeneratedBlobClient, generated::models::{ BlobClientAcquireLeaseResult, BlobClientBreakLeaseResult, BlobClientChangeLeaseResult, - BlobClientDownloadResult, BlobClientGetPropertiesResult, BlobClientReleaseLeaseResult, - BlobClientRenewLeaseResult, BlobClientStartCopyFromUrlResult, - BlockBlobClientCommitBlockListResult, BlockBlobClientStageBlockResult, - BlockBlobClientUploadResult, + BlobClientDownloadResult, BlobClientGetAccountInfoResult, BlobClientGetPropertiesResult, + BlobClientReleaseLeaseResult, BlobClientRenewLeaseResult, BlobClientSetTagsResult, + BlobClientStartCopyFromUrlResult, BlockBlobClientCommitBlockListResult, + BlockBlobClientStageBlockResult, BlockBlobClientUploadResult, }, models::{ AccessTier, BlobClientAcquireLeaseOptions, BlobClientBreakLeaseOptions, BlobClientChangeLeaseOptions, BlobClientDeleteOptions, BlobClientDownloadOptions, - BlobClientGetPropertiesOptions, BlobClientReleaseLeaseOptions, BlobClientRenewLeaseOptions, - BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlobClientSetTierOptions, - BlobClientStartCopyFromUrlOptions, BlockBlobClientCommitBlockListOptions, + BlobClientGetAccountInfoOptions, BlobClientGetPropertiesOptions, BlobClientGetTagsOptions, + BlobClientReleaseLeaseOptions, BlobClientRenewLeaseOptions, BlobClientSetMetadataOptions, + BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, BlobClientSetTierOptions, + BlobClientStartCopyFromUrlOptions, BlobTags, BlockBlobClientCommitBlockListOptions, BlockBlobClientUploadOptions, BlockList, BlockListType, BlockLookupList, }, pipeline::StorageHeadersPolicy, - AppendBlobClient, BlobClientOptions, BlockBlobClient, PageBlobClient, + serialize_blob_tags, AppendBlobClient, BlobClientOptions, BlockBlobClient, PageBlobClient, }; use azure_core::{ credentials::TokenCredential, @@ -29,6 +30,7 @@ use azure_core::{ }, Bytes, Result, }; +use std::collections::HashMap; use std::sync::Arc; /// A client to interact with a specific Azure storage blob, although that blob may not yet exist. @@ -333,4 +335,51 @@ impl BlobClient { ) -> Result> { self.client.start_copy_from_url(copy_source, options).await } + + /// Sets tags on a blob. Note that each call to this operation replaces all existing tags. To remove + /// all tags from the blob, call this operation with no tags specified. + /// + /// # Arguments + /// + /// * `tags` - Name-value pairs associated with the blob as tag. Tags are case-sensitive. + /// The tag set may contain at most 10 tags. Tag keys must be between 1 and 128 characters, + /// and tag values must be between 0 and 256 characters. + /// Valid tag key and value characters include: lowercase and uppercase letters, digits (0-9), + /// space (' '), plus (+), minus (-), period (.), solidus (/), colon (:), equals (=), underscore (_) + /// * `options` - Optional configuration for the request. + pub async fn set_tags( + &self, + tags: HashMap, + options: Option>, + ) -> Result> { + let blob_tags = serialize_blob_tags(tags); + self.client + .set_tags(RequestContent::try_from(blob_tags)?, options) + .await + } + + /// Gets the tags on a blob. + /// + /// # Arguments + /// + /// * `options` - Optional configuration for the request. + pub async fn get_tags( + &self, + options: Option>, + ) -> Result> { + self.client.get_tags(options).await + } + + /// Gets information related to the Storage account in which the blob resides. + /// This includes the `sku_name` and `account_kind`. + /// + /// # Arguments + /// + /// * `options` - Optional configuration for the request. + pub async fn get_account_info( + &self, + options: Option>, + ) -> Result> { + self.client.get_account_info(options).await + } } diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs index 01a785cb26..1b397e2ca3 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs @@ -5,16 +5,17 @@ use crate::{ generated::clients::BlobContainerClient as GeneratedBlobContainerClient, generated::models::{ BlobContainerClientAcquireLeaseResult, BlobContainerClientBreakLeaseResult, - BlobContainerClientChangeLeaseResult, BlobContainerClientGetPropertiesResult, - BlobContainerClientReleaseLeaseResult, BlobContainerClientRenewLeaseResult, + BlobContainerClientChangeLeaseResult, BlobContainerClientGetAccountInfoResult, + BlobContainerClientGetPropertiesResult, BlobContainerClientReleaseLeaseResult, + BlobContainerClientRenewLeaseResult, }, models::{ BlobContainerClientAcquireLeaseOptions, BlobContainerClientBreakLeaseOptions, BlobContainerClientChangeLeaseOptions, BlobContainerClientCreateOptions, - BlobContainerClientDeleteOptions, BlobContainerClientGetPropertiesOptions, - BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions, - BlobContainerClientRenewLeaseOptions, BlobContainerClientSetMetadataOptions, - ListBlobsFlatSegmentResponse, + BlobContainerClientDeleteOptions, BlobContainerClientGetAccountInfoOptions, + BlobContainerClientGetPropertiesOptions, BlobContainerClientListBlobFlatSegmentOptions, + BlobContainerClientReleaseLeaseOptions, BlobContainerClientRenewLeaseOptions, + BlobContainerClientSetMetadataOptions, ListBlobsFlatSegmentResponse, }, pipeline::StorageHeadersPolicy, BlobClient, BlobContainerClientOptions, @@ -242,4 +243,17 @@ impl BlobContainerClient { ) -> Result> { self.client.renew_lease(lease_id, options).await } + + /// Gets information related to the Storage account in which the container resides. + /// This includes the `sku_name` and `account_kind`. + /// + /// # Arguments + /// + /// * `options` - Optional configuration for the request. + pub async fn get_account_info( + &self, + options: Option>, + ) -> Result> { + self.client.get_account_info(options).await + } } diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs index 60b1d8156b..1b049f7265 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs @@ -3,10 +3,11 @@ use crate::{ generated::clients::BlobServiceClient as GeneratedBlobServiceClient, + generated::models::BlobServiceClientGetAccountInfoResult, models::{ - BlobServiceClientGetPropertiesOptions, BlobServiceClientListContainersSegmentOptions, - BlobServiceClientSetPropertiesOptions, ListContainersSegmentResponse, - StorageServiceProperties, + BlobServiceClientGetAccountInfoOptions, BlobServiceClientGetPropertiesOptions, + BlobServiceClientListContainersSegmentOptions, BlobServiceClientSetPropertiesOptions, + ListContainersSegmentResponse, StorageServiceProperties, }, pipeline::StorageHeadersPolicy, BlobContainerClient, BlobServiceClientOptions, @@ -121,4 +122,17 @@ impl BlobServiceClient { .set_properties(storage_service_properties, options) .await } + + /// Gets information related to the Storage account. + /// This includes the `sku_name` and `account_kind`. + /// + /// # Arguments + /// + /// * `options` - Optional configuration for the request. + pub async fn get_account_info( + &self, + options: Option>, + ) -> Result> { + self.client.get_account_info(options).await + } } diff --git a/sdk/storage/azure_storage_blob/src/models/mod.rs b/sdk/storage/azure_storage_blob/src/models/mod.rs index 9bd29d7540..5f9c3a3b68 100644 --- a/sdk/storage/azure_storage_blob/src/models/mod.rs +++ b/sdk/storage/azure_storage_blob/src/models/mod.rs @@ -4,7 +4,7 @@ mod extensions; pub use crate::generated::models::{ - AccessTier, AppendBlobClientAppendBlockFromUrlOptions, + AccessTier, AccountKind, AppendBlobClientAppendBlockFromUrlOptions, AppendBlobClientAppendBlockFromUrlResult, AppendBlobClientAppendBlockFromUrlResultHeaders, AppendBlobClientAppendBlockOptions, AppendBlobClientAppendBlockResult, AppendBlobClientAppendBlockResultHeaders, AppendBlobClientCreateOptions, @@ -14,37 +14,41 @@ pub use crate::generated::models::{ BlobClientAcquireLeaseResultHeaders, BlobClientBreakLeaseOptions, BlobClientBreakLeaseResult, BlobClientBreakLeaseResultHeaders, BlobClientChangeLeaseOptions, BlobClientChangeLeaseResult, BlobClientChangeLeaseResultHeaders, BlobClientDeleteOptions, BlobClientDownloadOptions, - BlobClientDownloadResult, BlobClientDownloadResultHeaders, BlobClientGetPropertiesOptions, - BlobClientGetPropertiesResult, BlobClientGetPropertiesResultHeaders, - BlobClientReleaseLeaseOptions, BlobClientReleaseLeaseResult, - BlobClientReleaseLeaseResultHeaders, BlobClientRenewLeaseOptions, BlobClientRenewLeaseResult, - BlobClientRenewLeaseResultHeaders, BlobClientSetMetadataOptions, - BlobClientSetPropertiesOptions, BlobClientSetTierOptions, BlobClientStartCopyFromUrlOptions, + BlobClientDownloadResult, BlobClientDownloadResultHeaders, BlobClientGetAccountInfoOptions, + BlobClientGetAccountInfoResult, BlobClientGetAccountInfoResultHeaders, + BlobClientGetPropertiesOptions, BlobClientGetPropertiesResult, + BlobClientGetPropertiesResultHeaders, BlobClientGetTagsOptions, BlobClientReleaseLeaseOptions, + BlobClientReleaseLeaseResult, BlobClientReleaseLeaseResultHeaders, BlobClientRenewLeaseOptions, + BlobClientRenewLeaseResult, BlobClientRenewLeaseResultHeaders, BlobClientSetMetadataOptions, + BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, BlobClientSetTagsResult, + BlobClientSetTagsResultHeaders, BlobClientSetTierOptions, BlobClientStartCopyFromUrlOptions, BlobClientStartCopyFromUrlResult, BlobClientStartCopyFromUrlResultHeaders, BlobContainerClientAcquireLeaseOptions, BlobContainerClientAcquireLeaseResult, BlobContainerClientAcquireLeaseResultHeaders, BlobContainerClientBreakLeaseOptions, BlobContainerClientBreakLeaseResult, BlobContainerClientBreakLeaseResultHeaders, BlobContainerClientChangeLeaseOptions, BlobContainerClientChangeLeaseResult, BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientCreateOptions, - BlobContainerClientDeleteOptions, BlobContainerClientGetPropertiesOptions, + BlobContainerClientDeleteOptions, BlobContainerClientGetAccountInfoOptions, + BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesOptions, BlobContainerClientGetPropertiesResult, BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions, BlobContainerClientReleaseLeaseResult, BlobContainerClientReleaseLeaseResultHeaders, BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, BlobContainerClientSetMetadataOptions, BlobImmutabilityPolicyMode, - BlobServiceClientGetPropertiesOptions, BlobServiceClientListContainersSegmentOptions, - BlobServiceClientSetPropertiesOptions, BlobType, BlockBlobClientCommitBlockListOptions, - BlockBlobClientCommitBlockListResult, BlockBlobClientCommitBlockListResultHeaders, - BlockBlobClientGetBlockListOptions, BlockBlobClientStageBlockOptions, - BlockBlobClientStageBlockResult, BlockBlobClientStageBlockResultHeaders, - BlockBlobClientUploadOptions, BlockBlobClientUploadResult, BlockBlobClientUploadResultHeaders, - BlockList, BlockListType, BlockLookupList, CopyStatus, LeaseState, LeaseStatus, - ListBlobsFlatSegmentResponse, ListContainersSegmentResponse, PageBlobClientClearPagesOptions, - PageBlobClientClearPagesResult, PageBlobClientClearPagesResultHeaders, - PageBlobClientCreateOptions, PageBlobClientCreateResult, PageBlobClientCreateResultHeaders, - PageBlobClientResizeOptions, PageBlobClientResizeResult, PageBlobClientResizeResultHeaders, - PageBlobClientUploadPagesOptions, PageBlobClientUploadPagesResult, - PageBlobClientUploadPagesResultHeaders, PublicAccessType, RehydratePriority, - StorageServiceProperties, + BlobServiceClientGetAccountInfoOptions, BlobServiceClientGetAccountInfoResult, + BlobServiceClientGetAccountInfoResultHeaders, BlobServiceClientGetPropertiesOptions, + BlobServiceClientListContainersSegmentOptions, BlobServiceClientSetPropertiesOptions, BlobTags, + BlobType, BlockBlobClientCommitBlockListOptions, BlockBlobClientCommitBlockListResult, + BlockBlobClientCommitBlockListResultHeaders, BlockBlobClientGetBlockListOptions, + BlockBlobClientStageBlockOptions, BlockBlobClientStageBlockResult, + BlockBlobClientStageBlockResultHeaders, BlockBlobClientUploadOptions, + BlockBlobClientUploadResult, BlockBlobClientUploadResultHeaders, BlockList, BlockListType, + BlockLookupList, CopyStatus, LeaseState, LeaseStatus, ListBlobsFlatSegmentResponse, + ListContainersSegmentResponse, PageBlobClientClearPagesOptions, PageBlobClientClearPagesResult, + PageBlobClientClearPagesResultHeaders, PageBlobClientCreateOptions, PageBlobClientCreateResult, + PageBlobClientCreateResultHeaders, PageBlobClientResizeOptions, PageBlobClientResizeResult, + PageBlobClientResizeResultHeaders, PageBlobClientUploadPagesOptions, + PageBlobClientUploadPagesResult, PageBlobClientUploadPagesResultHeaders, PublicAccessType, + RehydratePriority, StorageServiceProperties, }; pub use extensions::*; diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index e3c36c42d5..be6de8576b 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use crate::generated::models::{BlobTag, BlobTags}; +use azure_core::http::RequestContent; +use std::collections::HashMap; use std::io::{Error, ErrorKind}; /// Takes in an offset and a length, verifies alignment to a 512-byte boundary, and @@ -35,3 +38,23 @@ pub fn format_page_range(offset: u64, length: u64) -> Result { let content_range = format!("bytes={}-{}", offset, end_range); Ok(content_range) } + +/// Takes in an offset and a length and returns the HTTP range in string format. +/// +/// # Arguments +/// +/// * `tags` - A hash map containing the name-value pairs associated with the blob as tags. +pub fn serialize_blob_tags(tags: HashMap) -> BlobTags { + let mut blob_tags: Vec = vec![]; + + for (k, v) in tags.into_iter() { + let blob_tag = BlobTag { + key: Some(k), + value: Some(v), + }; + blob_tags.push(blob_tag); + } + BlobTags { + blob_tag_set: Some(blob_tags), + } +} diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index 759a12111a..fc5f011753 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -7,13 +7,17 @@ use azure_core::{ }; use azure_core_test::{recorded, TestContext}; use azure_storage_blob::models::{ - AccessTier, BlobClientAcquireLeaseResultHeaders, BlobClientChangeLeaseResultHeaders, - BlobClientDownloadOptions, BlobClientDownloadResultHeaders, BlobClientGetPropertiesOptions, + AccessTier, AccountKind, BlobClientAcquireLeaseResultHeaders, + BlobClientChangeLeaseResultHeaders, BlobClientDownloadOptions, BlobClientDownloadResultHeaders, + BlobClientGetAccountInfoResultHeaders, BlobClientGetPropertiesOptions, BlobClientGetPropertiesResultHeaders, BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlobClientSetTierOptions, BlobClientStartCopyFromUrlResultHeaders, BlockBlobClientUploadOptions, CopyStatus, LeaseState, }; -use azure_storage_blob_test::{create_test_blob, get_blob_name, get_container_client}; +use azure_storage_blob::serialize_blob_tags; +use azure_storage_blob_test::{ + create_test_blob, get_blob_name, get_container_client, test_blob_tag_equality, +}; use std::{collections::HashMap, error::Error, time::Duration}; use tokio::time; @@ -456,3 +460,56 @@ async fn test_start_copy_from_url(ctx: TestContext) -> Result<(), Box container_client.delete_container(None).await?; Ok(()) } + +#[recorded::test] +async fn test_blob_tags(ctx: TestContext) -> Result<(), Box> { + // Recording Setup + let recording = ctx.recording(); + let container_client = get_container_client(recording, true).await?; + let blob_client = container_client.blob_client(get_blob_name(recording)); + create_test_blob(&blob_client).await?; + + // Set Tags with Tags Specified + let blob_tags = HashMap::from([ + ("hello".to_string(), "world".to_string()), + ("ferris".to_string(), "crab".to_string()), + ]); + blob_client.set_tags(blob_tags.clone(), None).await?; + + // Assert + let response_tags = blob_client.get_tags(None).await?.into_body().await?; + assert!(test_blob_tag_equality( + serialize_blob_tags(blob_tags), + response_tags + )); + + // 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!(response_tags.blob_tag_set.is_none()); + + container_client.delete_container(None).await?; + Ok(()) +} + +#[recorded::test] +async fn test_get_account_info(ctx: TestContext) -> Result<(), Box> { + // Recording Setup + let recording = ctx.recording(); + let container_client = get_container_client(recording, true).await?; + let blob_client = container_client.blob_client(get_blob_name(recording)); + + // Act + let response = blob_client.get_account_info(None).await?; + + // Assert + let sku_name = response.sku_name()?; + let account_kind = response.account_kind()?; + + assert!(sku_name.is_some()); + assert_eq!(AccountKind::StorageV2, account_kind.unwrap()); + + Ok(()) +} diff --git a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs index 65420e18fb..3b9ca10e75 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs @@ -4,7 +4,8 @@ use azure_core::http::StatusCode; use azure_core_test::{recorded, TestContext}; use azure_storage_blob::models::{ - BlobContainerClientAcquireLeaseResultHeaders, BlobContainerClientChangeLeaseResultHeaders, + AccountKind, BlobContainerClientAcquireLeaseResultHeaders, + BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientSetMetadataOptions, BlobType, LeaseState, }; @@ -275,3 +276,22 @@ async fn test_container_lease_operations(ctx: TestContext) -> Result<(), Box Result<(), Box> { + // Recording Setup + let recording = ctx.recording(); + let container_client = get_container_client(recording, true).await?; + + // Act + let response = container_client.get_account_info(None).await?; + + // Assert + let sku_name = response.sku_name()?; + let account_kind = response.account_kind()?; + + assert!(sku_name.is_some()); + assert_eq!(AccountKind::StorageV2, account_kind.unwrap()); + + Ok(()) +} diff --git a/sdk/storage/azure_storage_blob/tests/blob_service_client.rs b/sdk/storage/azure_storage_blob/tests/blob_service_client.rs index 80dc8cb06e..83cc9a454e 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_service_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_service_client.rs @@ -4,6 +4,7 @@ use azure_core::http::RequestContent; use azure_core_test::{recorded, TestContext}; use azure_storage_blob::models::{ + AccountKind, BlobServiceClientGetAccountInfoResultHeaders, BlobServiceClientGetPropertiesOptions, BlobServiceClientListContainersSegmentOptions, StorageServiceProperties, }; @@ -148,3 +149,22 @@ async fn test_set_service_properties(ctx: TestContext) -> Result<(), Box Result<(), Box> { + // Recording Setup + let recording = ctx.recording(); + let service_client = get_blob_service_client(recording)?; + + // Act + let response = service_client.get_account_info(None).await?; + + // Assert + let sku_name = response.sku_name()?; + let account_kind = response.account_kind()?; + + assert!(sku_name.is_some()); + assert_eq!(AccountKind::StorageV2, account_kind.unwrap()); + + Ok(()) +} diff --git a/sdk/storage/azure_storage_blob_test/src/lib.rs b/sdk/storage/azure_storage_blob_test/src/lib.rs index cb6b164896..958d503237 100644 --- a/sdk/storage/azure_storage_blob_test/src/lib.rs +++ b/sdk/storage/azure_storage_blob_test/src/lib.rs @@ -7,9 +7,11 @@ use azure_core::{ }; use azure_core_test::Recording; use azure_storage_blob::{ - models::BlockBlobClientUploadResult, BlobClient, BlobContainerClient, - BlobContainerClientOptions, BlobServiceClient, BlobServiceClientOptions, + models::{BlobTags, BlockBlobClientUploadResult}, + BlobClient, BlobContainerClient, BlobContainerClientOptions, BlobServiceClient, + BlobServiceClientOptions, }; +use std::collections::HashMap; /// Takes in a Recording instance and returns an instrumented options bag and endpoint. /// @@ -112,3 +114,28 @@ pub async fn create_test_blob( ) .await } + +/// Takes in two separate BlobTags instances and compares their contents to check for equality. +/// +/// # Arguments +/// +/// * `tags1` - The first BlobTags to be compared. +/// * `tags2` - The second BlobTags to be compared. +pub fn test_blob_tag_equality(tags1: BlobTags, tags2: BlobTags) -> bool { + let mut count_map = HashMap::new(); + // Iterate through first set of tags, populate HashMap + for blob_tag in tags1.blob_tag_set.unwrap() { + count_map.insert(blob_tag.key.unwrap(), blob_tag.value.unwrap()); + } + // Iterate through second set of tags + for blob_tag in tags2.blob_tag_set.unwrap() { + // If tag is not found, return false + if !count_map.contains_key(&blob_tag.key.clone().unwrap()) { + return false; + } else { + count_map.remove(&blob_tag.key.unwrap()); + } + } + // Ensure HashMap has been completely consumed + count_map.is_empty() +} From 2c40e8666bd0756cbb0a75e8a98326ceb543798d Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 15 Jul 2025 14:34:23 -0700 Subject: [PATCH 03/24] Regenerate against .tsp --- .../src/clients/blob_client.rs | 8 +-- .../src/generated/clients/blob_client.rs | 9 ++-- .../src/generated/models/header_traits.rs | 54 +++---------------- .../src/generated/models/pub_models.rs | 4 -- .../azure_storage_blob/src/models/mod.rs | 38 ++++++------- .../azure_storage_blob/tsp-location.yaml | 2 +- 6 files changed, 36 insertions(+), 79 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index cb38776779..e95e228b8e 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -6,9 +6,9 @@ use crate::{ generated::models::{ BlobClientAcquireLeaseResult, BlobClientBreakLeaseResult, BlobClientChangeLeaseResult, BlobClientDownloadResult, BlobClientGetAccountInfoResult, BlobClientGetPropertiesResult, - BlobClientReleaseLeaseResult, BlobClientRenewLeaseResult, BlobClientSetTagsResult, - BlobClientStartCopyFromUrlResult, BlockBlobClientCommitBlockListResult, - BlockBlobClientStageBlockResult, BlockBlobClientUploadResult, + BlobClientReleaseLeaseResult, BlobClientRenewLeaseResult, BlobClientStartCopyFromUrlResult, + BlockBlobClientCommitBlockListResult, BlockBlobClientStageBlockResult, + BlockBlobClientUploadResult, }, models::{ AccessTier, BlobClientAcquireLeaseOptions, BlobClientBreakLeaseOptions, @@ -351,7 +351,7 @@ impl BlobClient { &self, tags: HashMap, options: Option>, - ) -> Result> { + ) -> Result> { let blob_tags = serialize_blob_tags(tags); self.client .set_tags(RequestContent::try_from(blob_tags)?, options) diff --git a/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs index 9dac2880bf..be6a013b7b 100644 --- a/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs @@ -19,10 +19,9 @@ use crate::generated::{ BlobClientSetExpiryOptions, BlobClientSetExpiryResult, BlobClientSetImmutabilityPolicyOptions, BlobClientSetImmutabilityPolicyResult, BlobClientSetLegalHoldOptions, BlobClientSetLegalHoldResult, BlobClientSetMetadataOptions, - BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, BlobClientSetTagsResult, - BlobClientSetTierOptions, BlobClientStartCopyFromUrlOptions, - BlobClientStartCopyFromUrlResult, BlobClientUndeleteOptions, BlobClientUndeleteResult, - BlobExpiryOptions, BlobTags, + BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, BlobClientSetTierOptions, + BlobClientStartCopyFromUrlOptions, BlobClientStartCopyFromUrlResult, + BlobClientUndeleteOptions, BlobClientUndeleteResult, BlobExpiryOptions, BlobTags, }, }; use azure_core::{ @@ -1251,7 +1250,7 @@ impl BlobClient { &self, tags: RequestContent, options: Option>, - ) -> Result> { + ) -> Result> { let options = options.unwrap_or_default(); let ctx = Context::with_context(&options.method_options.context); let mut url = self.endpoint.clone(); diff --git a/sdk/storage/azure_storage_blob/src/generated/models/header_traits.rs b/sdk/storage/azure_storage_blob/src/generated/models/header_traits.rs index 7a09d50746..abc65d07ea 100644 --- a/sdk/storage/azure_storage_blob/src/generated/models/header_traits.rs +++ b/sdk/storage/azure_storage_blob/src/generated/models/header_traits.rs @@ -11,14 +11,13 @@ use super::{ BlobClientDeleteImmutabilityPolicyResult, BlobClientDownloadResult, BlobClientGetAccountInfoResult, BlobClientGetPropertiesResult, BlobClientReleaseLeaseResult, BlobClientRenewLeaseResult, BlobClientSetExpiryResult, BlobClientSetImmutabilityPolicyResult, - BlobClientSetLegalHoldResult, BlobClientSetTagsResult, BlobClientStartCopyFromUrlResult, - BlobClientUndeleteResult, BlobContainerClientAcquireLeaseResult, - BlobContainerClientBreakLeaseResult, BlobContainerClientChangeLeaseResult, - BlobContainerClientGetAccountInfoResult, BlobContainerClientGetPropertiesResult, - BlobContainerClientReleaseLeaseResult, BlobContainerClientRenameResult, - BlobContainerClientRenewLeaseResult, BlobContainerClientRestoreResult, - BlobContainerClientSetAccessPolicyResult, BlobImmutabilityPolicyMode, - BlobServiceClientGetAccountInfoResult, BlobTags, BlobType, + BlobClientSetLegalHoldResult, BlobClientStartCopyFromUrlResult, BlobClientUndeleteResult, + BlobContainerClientAcquireLeaseResult, BlobContainerClientBreakLeaseResult, + BlobContainerClientChangeLeaseResult, BlobContainerClientGetAccountInfoResult, + BlobContainerClientGetPropertiesResult, BlobContainerClientReleaseLeaseResult, + BlobContainerClientRenameResult, BlobContainerClientRenewLeaseResult, + BlobContainerClientRestoreResult, BlobContainerClientSetAccessPolicyResult, + BlobImmutabilityPolicyMode, BlobServiceClientGetAccountInfoResult, BlobTags, BlobType, BlockBlobClientCommitBlockListResult, BlockBlobClientPutBlobFromUrlResult, BlockBlobClientQueryResult, BlockBlobClientStageBlockFromUrlResult, BlockBlobClientStageBlockResult, BlockBlobClientUploadResult, BlockList, CopyStatus, @@ -835,18 +834,12 @@ impl BlobClientDownloadResultHeaders for Response Result>; fn account_kind(&self) -> Result>; fn is_hierarchical_namespace_enabled(&self) -> Result>; fn sku_name(&self) -> Result>; } impl BlobClientGetAccountInfoResultHeaders for Response { - /// UTC date/time value generated by the service that indicates the time at which the response was initiated - fn date(&self) -> Result> { - Headers::get_optional_with(self.headers(), &DATE, |h| parse_rfc7231(h.as_str())) - } - /// Identifies the account kind fn account_kind(&self) -> Result> { Headers::get_optional_as(self.headers(), &ACCOUNT_KIND) @@ -1302,21 +1295,8 @@ impl BlobClientSetLegalHoldResultHeaders for Response Result>; -} - -impl BlobClientSetTagsResultHeaders for Response { - /// UTC date/time value generated by the service that indicates the time at which the response was initiated - fn date(&self) -> Result> { - Headers::get_optional_with(self.headers(), &DATE, |h| parse_rfc7231(h.as_str())) - } -} - /// Provides access to typed response headers for `BlobClient::start_copy_from_url()` pub trait BlobClientStartCopyFromUrlResultHeaders: private::Sealed { - fn date(&self) -> Result>; fn last_modified(&self) -> Result>; fn etag(&self) -> Result>; fn copy_id(&self) -> Result>; @@ -1327,11 +1307,6 @@ pub trait BlobClientStartCopyFromUrlResultHeaders: private::Sealed { impl BlobClientStartCopyFromUrlResultHeaders for Response { - /// UTC date/time value generated by the service that indicates the time at which the response was initiated - fn date(&self) -> Result> { - Headers::get_optional_with(self.headers(), &DATE, |h| parse_rfc7231(h.as_str())) - } - /// The date/time that the container was last modified. fn last_modified(&self) -> Result> { Headers::get_optional_with(self.headers(), &LAST_MODIFIED, |h| { @@ -1460,7 +1435,6 @@ impl BlobContainerClientChangeLeaseResultHeaders /// Provides access to typed response headers for `BlobContainerClient::get_account_info()` pub trait BlobContainerClientGetAccountInfoResultHeaders: private::Sealed { - fn date(&self) -> Result>; fn account_kind(&self) -> Result>; fn is_hierarchical_namespace_enabled(&self) -> Result>; fn sku_name(&self) -> Result>; @@ -1469,11 +1443,6 @@ pub trait BlobContainerClientGetAccountInfoResultHeaders: private::Sealed { impl BlobContainerClientGetAccountInfoResultHeaders for Response { - /// UTC date/time value generated by the service that indicates the time at which the response was initiated - fn date(&self) -> Result> { - Headers::get_optional_with(self.headers(), &DATE, |h| parse_rfc7231(h.as_str())) - } - /// Identifies the account kind fn account_kind(&self) -> Result> { Headers::get_optional_as(self.headers(), &ACCOUNT_KIND) @@ -1689,7 +1658,6 @@ impl BlobContainerClientSetAccessPolicyResultHeaders /// Provides access to typed response headers for `BlobServiceClient::get_account_info()` pub trait BlobServiceClientGetAccountInfoResultHeaders: private::Sealed { - fn date(&self) -> Result>; fn account_kind(&self) -> Result>; fn is_hierarchical_namespace_enabled(&self) -> Result>; fn sku_name(&self) -> Result>; @@ -1698,11 +1666,6 @@ pub trait BlobServiceClientGetAccountInfoResultHeaders: private::Sealed { impl BlobServiceClientGetAccountInfoResultHeaders for Response { - /// UTC date/time value generated by the service that indicates the time at which the response was initiated - fn date(&self) -> Result> { - Headers::get_optional_with(self.headers(), &DATE, |h| parse_rfc7231(h.as_str())) - } - /// Identifies the account kind fn account_kind(&self) -> Result> { Headers::get_optional_as(self.headers(), &ACCOUNT_KIND) @@ -2716,7 +2679,7 @@ mod private { BlobClientGetAccountInfoResult, BlobClientGetPropertiesResult, BlobClientReleaseLeaseResult, BlobClientRenewLeaseResult, BlobClientSetExpiryResult, BlobClientSetImmutabilityPolicyResult, BlobClientSetLegalHoldResult, - BlobClientSetTagsResult, BlobClientStartCopyFromUrlResult, BlobClientUndeleteResult, + BlobClientStartCopyFromUrlResult, BlobClientUndeleteResult, BlobContainerClientAcquireLeaseResult, BlobContainerClientBreakLeaseResult, BlobContainerClientChangeLeaseResult, BlobContainerClientGetAccountInfoResult, BlobContainerClientGetPropertiesResult, BlobContainerClientReleaseLeaseResult, @@ -2755,7 +2718,6 @@ mod private { impl Sealed for Response {} impl Sealed for Response {} impl Sealed for Response {} - impl Sealed for Response {} impl Sealed for Response {} impl Sealed for Response {} impl Sealed for Response {} diff --git a/sdk/storage/azure_storage_blob/src/generated/models/pub_models.rs b/sdk/storage/azure_storage_blob/src/generated/models/pub_models.rs index c53356b7a9..f57fb555c0 100644 --- a/sdk/storage/azure_storage_blob/src/generated/models/pub_models.rs +++ b/sdk/storage/azure_storage_blob/src/generated/models/pub_models.rs @@ -159,10 +159,6 @@ pub struct BlobClientSetImmutabilityPolicyResult; #[derive(SafeDebug)] pub struct BlobClientSetLegalHoldResult; -/// Contains results for `BlobClient::set_tags()` -#[derive(SafeDebug)] -pub struct BlobClientSetTagsResult; - /// Contains results for `BlobClient::start_copy_from_url()` #[derive(SafeDebug)] pub struct BlobClientStartCopyFromUrlResult; diff --git a/sdk/storage/azure_storage_blob/src/models/mod.rs b/sdk/storage/azure_storage_blob/src/models/mod.rs index 5f9c3a3b68..13ce3b9f13 100644 --- a/sdk/storage/azure_storage_blob/src/models/mod.rs +++ b/sdk/storage/azure_storage_blob/src/models/mod.rs @@ -20,25 +20,25 @@ pub use crate::generated::models::{ BlobClientGetPropertiesResultHeaders, BlobClientGetTagsOptions, BlobClientReleaseLeaseOptions, BlobClientReleaseLeaseResult, BlobClientReleaseLeaseResultHeaders, BlobClientRenewLeaseOptions, BlobClientRenewLeaseResult, BlobClientRenewLeaseResultHeaders, BlobClientSetMetadataOptions, - BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, BlobClientSetTagsResult, - BlobClientSetTagsResultHeaders, BlobClientSetTierOptions, BlobClientStartCopyFromUrlOptions, - BlobClientStartCopyFromUrlResult, BlobClientStartCopyFromUrlResultHeaders, - BlobContainerClientAcquireLeaseOptions, BlobContainerClientAcquireLeaseResult, - BlobContainerClientAcquireLeaseResultHeaders, BlobContainerClientBreakLeaseOptions, - BlobContainerClientBreakLeaseResult, BlobContainerClientBreakLeaseResultHeaders, - BlobContainerClientChangeLeaseOptions, BlobContainerClientChangeLeaseResult, - BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientCreateOptions, - BlobContainerClientDeleteOptions, BlobContainerClientGetAccountInfoOptions, - BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesOptions, - BlobContainerClientGetPropertiesResult, BlobContainerClientGetPropertiesResultHeaders, - BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions, - BlobContainerClientReleaseLeaseResult, BlobContainerClientReleaseLeaseResultHeaders, - BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, - BlobContainerClientSetMetadataOptions, BlobImmutabilityPolicyMode, - BlobServiceClientGetAccountInfoOptions, BlobServiceClientGetAccountInfoResult, - BlobServiceClientGetAccountInfoResultHeaders, BlobServiceClientGetPropertiesOptions, - BlobServiceClientListContainersSegmentOptions, BlobServiceClientSetPropertiesOptions, BlobTags, - BlobType, BlockBlobClientCommitBlockListOptions, BlockBlobClientCommitBlockListResult, + BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, BlobClientSetTierOptions, + BlobClientStartCopyFromUrlOptions, BlobClientStartCopyFromUrlResult, + BlobClientStartCopyFromUrlResultHeaders, BlobContainerClientAcquireLeaseOptions, + BlobContainerClientAcquireLeaseResult, BlobContainerClientAcquireLeaseResultHeaders, + BlobContainerClientBreakLeaseOptions, BlobContainerClientBreakLeaseResult, + BlobContainerClientBreakLeaseResultHeaders, BlobContainerClientChangeLeaseOptions, + BlobContainerClientChangeLeaseResult, BlobContainerClientChangeLeaseResultHeaders, + BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions, + BlobContainerClientGetAccountInfoOptions, BlobContainerClientGetAccountInfoResultHeaders, + BlobContainerClientGetPropertiesOptions, BlobContainerClientGetPropertiesResult, + BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, + BlobContainerClientReleaseLeaseOptions, BlobContainerClientReleaseLeaseResult, + BlobContainerClientReleaseLeaseResultHeaders, BlobContainerClientRenewLeaseOptions, + BlobContainerClientRenewLeaseResult, BlobContainerClientSetMetadataOptions, + BlobImmutabilityPolicyMode, BlobServiceClientGetAccountInfoOptions, + BlobServiceClientGetAccountInfoResult, BlobServiceClientGetAccountInfoResultHeaders, + BlobServiceClientGetPropertiesOptions, BlobServiceClientListContainersSegmentOptions, + BlobServiceClientSetPropertiesOptions, BlobTags, BlobType, + BlockBlobClientCommitBlockListOptions, BlockBlobClientCommitBlockListResult, BlockBlobClientCommitBlockListResultHeaders, BlockBlobClientGetBlockListOptions, BlockBlobClientStageBlockOptions, BlockBlobClientStageBlockResult, BlockBlobClientStageBlockResultHeaders, BlockBlobClientUploadOptions, diff --git a/sdk/storage/azure_storage_blob/tsp-location.yaml b/sdk/storage/azure_storage_blob/tsp-location.yaml index 73d9db1736..33d5d00cc7 100644 --- a/sdk/storage/azure_storage_blob/tsp-location.yaml +++ b/sdk/storage/azure_storage_blob/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/storage/Microsoft.BlobStorage -commit: ac2d7f6cf9e37d6d4d35bd917bd2a06c31f9c1c4 +commit: a4d4ef47d3cffcaf30aa2449c251d62dc4c62b59 repo: Azure/azure-rest-api-specs additionalDirectories: From d1332838b85c7dcee8209db1272f82423fe83e61 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 15 Jul 2025 14:39:22 -0700 Subject: [PATCH 04/24] Test recordings --- sdk/storage/assets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/assets.json b/sdk/storage/assets.json index 20cba4eaa9..69e79923d5 100644 --- a/sdk/storage/assets.json +++ b/sdk/storage/assets.json @@ -1,6 +1,6 @@ { "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "rust", - "Tag": "rust/azure_storage_blob_f12afa9550", + "Tag": "rust/azure_storage_blob_37bd13b584", "TagPrefix": "rust/azure_storage_blob" } \ No newline at end of file From c47b704172e9b1156f6bdbd33882a3cef9d595b1 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 15 Jul 2025 14:54:29 -0700 Subject: [PATCH 05/24] Resolve rustdoc error bare-urls --- sdk/storage/azure_storage_blob/src/clients/blob_client.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index e95e228b8e..697241638c 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -326,7 +326,6 @@ impl BlobClient { /// If the source is in another account, the source must either be public /// or must be authenticated via a shared access signature. If the source /// is public, no authentication is required. - /// Example: https://myaccount.blob.core.windows.net/mycontainer/myblob /// * `options` - Optional configuration for the request. pub async fn start_copy_from_url( &self, From 4fc6dba4e4d675f1b85ca3f3f0571e8e12c2739f Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 15 Jul 2025 15:03:08 -0700 Subject: [PATCH 06/24] Recordings update --- sdk/storage/assets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/assets.json b/sdk/storage/assets.json index 69e79923d5..e6c60f7d02 100644 --- a/sdk/storage/assets.json +++ b/sdk/storage/assets.json @@ -1,6 +1,6 @@ { "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "rust", - "Tag": "rust/azure_storage_blob_37bd13b584", + "Tag": "rust/azure_storage_blob_c561afee00", "TagPrefix": "rust/azure_storage_blob" } \ No newline at end of file From 6e36e3a99fecfd0670cc1ebfb8e2c61991961516 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Thu, 17 Jul 2025 17:20:09 -0700 Subject: [PATCH 07/24] nit --- sdk/storage/azure_storage_blob/src/parsers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index be6de8576b..4d05fcf447 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -39,13 +39,13 @@ pub fn format_page_range(offset: u64, length: u64) -> Result { Ok(content_range) } -/// Takes in an offset and a length and returns the HTTP range in string format. +/// 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 fn serialize_blob_tags(tags: HashMap) -> BlobTags { - let mut blob_tags: Vec = vec![]; + let mut blob_tags = vec![]; for (k, v) in tags.into_iter() { let blob_tag = BlobTag { From b8a375b82a9965b40a9673fe4124304a65d41cdf Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 21 Jul 2025 12:35:25 -0700 Subject: [PATCH 08/24] Regen after omitting start_copy_from_url --- .../src/generated/models/header_traits.rs | 6 ++++++ sdk/storage/azure_storage_blob/tsp-location.yaml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/sdk/storage/azure_storage_blob/src/generated/models/header_traits.rs b/sdk/storage/azure_storage_blob/src/generated/models/header_traits.rs index abc65d07ea..77555c4b4b 100644 --- a/sdk/storage/azure_storage_blob/src/generated/models/header_traits.rs +++ b/sdk/storage/azure_storage_blob/src/generated/models/header_traits.rs @@ -1297,6 +1297,7 @@ impl BlobClientSetLegalHoldResultHeaders for Response Result>; fn last_modified(&self) -> Result>; fn etag(&self) -> Result>; fn copy_id(&self) -> Result>; @@ -1307,6 +1308,11 @@ pub trait BlobClientStartCopyFromUrlResultHeaders: private::Sealed { impl BlobClientStartCopyFromUrlResultHeaders for Response { + /// UTC date/time value generated by the service that indicates the time at which the response was initiated + fn date(&self) -> Result> { + Headers::get_optional_with(self.headers(), &DATE, |h| parse_rfc7231(h.as_str())) + } + /// The date/time that the container was last modified. fn last_modified(&self) -> Result> { Headers::get_optional_with(self.headers(), &LAST_MODIFIED, |h| { diff --git a/sdk/storage/azure_storage_blob/tsp-location.yaml b/sdk/storage/azure_storage_blob/tsp-location.yaml index 33d5d00cc7..5df3685349 100644 --- a/sdk/storage/azure_storage_blob/tsp-location.yaml +++ b/sdk/storage/azure_storage_blob/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/storage/Microsoft.BlobStorage -commit: a4d4ef47d3cffcaf30aa2449c251d62dc4c62b59 +commit: 7b55a1dba199a8f9cb269d9cc76b9879d376aa37 repo: Azure/azure-rest-api-specs additionalDirectories: From 2633e4dcf2551ad11aaf63d946cf8c5d15b6a4ec Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 21 Jul 2025 13:48:09 -0700 Subject: [PATCH 09/24] Refactor that any cx facing is HashMap --- sdk/storage/assets.json | 2 +- .../src/clients/blob_client.rs | 30 +++-------- .../azure_storage_blob/src/models/mod.rs | 34 ++++++------- sdk/storage/azure_storage_blob/src/parsers.rs | 31 +++++++++++- .../azure_storage_blob/tests/blob_client.rs | 50 +++---------------- .../azure_storage_blob_test/src/lib.rs | 25 ---------- 6 files changed, 59 insertions(+), 113 deletions(-) diff --git a/sdk/storage/assets.json b/sdk/storage/assets.json index e6c60f7d02..e583470309 100644 --- a/sdk/storage/assets.json +++ b/sdk/storage/assets.json @@ -1,6 +1,6 @@ { "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "rust", - "Tag": "rust/azure_storage_blob_c561afee00", + "Tag": "rust/azure_storage_blob_a2540ce0f0", "TagPrefix": "rust/azure_storage_blob" } \ No newline at end of file diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index 697241638c..0e322f8ca7 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -2,11 +2,12 @@ // Licensed under the MIT License. use crate::{ + deserialize_blob_tags, generated::clients::BlobClient as GeneratedBlobClient, generated::models::{ BlobClientAcquireLeaseResult, BlobClientBreakLeaseResult, BlobClientChangeLeaseResult, BlobClientDownloadResult, BlobClientGetAccountInfoResult, BlobClientGetPropertiesResult, - BlobClientReleaseLeaseResult, BlobClientRenewLeaseResult, BlobClientStartCopyFromUrlResult, + BlobClientReleaseLeaseResult, BlobClientRenewLeaseResult, BlockBlobClientCommitBlockListResult, BlockBlobClientStageBlockResult, BlockBlobClientUploadResult, }, @@ -16,8 +17,8 @@ use crate::{ BlobClientGetAccountInfoOptions, BlobClientGetPropertiesOptions, BlobClientGetTagsOptions, BlobClientReleaseLeaseOptions, BlobClientRenewLeaseOptions, BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, BlobClientSetTierOptions, - BlobClientStartCopyFromUrlOptions, BlobTags, BlockBlobClientCommitBlockListOptions, - BlockBlobClientUploadOptions, BlockList, BlockListType, BlockLookupList, + BlobTags, BlockBlobClientCommitBlockListOptions, BlockBlobClientUploadOptions, BlockList, + BlockListType, BlockLookupList, }, pipeline::StorageHeadersPolicy, serialize_blob_tags, AppendBlobClient, BlobClientOptions, BlockBlobClient, PageBlobClient, @@ -317,24 +318,6 @@ impl BlobClient { self.client.renew_lease(lease_id, options).await } - /// Copies a blob or an internet resource to a new blob. - /// - /// # Arguments - /// - /// * `copy_source` - A URL of up to 2 KB in length that specifies a file or blob. - /// The value should be URL-encoded as it would appear in a request URI. - /// If the source is in another account, the source must either be public - /// or must be authenticated via a shared access signature. If the source - /// is public, no authentication is required. - /// * `options` - Optional configuration for the request. - pub async fn start_copy_from_url( - &self, - copy_source: String, - options: Option>, - ) -> Result> { - self.client.start_copy_from_url(copy_source, options).await - } - /// Sets tags on a blob. Note that each call to this operation replaces all existing tags. To remove /// all tags from the blob, call this operation with no tags specified. /// @@ -365,8 +348,9 @@ impl BlobClient { pub async fn get_tags( &self, options: Option>, - ) -> Result> { - self.client.get_tags(options).await + ) -> Result, XmlFormat>> { + let response = self.client.get_tags(options).await?; + deserialize_blob_tags(response).await } /// Gets information related to the Storage account in which the blob resides. diff --git a/sdk/storage/azure_storage_blob/src/models/mod.rs b/sdk/storage/azure_storage_blob/src/models/mod.rs index 13ce3b9f13..8abc456f10 100644 --- a/sdk/storage/azure_storage_blob/src/models/mod.rs +++ b/sdk/storage/azure_storage_blob/src/models/mod.rs @@ -21,24 +21,22 @@ pub use crate::generated::models::{ BlobClientReleaseLeaseResult, BlobClientReleaseLeaseResultHeaders, BlobClientRenewLeaseOptions, BlobClientRenewLeaseResult, BlobClientRenewLeaseResultHeaders, BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, BlobClientSetTierOptions, - BlobClientStartCopyFromUrlOptions, BlobClientStartCopyFromUrlResult, - BlobClientStartCopyFromUrlResultHeaders, BlobContainerClientAcquireLeaseOptions, - BlobContainerClientAcquireLeaseResult, BlobContainerClientAcquireLeaseResultHeaders, - BlobContainerClientBreakLeaseOptions, BlobContainerClientBreakLeaseResult, - BlobContainerClientBreakLeaseResultHeaders, BlobContainerClientChangeLeaseOptions, - BlobContainerClientChangeLeaseResult, BlobContainerClientChangeLeaseResultHeaders, - BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions, - BlobContainerClientGetAccountInfoOptions, BlobContainerClientGetAccountInfoResultHeaders, - BlobContainerClientGetPropertiesOptions, BlobContainerClientGetPropertiesResult, - BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, - BlobContainerClientReleaseLeaseOptions, BlobContainerClientReleaseLeaseResult, - BlobContainerClientReleaseLeaseResultHeaders, BlobContainerClientRenewLeaseOptions, - BlobContainerClientRenewLeaseResult, BlobContainerClientSetMetadataOptions, - BlobImmutabilityPolicyMode, BlobServiceClientGetAccountInfoOptions, - BlobServiceClientGetAccountInfoResult, BlobServiceClientGetAccountInfoResultHeaders, - BlobServiceClientGetPropertiesOptions, BlobServiceClientListContainersSegmentOptions, - BlobServiceClientSetPropertiesOptions, BlobTags, BlobType, - BlockBlobClientCommitBlockListOptions, BlockBlobClientCommitBlockListResult, + BlobContainerClientAcquireLeaseOptions, BlobContainerClientAcquireLeaseResult, + BlobContainerClientAcquireLeaseResultHeaders, BlobContainerClientBreakLeaseOptions, + BlobContainerClientBreakLeaseResult, BlobContainerClientBreakLeaseResultHeaders, + BlobContainerClientChangeLeaseOptions, BlobContainerClientChangeLeaseResult, + BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientCreateOptions, + BlobContainerClientDeleteOptions, BlobContainerClientGetAccountInfoOptions, + BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesOptions, + BlobContainerClientGetPropertiesResult, BlobContainerClientGetPropertiesResultHeaders, + BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions, + BlobContainerClientReleaseLeaseResult, BlobContainerClientReleaseLeaseResultHeaders, + BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, + BlobContainerClientSetMetadataOptions, BlobImmutabilityPolicyMode, + BlobServiceClientGetAccountInfoOptions, BlobServiceClientGetAccountInfoResult, + BlobServiceClientGetAccountInfoResultHeaders, BlobServiceClientGetPropertiesOptions, + BlobServiceClientListContainersSegmentOptions, BlobServiceClientSetPropertiesOptions, BlobTags, + BlobType, BlockBlobClientCommitBlockListOptions, BlockBlobClientCommitBlockListResult, BlockBlobClientCommitBlockListResultHeaders, BlockBlobClientGetBlockListOptions, BlockBlobClientStageBlockOptions, BlockBlobClientStageBlockResult, BlockBlobClientStageBlockResultHeaders, BlockBlobClientUploadOptions, diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index 4d05fcf447..dad66991c7 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -2,7 +2,8 @@ // Licensed under the MIT License. use crate::generated::models::{BlobTag, BlobTags}; -use azure_core::http::RequestContent; +use azure_core::http::{RawResponse, RequestContent, Response, XmlFormat}; +use azure_core::xml; use std::collections::HashMap; use std::io::{Error, ErrorKind}; @@ -44,7 +45,7 @@ pub fn format_page_range(offset: u64, length: u64) -> Result { /// # Arguments /// /// * `tags` - A hash map containing the name-value pairs associated with the blob as tags. -pub fn serialize_blob_tags(tags: HashMap) -> BlobTags { +pub(crate) fn serialize_blob_tags(tags: HashMap) -> BlobTags { let mut blob_tags = vec![]; for (k, v) in tags.into_iter() { @@ -58,3 +59,29 @@ pub fn serialize_blob_tags(tags: HashMap) -> 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, +) -> azure_core::Result, XmlFormat>> +where +{ + let mut blob_tags_map = 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 blob_tag in blob_tag_set { + blob_tags_map.insert(blob_tag.key, blob_tag.value); + } + } + + let xml_body = xml::to_xml_with_root("HashMap", &blob_tags_map)?; + let raw_response = RawResponse::from_bytes(status, headers, xml_body); + Ok(raw_response.into()) +} diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index fc5f011753..f850784dea 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -11,13 +11,10 @@ use azure_storage_blob::models::{ BlobClientChangeLeaseResultHeaders, BlobClientDownloadOptions, BlobClientDownloadResultHeaders, BlobClientGetAccountInfoResultHeaders, BlobClientGetPropertiesOptions, BlobClientGetPropertiesResultHeaders, BlobClientSetMetadataOptions, - BlobClientSetPropertiesOptions, BlobClientSetTierOptions, - BlobClientStartCopyFromUrlResultHeaders, BlockBlobClientUploadOptions, CopyStatus, LeaseState, -}; -use azure_storage_blob::serialize_blob_tags; -use azure_storage_blob_test::{ - create_test_blob, get_blob_name, get_container_client, test_blob_tag_equality, + BlobClientSetPropertiesOptions, BlobClientSetTierOptions, BlockBlobClientUploadOptions, + LeaseState, }; +use azure_storage_blob_test::{create_test_blob, get_blob_name, get_container_client}; use std::{collections::HashMap, error::Error, time::Duration}; use tokio::time; @@ -428,39 +425,6 @@ async fn test_leased_blob_operations(ctx: TestContext) -> Result<(), Box Result<(), Box> { - // Recording Setup - let recording = ctx.recording(); - let container_client = get_container_client(recording, true).await?; - let source_blob_client = container_client.blob_client(get_blob_name(recording)); - create_test_blob(&source_blob_client).await?; - - let blob_client = container_client.blob_client("destination_blob".to_string()); - let source_url = format!( - "{}{}/{}", - source_blob_client.endpoint().as_str(), - source_blob_client.container_name(), - source_blob_client.blob_name() - ); - let response = blob_client.start_copy_from_url(source_url, None).await?; - let (_, _, source_content) = source_blob_client.download(None).await?.deconstruct(); - let (_, _, copied_content) = blob_client.download(None).await?.deconstruct(); - - // Assert - let copy_status = response.copy_status()?; - let copy_id = response.copy_id()?; - assert_eq!(CopyStatus::Success, copy_status.unwrap()); - assert!(copy_id.is_some()); - assert_eq!( - source_content.collect().await?, - copied_content.collect().await? - ); - - container_client.delete_container(None).await?; - Ok(()) -} - #[recorded::test] async fn test_blob_tags(ctx: TestContext) -> Result<(), Box> { // Recording Setup @@ -478,17 +442,15 @@ async fn test_blob_tags(ctx: TestContext) -> Result<(), Box> { // Assert let response_tags = blob_client.get_tags(None).await?.into_body().await?; - assert!(test_blob_tag_equality( - serialize_blob_tags(blob_tags), - response_tags - )); + println!("response:{:?}", response_tags.clone()); + assert_eq!(blob_tags, response_tags); // 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!(response_tags.blob_tag_set.is_none()); + assert_eq!(HashMap::new(), response_tags); container_client.delete_container(None).await?; Ok(()) diff --git a/sdk/storage/azure_storage_blob_test/src/lib.rs b/sdk/storage/azure_storage_blob_test/src/lib.rs index 958d503237..e94d242327 100644 --- a/sdk/storage/azure_storage_blob_test/src/lib.rs +++ b/sdk/storage/azure_storage_blob_test/src/lib.rs @@ -114,28 +114,3 @@ pub async fn create_test_blob( ) .await } - -/// Takes in two separate BlobTags instances and compares their contents to check for equality. -/// -/// # Arguments -/// -/// * `tags1` - The first BlobTags to be compared. -/// * `tags2` - The second BlobTags to be compared. -pub fn test_blob_tag_equality(tags1: BlobTags, tags2: BlobTags) -> bool { - let mut count_map = HashMap::new(); - // Iterate through first set of tags, populate HashMap - for blob_tag in tags1.blob_tag_set.unwrap() { - count_map.insert(blob_tag.key.unwrap(), blob_tag.value.unwrap()); - } - // Iterate through second set of tags - for blob_tag in tags2.blob_tag_set.unwrap() { - // If tag is not found, return false - if !count_map.contains_key(&blob_tag.key.clone().unwrap()) { - return false; - } else { - count_map.remove(&blob_tag.key.unwrap()); - } - } - // Ensure HashMap has been completely consumed - count_map.is_empty() -} From 3c3d7165bbf81ca3fb0e0d90fa4fcd400585cb8a Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 21 Jul 2025 13:54:22 -0700 Subject: [PATCH 10/24] nit --- sdk/storage/azure_storage_blob_test/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk/storage/azure_storage_blob_test/src/lib.rs b/sdk/storage/azure_storage_blob_test/src/lib.rs index e94d242327..cb6b164896 100644 --- a/sdk/storage/azure_storage_blob_test/src/lib.rs +++ b/sdk/storage/azure_storage_blob_test/src/lib.rs @@ -7,11 +7,9 @@ use azure_core::{ }; use azure_core_test::Recording; use azure_storage_blob::{ - models::{BlobTags, BlockBlobClientUploadResult}, - BlobClient, BlobContainerClient, BlobContainerClientOptions, BlobServiceClient, - BlobServiceClientOptions, + models::BlockBlobClientUploadResult, BlobClient, BlobContainerClient, + BlobContainerClientOptions, BlobServiceClient, BlobServiceClientOptions, }; -use std::collections::HashMap; /// Takes in a Recording instance and returns an instrumented options bag and endpoint. /// From b7ca0f6ac66f27325b48c129721cf43a452f3374 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 22 Jul 2025 17:16:35 -0700 Subject: [PATCH 11/24] Moved asset file --- sdk/storage/azure_storage_blob/assets.json | 6 ++++++ .../azure_storage_blob/src/clients/append_blob_client.rs | 6 +++--- sdk/storage/azure_storage_blob/src/clients/blob_client.rs | 6 +++--- .../src/clients/blob_container_client.rs | 8 ++------ .../azure_storage_blob/src/clients/blob_service_client.rs | 2 +- .../azure_storage_blob/src/clients/block_blob_client.rs | 6 +++--- .../azure_storage_blob/src/clients/page_blob_client.rs | 6 +++--- sdk/storage/azure_storage_blob/tests/blob_client.rs | 1 - 8 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 sdk/storage/azure_storage_blob/assets.json diff --git a/sdk/storage/azure_storage_blob/assets.json b/sdk/storage/azure_storage_blob/assets.json new file mode 100644 index 0000000000..3a4403d497 --- /dev/null +++ b/sdk/storage/azure_storage_blob/assets.json @@ -0,0 +1,6 @@ +{ + "AssetsRepo": "Azure/azure-sdk-assets", + "AssetsRepoPrefixPath": "rust", + "Tag": "rust/azure_storage_blob_9f78f8429b", + "TagPrefix": "rust/azure_storage_blob" +} \ No newline at end of file diff --git a/sdk/storage/azure_storage_blob/src/clients/append_blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/append_blob_client.rs index dd5e6713bb..09a5942626 100644 --- a/sdk/storage/azure_storage_blob/src/clients/append_blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/append_blob_client.rs @@ -64,9 +64,9 @@ impl AppendBlobClient { let client = GeneratedAppendBlobClient::new( endpoint, - credential.clone(), - container_name.clone(), - blob_name.clone(), + credential, + container_name, + blob_name, Some(options), )?; Ok(Self { diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index 0e322f8ca7..a199fff25d 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -76,9 +76,9 @@ impl BlobClient { let client = GeneratedBlobClient::new( endpoint, - credential.clone(), - container_name.clone(), - blob_name.clone(), + credential, + container_name, + blob_name, Some(options), )?; Ok(Self { diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs index 1b397e2ca3..2ffe308a4c 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs @@ -68,12 +68,8 @@ impl BlobContainerClient { .per_try_policies .push(Arc::new(oauth_token_policy) as Arc); - let client = GeneratedBlobContainerClient::new( - endpoint, - credential.clone(), - container_name.clone(), - Some(options), - )?; + let client = + GeneratedBlobContainerClient::new(endpoint, credential, container_name, Some(options))?; Ok(Self { endpoint: endpoint.parse()?, diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs index 1b049f7265..39068c263b 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs @@ -58,7 +58,7 @@ impl BlobServiceClient { .per_try_policies .push(Arc::new(oauth_token_policy) as Arc); - let client = GeneratedBlobServiceClient::new(endpoint, credential.clone(), Some(options))?; + let client = GeneratedBlobServiceClient::new(endpoint, credential, Some(options))?; Ok(Self { endpoint: endpoint.parse()?, diff --git a/sdk/storage/azure_storage_blob/src/clients/block_blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/block_blob_client.rs index fcf32f0398..030565ba49 100644 --- a/sdk/storage/azure_storage_blob/src/clients/block_blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/block_blob_client.rs @@ -70,9 +70,9 @@ impl BlockBlobClient { let client = GeneratedBlockBlobClient::new( endpoint, - credential.clone(), - container_name.clone(), - blob_name.clone(), + credential, + container_name, + blob_name, Some(options), )?; Ok(Self { diff --git a/sdk/storage/azure_storage_blob/src/clients/page_blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/page_blob_client.rs index 243d229368..7a0fd64733 100644 --- a/sdk/storage/azure_storage_blob/src/clients/page_blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/page_blob_client.rs @@ -64,9 +64,9 @@ impl PageBlobClient { let client = GeneratedPageBlobClient::new( endpoint, - credential.clone(), - container_name.clone(), - blob_name.clone(), + credential, + container_name, + blob_name, Some(options), )?; Ok(Self { diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index f850784dea..b47406c596 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -442,7 +442,6 @@ async fn test_blob_tags(ctx: TestContext) -> Result<(), Box> { // Assert let response_tags = blob_client.get_tags(None).await?.into_body().await?; - println!("response:{:?}", response_tags.clone()); assert_eq!(blob_tags, response_tags); // Set Tags with No Tags (Clear Tags) From e67a13ceba37b8951e2f4f82cde053901c9503b6 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 22 Jul 2025 17:17:39 -0700 Subject: [PATCH 12/24] nit --- sdk/storage/assets.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 sdk/storage/assets.json diff --git a/sdk/storage/assets.json b/sdk/storage/assets.json deleted file mode 100644 index e583470309..0000000000 --- a/sdk/storage/assets.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "AssetsRepo": "Azure/azure-sdk-assets", - "AssetsRepoPrefixPath": "rust", - "Tag": "rust/azure_storage_blob_a2540ce0f0", - "TagPrefix": "rust/azure_storage_blob" -} \ No newline at end of file From 1c1a16646e49728822704d416f66846b3d2f1fa1 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Wed, 30 Jul 2025 14:39:31 -0700 Subject: [PATCH 13/24] Use BTree for fixed-ordering, re-record --- sdk/storage/azure_storage_blob/assets.json | 2 +- sdk/storage/azure_storage_blob/src/parsers.rs | 15 +++++----- .../azure_storage_blob/tests/blob_client.rs | 30 +++++++++++++++++++ 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/sdk/storage/azure_storage_blob/assets.json b/sdk/storage/azure_storage_blob/assets.json index 3a4403d497..4df31bcbeb 100644 --- a/sdk/storage/azure_storage_blob/assets.json +++ b/sdk/storage/azure_storage_blob/assets.json @@ -1,6 +1,6 @@ { "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "rust", - "Tag": "rust/azure_storage_blob_9f78f8429b", + "Tag": "rust/azure_storage_blob_64e723a779", "TagPrefix": "rust/azure_storage_blob" } \ No newline at end of file diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index dad66991c7..b025de5e88 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -4,7 +4,7 @@ use crate::generated::models::{BlobTag, BlobTags}; use azure_core::http::{RawResponse, RequestContent, Response, XmlFormat}; use azure_core::xml; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::io::{Error, ErrorKind}; /// Takes in an offset and a length, verifies alignment to a 512-byte boundary, and @@ -46,15 +46,14 @@ pub fn format_page_range(offset: u64, length: u64) -> Result { /// /// * `tags` - A hash map containing the name-value pairs associated with the blob as tags. pub(crate) fn serialize_blob_tags(tags: HashMap) -> BlobTags { - let mut blob_tags = vec![]; - - for (k, v) in tags.into_iter() { - let blob_tag = BlobTag { + 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), - }; - blob_tags.push(blob_tag); - } + }) + .collect(); BlobTags { blob_tag_set: Some(blob_tags), } diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index b47406c596..b3e04dc801 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -455,6 +455,36 @@ async fn test_blob_tags(ctx: TestContext) -> Result<(), Box> { Ok(()) } +#[recorded::test] +async fn test_blob_tags2(ctx: TestContext) -> Result<(), Box> { + // Recording Setup + let recording = ctx.recording(); + let container_client = get_container_client(recording, true).await?; + let blob_client = container_client.blob_client(get_blob_name(recording)); + create_test_blob(&blob_client).await?; + + // Set Tags with Tags Specified + let blob_tags = HashMap::from([ + ("hello".to_string(), "world".to_string()), + ("ferris".to_string(), "crab".to_string()), + ]); + blob_client.set_tags(blob_tags.clone(), None).await?; + + // Assert + let response_tags = blob_client.get_tags(None).await?.into_body().await?; + assert_eq!(blob_tags, response_tags); + + // 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); + + container_client.delete_container(None).await?; + Ok(()) +} + #[recorded::test] async fn test_get_account_info(ctx: TestContext) -> Result<(), Box> { // Recording Setup From 87e9d4e5fbaab14bb2468d8f51ed6eb689c9a9a9 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Wed, 30 Jul 2025 14:45:29 -0700 Subject: [PATCH 14/24] nit --- .../azure_storage_blob/tests/blob_client.rs | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index b3e04dc801..b47406c596 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -455,36 +455,6 @@ async fn test_blob_tags(ctx: TestContext) -> Result<(), Box> { Ok(()) } -#[recorded::test] -async fn test_blob_tags2(ctx: TestContext) -> Result<(), Box> { - // Recording Setup - let recording = ctx.recording(); - let container_client = get_container_client(recording, true).await?; - let blob_client = container_client.blob_client(get_blob_name(recording)); - create_test_blob(&blob_client).await?; - - // Set Tags with Tags Specified - let blob_tags = HashMap::from([ - ("hello".to_string(), "world".to_string()), - ("ferris".to_string(), "crab".to_string()), - ]); - blob_client.set_tags(blob_tags.clone(), None).await?; - - // Assert - let response_tags = blob_client.get_tags(None).await?.into_body().await?; - assert_eq!(blob_tags, response_tags); - - // 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); - - container_client.delete_container(None).await?; - Ok(()) -} - #[recorded::test] async fn test_get_account_info(ctx: TestContext) -> Result<(), Box> { // Recording Setup From f24aaed4b216a8b47503b6d468c645fa08c87816 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 4 Aug 2025 13:35:35 -0700 Subject: [PATCH 15/24] Refactor to new azure-core code, now hitting error case --- .../src/clients/blob_service_client.rs | 2 +- sdk/storage/azure_storage_blob/src/parsers.rs | 14 +++++++++----- .../tests/blob_service_client.rs | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs index 3d70dd7424..401081e6f1 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs @@ -106,7 +106,7 @@ impl BlobServiceClient { /// * `options` - Optional configuration for the request. pub async fn set_properties( &self, - storage_service_properties: RequestContent, + storage_service_properties: RequestContent, options: Option>, ) -> Result> { self.client diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index b025de5e88..4b44b025b7 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -2,6 +2,7 @@ // Licensed under the MIT License. use crate::generated::models::{BlobTag, BlobTags}; +use azure_core::http::response::ResponseBody; use azure_core::http::{RawResponse, RequestContent, Response, XmlFormat}; use azure_core::xml; use std::collections::{BTreeMap, HashMap}; @@ -69,18 +70,21 @@ pub(crate) async fn deserialize_blob_tags( ) -> azure_core::Result, XmlFormat>> where { - let mut blob_tags_map = HashMap::new(); + let mut blob_tags_map: HashMap = 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 blob_tag in blob_tag_set { - blob_tags_map.insert(blob_tag.key, blob_tag.value); + for tag in blob_tag_set { + if let (Some(k), Some(v)) = (tag.key, tag.value) { + blob_tags_map.insert(k, v); + } } } - let xml_body = xml::to_xml_with_root("HashMap", &blob_tags_map)?; - let raw_response = RawResponse::from_bytes(status, headers, xml_body); + let request_content: RequestContent> = + RequestContent::try_from(blob_tags_map)?; + let raw_response = RawResponse::from_bytes(status, headers, request_content.body()); Ok(raw_response.into()) } diff --git a/sdk/storage/azure_storage_blob/tests/blob_service_client.rs b/sdk/storage/azure_storage_blob/tests/blob_service_client.rs index 83cc9a454e..62b24628e8 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_service_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_service_client.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use azure_core::http::RequestContent; +use azure_core::http::{RequestContent, XmlFormat}; use azure_core_test::{recorded, TestContext}; use azure_storage_blob::models::{ AccountKind, BlobServiceClientGetAccountInfoResultHeaders, @@ -137,7 +137,7 @@ async fn test_set_service_properties(ctx: TestContext) -> Result<(), Box = + let request_content: RequestContent = storage_service_properties.try_into()?; service_client.set_properties(request_content, None).await?; From 9fa4395786b5c1f2b01a438ff22afdd436f4ad3c Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 5 Aug 2025 15:01:24 -0700 Subject: [PATCH 16/24] Refactor for v20 emitter changes --- sdk/storage/azure_storage_blob/assets.json | 2 +- sdk/storage/azure_storage_blob/src/clients/blob_client.rs | 4 ++-- sdk/storage/azure_storage_blob/src/parsers.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/storage/azure_storage_blob/assets.json b/sdk/storage/azure_storage_blob/assets.json index 4df31bcbeb..6f66fbd731 100644 --- a/sdk/storage/azure_storage_blob/assets.json +++ b/sdk/storage/azure_storage_blob/assets.json @@ -1,6 +1,6 @@ { "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "rust", - "Tag": "rust/azure_storage_blob_64e723a779", + "Tag": "rust/azure_storage_blob_49079e88a1", "TagPrefix": "rust/azure_storage_blob" } \ No newline at end of file diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index c40630adf7..e7f2f9e588 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -27,7 +27,7 @@ use azure_core::{ credentials::TokenCredential, http::{ policies::{BearerTokenCredentialPolicy, Policy}, - NoFormat, RequestContent, Response, Url, XmlFormat, + JsonFormat, NoFormat, RequestContent, Response, Url, XmlFormat, }, Bytes, Result, }; @@ -339,7 +339,7 @@ impl BlobClient { pub async fn get_tags( &self, options: Option>, - ) -> Result, XmlFormat>> { + ) -> Result, JsonFormat>> { let response = self.client.get_tags(options).await?; deserialize_blob_tags(response).await } diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index 4b44b025b7..6903dd168a 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -3,7 +3,7 @@ use crate::generated::models::{BlobTag, BlobTags}; use azure_core::http::response::ResponseBody; -use azure_core::http::{RawResponse, RequestContent, Response, XmlFormat}; +use azure_core::http::{JsonFormat, RawResponse, RequestContent, Response, XmlFormat}; use azure_core::xml; use std::collections::{BTreeMap, HashMap}; use std::io::{Error, ErrorKind}; @@ -67,7 +67,7 @@ pub(crate) fn serialize_blob_tags(tags: HashMap) -> BlobTags { /// * `response` - The `get_tags()` response to be deserialized. pub(crate) async fn deserialize_blob_tags( response: Response, -) -> azure_core::Result, XmlFormat>> +) -> azure_core::Result, JsonFormat>> where { let mut blob_tags_map: HashMap = HashMap::new(); From 8c390ef62e1f51dcb4f6431744c168987c841534 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Wed, 13 Aug 2025 17:42:01 -0700 Subject: [PATCH 17/24] Added TryFrom BlobTags -> HashMap --- .../src/clients/blob_client.rs | 6 ++-- .../src/models/extensions.rs | 27 ++++++++++++++++- sdk/storage/azure_storage_blob/src/parsers.rs | 29 ------------------- .../azure_storage_blob/tests/blob_client.rs | 6 ++-- 4 files changed, 32 insertions(+), 36 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index e7f2f9e588..8eae89a8b9 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -2,7 +2,6 @@ // Licensed under the MIT License. use crate::{ - deserialize_blob_tags, generated::clients::BlobClient as GeneratedBlobClient, generated::models::{ BlobClientAcquireLeaseResult, BlobClientBreakLeaseResult, BlobClientChangeLeaseResult, @@ -339,9 +338,8 @@ impl BlobClient { pub async fn get_tags( &self, options: Option>, - ) -> Result, JsonFormat>> { - let response = self.client.get_tags(options).await?; - deserialize_blob_tags(response).await + ) -> Result> { + self.client.get_tags(options).await } /// Gets information related to the Storage account in which the blob resides. diff --git a/sdk/storage/azure_storage_blob/src/models/extensions.rs b/sdk/storage/azure_storage_blob/src/models/extensions.rs index 17145a1b28..446e0cc996 100644 --- a/sdk/storage/azure_storage_blob/src/models/extensions.rs +++ b/sdk/storage/azure_storage_blob/src/models/extensions.rs @@ -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::HashMap; /// Provides usage helpers for setting the `PageBlobClientCreateOptions` optional configurations. pub trait PageBlobClientCreateOptionsExt { @@ -38,3 +41,25 @@ impl AppendBlobClientCreateOptionsExt for AppendBlobClientCreateOptions<'_> { } } } + +/// Converts a `BlobTags` struct into `HashMap`. +impl TryFrom for HashMap { + type Error = &'static str; + + fn try_from(blob_tags: BlobTags) -> Result { + 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) + } +} diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index 6903dd168a..4684cbf7f4 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -59,32 +59,3 @@ pub(crate) fn serialize_blob_tags(tags: HashMap) -> 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, -) -> azure_core::Result, JsonFormat>> -where -{ - let mut blob_tags_map: HashMap = 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> = - RequestContent::try_from(blob_tags_map)?; - let raw_response = RawResponse::from_bytes(status, headers, request_content.body()); - Ok(raw_response.into()) -} diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index b47406c596..58d9f91f0f 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -442,14 +442,16 @@ async fn test_blob_tags(ctx: TestContext) -> Result<(), Box> { // Assert let response_tags = blob_client.get_tags(None).await?.into_body().await?; - assert_eq!(blob_tags, response_tags); + let map: HashMap = 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 = response_tags.try_into()?; + assert_eq!(HashMap::new(), map); container_client.delete_container(None).await?; Ok(()) From aa92db4feeec9d3178a153d737c75e515da2a5b0 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Wed, 13 Aug 2025 18:05:18 -0700 Subject: [PATCH 18/24] Re-record tests, add commutativity --- sdk/storage/azure_storage_blob/assets.json | 2 +- .../src/clients/blob_client.rs | 4 ++-- .../src/models/extensions.rs | 19 ++++++++++++++++++- sdk/storage/azure_storage_blob/src/parsers.rs | 19 ------------------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/sdk/storage/azure_storage_blob/assets.json b/sdk/storage/azure_storage_blob/assets.json index 6f66fbd731..8bd5b37dca 100644 --- a/sdk/storage/azure_storage_blob/assets.json +++ b/sdk/storage/azure_storage_blob/assets.json @@ -1,6 +1,6 @@ { "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "rust", - "Tag": "rust/azure_storage_blob_49079e88a1", + "Tag": "rust/azure_storage_blob_9de297e630", "TagPrefix": "rust/azure_storage_blob" } \ No newline at end of file diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index 8eae89a8b9..525b38e4fc 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -20,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, @@ -324,7 +324,7 @@ impl BlobClient { tags: HashMap, options: Option>, ) -> Result> { - let blob_tags = serialize_blob_tags(tags); + let blob_tags: BlobTags = tags.into(); self.client .set_tags(RequestContent::try_from(blob_tags)?, options) .await diff --git a/sdk/storage/azure_storage_blob/src/models/extensions.rs b/sdk/storage/azure_storage_blob/src/models/extensions.rs index 446e0cc996..77dc0558b7 100644 --- a/sdk/storage/azure_storage_blob/src/models/extensions.rs +++ b/sdk/storage/azure_storage_blob/src/models/extensions.rs @@ -4,7 +4,7 @@ use crate::models::{ AppendBlobClientCreateOptions, BlobTag, BlobTags, PageBlobClientCreateOptions, }; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; /// Provides usage helpers for setting the `PageBlobClientCreateOptions` optional configurations. pub trait PageBlobClientCreateOptionsExt { @@ -63,3 +63,20 @@ impl TryFrom for HashMap { Ok(map) } } + +/// Converts a `HashMap` into a `BlobTags` struct. +impl From> for BlobTags { + fn from(tags: HashMap) -> Self { + 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), + } + } +} diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index 4684cbf7f4..9882915a12 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -40,22 +40,3 @@ pub fn format_page_range(offset: u64, length: u64) -> Result { 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) -> 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), - } -} From 61def999ff1564c6a48b4fc45d76b439dc40eac1 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Wed, 13 Aug 2025 18:15:47 -0700 Subject: [PATCH 19/24] nit --- sdk/storage/azure_storage_blob/assets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/azure_storage_blob/assets.json b/sdk/storage/azure_storage_blob/assets.json index 8bd5b37dca..e326bc025c 100644 --- a/sdk/storage/azure_storage_blob/assets.json +++ b/sdk/storage/azure_storage_blob/assets.json @@ -1,6 +1,6 @@ { "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "rust", - "Tag": "rust/azure_storage_blob_9de297e630", + "Tag": "rust/azure_storage_blob_cb9b90a35f", "TagPrefix": "rust/azure_storage_blob" } \ No newline at end of file From 790e3faa3593a34517459a0744182e8f96b08867 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Thu, 14 Aug 2025 17:02:47 -0700 Subject: [PATCH 20/24] Regen against new feature/blob-tsp-rust --- sdk/storage/azure_storage_blob/src/parsers.rs | 5 ----- sdk/storage/azure_storage_blob/tsp-location.yaml | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index 9882915a12..e3c36c42d5 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -1,11 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use crate::generated::models::{BlobTag, BlobTags}; -use azure_core::http::response::ResponseBody; -use azure_core::http::{JsonFormat, RawResponse, RequestContent, Response, XmlFormat}; -use azure_core::xml; -use std::collections::{BTreeMap, HashMap}; use std::io::{Error, ErrorKind}; /// Takes in an offset and a length, verifies alignment to a 512-byte boundary, and diff --git a/sdk/storage/azure_storage_blob/tsp-location.yaml b/sdk/storage/azure_storage_blob/tsp-location.yaml index feda9f29f9..a3ddd99f7c 100644 --- a/sdk/storage/azure_storage_blob/tsp-location.yaml +++ b/sdk/storage/azure_storage_blob/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/storage/Microsoft.BlobStorage -commit: b0760deb3480f3a648c8e656224e89d172bbe244 +commit: c9f4849ea5e73ada5cc070c2c728526e0297eee0 repo: Azure/azure-rest-api-specs -additionalDirectories: +additionalDirectories: From 010c18b8359d1c6af0d096bac968e167c2b1bb24 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Thu, 14 Aug 2025 17:23:55 -0700 Subject: [PATCH 21/24] Regen against feature/blob-tsp-rust --- sdk/storage/azure_storage_blob/tsp-location.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/azure_storage_blob/tsp-location.yaml b/sdk/storage/azure_storage_blob/tsp-location.yaml index a3ddd99f7c..c12649c61b 100644 --- a/sdk/storage/azure_storage_blob/tsp-location.yaml +++ b/sdk/storage/azure_storage_blob/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/storage/Microsoft.BlobStorage -commit: c9f4849ea5e73ada5cc070c2c728526e0297eee0 +commit: bafe771a792f70e71926d83bc0b27a925fba3f01 repo: Azure/azure-rest-api-specs additionalDirectories: From c243588ae5f948389642c9bab95f33c088490c0d Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 18 Aug 2025 15:49:15 -0700 Subject: [PATCH 22/24] Add parser that is context aware, remove sort in prod code --- .../src/clients/blob_client.rs | 4 ++-- .../src/models/extensions.rs | 15 ++++++++----- sdk/storage/azure_storage_blob/src/parsers.rs | 22 +++++++++++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index 525b38e4fc..770c90dfa3 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -20,7 +20,7 @@ use crate::{ BlockListType, BlockLookupList, }, pipeline::StorageHeadersPolicy, - AppendBlobClient, BlobClientOptions, BlockBlobClient, PageBlobClient, + serialize_blob_tags, AppendBlobClient, BlobClientOptions, BlockBlobClient, PageBlobClient, }; use azure_core::{ credentials::TokenCredential, @@ -324,7 +324,7 @@ impl BlobClient { tags: HashMap, options: Option>, ) -> Result> { - let blob_tags: BlobTags = tags.into(); + let blob_tags: BlobTags = serialize_blob_tags(tags); self.client .set_tags(RequestContent::try_from(blob_tags)?, options) .await diff --git a/sdk/storage/azure_storage_blob/src/models/extensions.rs b/sdk/storage/azure_storage_blob/src/models/extensions.rs index 77dc0558b7..61084ea113 100644 --- a/sdk/storage/azure_storage_blob/src/models/extensions.rs +++ b/sdk/storage/azure_storage_blob/src/models/extensions.rs @@ -4,6 +4,7 @@ use crate::models::{ AppendBlobClientCreateOptions, BlobTag, BlobTags, PageBlobClientCreateOptions, }; +use azure_core::error::ErrorKind; use std::collections::{BTreeMap, HashMap}; /// Provides usage helpers for setting the `PageBlobClientCreateOptions` optional configurations. @@ -44,9 +45,9 @@ impl AppendBlobClientCreateOptionsExt for AppendBlobClientCreateOptions<'_> { /// Converts a `BlobTags` struct into `HashMap`. impl TryFrom for HashMap { - type Error = &'static str; + type Error = azure_core::Error; - fn try_from(blob_tags: BlobTags) -> Result { + fn try_from(blob_tags: BlobTags) -> Result { let mut map = HashMap::new(); if let Some(tags) = blob_tags.blob_tag_set { @@ -55,7 +56,12 @@ impl TryFrom for HashMap { (Some(k), Some(v)) => { map.insert(k, v); } - _ => return Err("BlobTag missing key or value"), + _ => { + return Err(azure_core::Error::message( + azure_core::error::ErrorKind::DataConversion, + "BlobTag missing key or value", + )); + } } } } @@ -67,8 +73,7 @@ impl TryFrom for HashMap { /// Converts a `HashMap` into a `BlobTags` struct. impl From> for BlobTags { fn from(tags: HashMap) -> Self { - let sorted_tags: BTreeMap<_, _> = tags.into_iter().collect(); - let blob_tags = sorted_tags + let blob_tags = tags .into_iter() .map(|(k, v)| BlobTag { key: Some(k), diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index e3c36c42d5..d6837d5ae6 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use crate::models::{BlobTag, BlobTags}; +use std::collections::HashMap; use std::io::{Error, ErrorKind}; /// Takes in an offset and a length, verifies alignment to a 512-byte boundary, and @@ -35,3 +37,23 @@ pub fn format_page_range(offset: u64, length: u64) -> Result { let content_range = format!("bytes={}-{}", offset, end_range); Ok(content_range) } + +/// Helper function to call the correct conversion method depending on test context. +#[cfg(test)] +pub(crate) fn serialize_blob_tags(tags: HashMap) -> BlobTags { + let blob_tags = tags + .into_iter() + .map(|(k, v)| BlobTag { + key: Some(k), + value: Some(v), + }) + .collect(); + BlobTags { + blob_tag_set: Some(blob_tags), + } +} + +#[cfg(not(test))] +pub(crate) fn serialize_blob_tags(tags: HashMap) -> BlobTags { + tags.into() +} From 5f057e9aa91006ddec0a27e89b2ae8317e1716da Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 18 Aug 2025 16:06:07 -0700 Subject: [PATCH 23/24] Revert, unit test flag --- .../src/clients/blob_client.rs | 4 ++-- sdk/storage/azure_storage_blob/src/parsers.rs | 20 ------------------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index 770c90dfa3..525b38e4fc 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -20,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, @@ -324,7 +324,7 @@ impl BlobClient { tags: HashMap, options: Option>, ) -> Result> { - let blob_tags: BlobTags = serialize_blob_tags(tags); + let blob_tags: BlobTags = tags.into(); self.client .set_tags(RequestContent::try_from(blob_tags)?, options) .await diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index d6837d5ae6..4cc65a18af 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -37,23 +37,3 @@ pub fn format_page_range(offset: u64, length: u64) -> Result { let content_range = format!("bytes={}-{}", offset, end_range); Ok(content_range) } - -/// Helper function to call the correct conversion method depending on test context. -#[cfg(test)] -pub(crate) fn serialize_blob_tags(tags: HashMap) -> BlobTags { - let blob_tags = tags - .into_iter() - .map(|(k, v)| BlobTag { - key: Some(k), - value: Some(v), - }) - .collect(); - BlobTags { - blob_tag_set: Some(blob_tags), - } -} - -#[cfg(not(test))] -pub(crate) fn serialize_blob_tags(tags: HashMap) -> BlobTags { - tags.into() -} From 5f6e56b9ff112adef4951d5565c90a724ac67d7a Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 19 Aug 2025 14:33:04 -0700 Subject: [PATCH 24/24] Use BodilessMatcher --- sdk/storage/azure_storage_blob/src/models/extensions.rs | 2 +- sdk/storage/azure_storage_blob/tests/blob_client.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/models/extensions.rs b/sdk/storage/azure_storage_blob/src/models/extensions.rs index 61084ea113..4a358fc70a 100644 --- a/sdk/storage/azure_storage_blob/src/models/extensions.rs +++ b/sdk/storage/azure_storage_blob/src/models/extensions.rs @@ -5,7 +5,7 @@ use crate::models::{ AppendBlobClientCreateOptions, BlobTag, BlobTags, PageBlobClientCreateOptions, }; use azure_core::error::ErrorKind; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; /// Provides usage helpers for setting the `PageBlobClientCreateOptions` optional configurations. pub trait PageBlobClientCreateOptionsExt { diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index 58d9f91f0f..dad85c91f5 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -5,7 +5,7 @@ use azure_core::{ http::{RequestContent, StatusCode}, Bytes, }; -use azure_core_test::{recorded, TestContext}; +use azure_core_test::{recorded, Matcher, TestContext}; use azure_storage_blob::models::{ AccessTier, AccountKind, BlobClientAcquireLeaseResultHeaders, BlobClientChangeLeaseResultHeaders, BlobClientDownloadOptions, BlobClientDownloadResultHeaders, @@ -429,6 +429,7 @@ async fn test_leased_blob_operations(ctx: TestContext) -> Result<(), Box Result<(), Box> { // Recording Setup let recording = ctx.recording(); + recording.set_matcher(Matcher::BodilessMatcher).await?; let container_client = get_container_client(recording, true).await?; let blob_client = container_client.blob_client(get_blob_name(recording)); create_test_blob(&blob_client).await?;