Repository: Azure/azure-sdk-for-rust
Pull request #2539: [Storage] Handwritten `BlockBlobClient` 
Author: login: vincenttran-msft
Labels: name: Storage
description: Storage Service (Queues, Blobs, Files)
Created: 2025-04-29T22:22:12Z | Merged: 2025-05-01T20:41:45Z

This PR introduces the following:

- Initial implementation of handwritten `BlockBlobClient`
- Moves relevant tests from base BlobClient -> BlockBlobClient
- Test recordings (combined redundant block list tests into one)


Diff:
diff --git a/sdk/storage/assets.json b/sdk/storage/assets.json
index 4c13e285e92..e28deb23935 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_c54b1e37ce",
+  "Tag": "rust/azure_storage_blob_be0a70462b",
   "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 e68e86c5323..bd76a614cc7 100644
--- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs
+++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs
@@ -12,7 +12,7 @@ use crate::{
     pipeline::StorageHeadersPolicy,
     BlobClientDeleteOptions, BlobClientDownloadOptions, BlobClientGetPropertiesOptions,
     BlobClientOptions, BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions,
-    BlobClientSetTierOptions, BlockBlobClientCommitBlockListOptions,
+    BlobClientSetTierOptions, BlockBlobClient, BlockBlobClientCommitBlockListOptions,
     BlockBlobClientGetBlockListOptions, BlockBlobClientStageBlockOptions,
     BlockBlobClientUploadOptions,
 };
@@ -79,6 +79,17 @@ impl BlobClient {
         })
     }
 
+    /// Returns a new instance of BlockBlobClient.
+    ///
+    /// # Arguments
+    ///
+    pub fn block_blob_client(&self) -> BlockBlobClient {
+        BlockBlobClient {
+            endpoint: self.client.endpoint.clone(),
+            client: self.client.get_block_blob_client(),
+        }
+    }
+
     /// Gets the endpoint of the Storage account this client is connected to.
     pub fn endpoint(&self) -> &Url {
         &self.endpoint
@@ -184,58 +195,6 @@ impl BlobClient {
         self.client.delete(options).await
     }
 
-    /// Writes to a blob based on blocks specified by the list of IDs and content that make up the blob.
-    ///
-    /// # Arguments
-    ///
-    /// * `blocks` - The list of Blob blocks to commit.
-    /// * `options` - Optional configuration for the request.
-    pub async fn commit_block_list(
-        &self,
-        blocks: RequestContent<BlockLookupList>,
-        options: Option<BlockBlobClientCommitBlockListOptions<'_>>,
-    ) -> Result<Response<BlockBlobClientCommitBlockListResult>> {
-        let block_blob_client = self.client.get_block_blob_client();
-        block_blob_client.commit_block_list(blocks, options).await
-    }
-
-    /// Creates a new block to be later committed as part of a blob.
-    ///
-    /// # Arguments
-    ///
-    /// * `block_id` - The unique identifier for the block. The identifier should be less than or equal to 64 bytes in size.
-    ///   For a given blob, the `block_id` must be the same size for each block.
-    /// * `content_length` - Total length of the blob data to be staged.
-    /// * `data` - The content of the block.
-    /// * `options` - Optional configuration for the request.
-    pub async fn stage_block(
-        &self,
-        block_id: Vec<u8>,
-        content_length: u64,
-        body: RequestContent<Bytes>,
-        options: Option<BlockBlobClientStageBlockOptions<'_>>,
-    ) -> Result<Response<BlockBlobClientStageBlockResult>> {
-        let block_blob_client = self.client.get_block_blob_client();
-        block_blob_client
-            .stage_block(block_id, content_length, body, options)
-            .await
-    }
-
-    /// Retrieves the list of blocks that have been uploaded as part of a block blob.
-    ///
-    /// # Arguments
-    ///
-    /// * `list_type` - Specifies whether to return the list of committed blocks, uncommitted blocks, or both lists together.
-    /// * `options` - Optional configuration for the request.
-    pub async fn get_block_list(
-        &self,
-        list_type: BlockListType,
-        options: Option<BlockBlobClientGetBlockListOptions<'_>>,
-    ) -> Result<Response<BlockList>> {
-        let block_blob_client = self.client.get_block_blob_client();
-        block_blob_client.get_block_list(list_type, options).await
-    }
-
     /// Sets the tier on a blob. Standard tiers are only applicable for Block blobs, while Premium tiers are only applicable
     /// for Page blobs.
     ///
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 f5c36a17d1b..f66ff7a67ba 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
@@ -74,7 +74,6 @@ impl BlobContainerClient {
     /// # Arguments
     ///
     /// * `blob_name` - The name of the blob.
-    /// * `options` - Optional configuration for the client.
     pub fn blob_client(&self, blob_name: String) -> BlobClient {
         BlobClient {
             endpoint: self.client.endpoint.clone(),
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 b35468397b7..bfb1d9a3458 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
@@ -65,7 +65,6 @@ impl BlobServiceClient {
     /// # Arguments
     ///
     /// * `container_name` - The name of the container.
-    /// * `options` - Optional configuration for the client.
     pub fn blob_container_client(&self, container_name: String) -> BlobContainerClient {
         BlobContainerClient {
             endpoint: self.client.endpoint.clone(),
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
new file mode 100644
index 00000000000..56402ead85e
--- /dev/null
+++ b/sdk/storage/azure_storage_blob/src/clients/block_blob_client.rs
@@ -0,0 +1,145 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+use crate::{
+    generated::clients::BlockBlobClient as GeneratedBlockBlobClient,
+    generated::models::{
+        BlobClientDownloadResult, BlobClientGetPropertiesResult,
+        BlockBlobClientCommitBlockListResult, BlockBlobClientStageBlockResult,
+        BlockBlobClientUploadResult,
+    },
+    models::{BlockList, BlockListType, BlockLookupList, StorageServiceProperties},
+    pipeline::StorageHeadersPolicy,
+    BlobClientDeleteOptions, BlobClientDownloadOptions, BlobClientGetPropertiesOptions,
+    BlobClientOptions, BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions,
+    BlobClientSetTierOptions, BlockBlobClientCommitBlockListOptions,
+    BlockBlobClientGetBlockListOptions, BlockBlobClientOptions, BlockBlobClientStageBlockOptions,
+    BlockBlobClientUploadOptions,
+};
+use azure_core::{
+    credentials::TokenCredential,
+    http::{
+        policies::{BearerTokenCredentialPolicy, Policy},
+        RequestContent, Response, Url,
+    },
+    Bytes, Result,
+};
+use std::sync::Arc;
+
+/// A client to interact with a specific Azure storage Block blob, although that blob may not yet exist.
+pub struct BlockBlobClient {
+    pub(crate) endpoint: Url,
+    pub(crate) client: GeneratedBlockBlobClient,
+}
+
+impl BlockBlobClient {
+    /// Creates a new BlockBlobClient, using Entra ID authentication.
+    ///
+    /// # Arguments
+    ///
+    /// * `endpoint` - The full URL of the Azure storage account, for example `https://myaccount.blob.core.windows.net/`
+    /// * `container_name` - The name of the container containing this Block blob.
+    /// * `blob_name` - The name of the Block blob to interact with.
+    /// * `credential` - An implementation of [`TokenCredential`] that can provide an Entra ID token to use when authenticating.
+    /// * `options` - Optional configuration for the client.
+    pub fn new(
+        endpoint: &str,
+        container_name: String,
+        blob_name: String,
+        credential: Arc<dyn TokenCredential>,
+        options: Option<BlockBlobClientOptions>,
+    ) -> Result<Self> {
+        let mut options = options.unwrap_or_default();
+
+        let storage_headers_policy = Arc::new(StorageHeadersPolicy);
+        options
+            .client_options
+            .per_call_policies
+            .push(storage_headers_policy);
+
+        let oauth_token_policy = BearerTokenCredentialPolicy::new(
+            credential.clone(),
+            ["https://storage.azure.com/.default"],
+        );
+        options
+            .client_options
+            .per_try_policies
+            .push(Arc::new(oauth_token_policy) as Arc<dyn Policy>);
+
+        let client = GeneratedBlockBlobClient::new(
+            endpoint,
+            credential.clone(),
+            container_name.clone(),
+            blob_name.clone(),
+            Some(options),
+        )?;
+        Ok(Self {
+            endpoint: endpoint.parse()?,
+            client,
+        })
+    }
+
+    /// Gets the endpoint of the Storage account this client is connected to.
+    pub fn endpoint(&self) -> &Url {
+        &self.endpoint
+    }
+
+    /// Gets the container name of the Storage account this client is connected to.
+    pub fn container_name(&self) -> &str {
+        &self.client.container_name
+    }
+
+    /// Gets the blob name of the Storage account this client is connected to.
+    pub fn blob_name(&self) -> &str {
+        &self.client.blob_name
+    }
+
+    /// Writes to a blob based on blocks specified by the list of IDs and content that make up the blob.
+    ///
+    /// # Arguments
+    ///
+    /// * `blocks` - The list of Blob blocks to commit.
+    /// * `options` - Optional configuration for the request.
+    pub async fn commit_block_list(
+        &self,
+        blocks: RequestContent<BlockLookupList>,
+        options: Option<BlockBlobClientCommitBlockListOptions<'_>>,
+    ) -> Result<Response<BlockBlobClientCommitBlockListResult>> {
+        self.client.commit_block_list(blocks, options).await
+    }
+
+    /// Creates a new block to be later committed as part of a blob.
+    ///
+    /// # Arguments
+    ///
+    /// * `block_id` - The unique identifier for the block. The identifier should be less than or equal to 64 bytes in size.
+    ///   For a given blob, the `block_id` must be the same size for each block.
+    /// * `content_length` - Total length of the blob data to be staged.
+    /// * `data` - The content of the block.
+    /// * `options` - Optional configuration for the request.
+    pub async fn stage_block(
+        &self,
+        block_id: Vec<u8>,
+        content_length: u64,
+        body: RequestContent<Bytes>,
+        options: Option<BlockBlobClientStageBlockOptions<'_>>,
+    ) -> Result<Response<BlockBlobClientStageBlockResult>> {
+        self.client
+            .stage_block(block_id, content_length, body, options)
+            .await
+    }
+
+    /// Retrieves the list of blocks that have been uploaded as part of a block blob.
+    ///
+    /// # Arguments
+    ///
+    /// * `list_type` - Specifies whether to return the list of committed blocks, uncommitted blocks, or both lists together.
+    /// * `options` - Optional configuration for the request.
+    pub async fn get_block_list(
+        &self,
+        list_type: BlockListType,
+        options: Option<BlockBlobClientGetBlockListOptions<'_>>,
+    ) -> Result<Response<BlockList>> {
+        self.client.get_block_list(list_type, options).await
+    }
+}
diff --git a/sdk/storage/azure_storage_blob/src/clients/mod.rs b/sdk/storage/azure_storage_blob/src/clients/mod.rs
index 322ff442445..63986085798 100644
--- a/sdk/storage/azure_storage_blob/src/clients/mod.rs
+++ b/sdk/storage/azure_storage_blob/src/clients/mod.rs
@@ -6,7 +6,9 @@
 mod blob_client;
 mod blob_container_client;
 mod blob_service_client;
+mod block_blob_client;
 
 pub use blob_client::BlobClient;
 pub use blob_container_client::BlobContainerClient;
 pub use blob_service_client::BlobServiceClient;
+pub use block_blob_client::BlockBlobClient;
diff --git a/sdk/storage/azure_storage_blob/src/lib.rs b/sdk/storage/azure_storage_blob/src/lib.rs
index 75460580b8c..ec64004b2db 100644
--- a/sdk/storage/azure_storage_blob/src/lib.rs
+++ b/sdk/storage/azure_storage_blob/src/lib.rs
@@ -11,9 +11,9 @@ pub mod clients;
 mod generated;
 mod pipeline;
 
-pub use crate::clients::{BlobClient, BlobContainerClient, BlobServiceClient};
+pub use crate::clients::{BlobClient, BlobContainerClient, BlobServiceClient, BlockBlobClient};
 pub use crate::generated::clients::{
-    BlobClientOptions, BlobContainerClientOptions, BlobServiceClientOptions,
+    BlobClientOptions, BlobContainerClientOptions, BlobServiceClientOptions, BlockBlobClientOptions,
 };
 pub use crate::generated::models::{
     BlobClientDeleteOptions, BlobClientDownloadOptions, BlobClientGetPropertiesOptions,
diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs
index 284d990cb64..af31c4a6d09 100644
--- a/sdk/storage/azure_storage_blob/tests/blob_client.rs
+++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs
@@ -9,7 +9,7 @@ use azure_core_test::{recorded, TestContext};
 use azure_storage_blob::{
     models::{
         AccessTier, BlobClientDownloadResultHeaders, BlobClientGetPropertiesResultHeaders,
-        BlockListType, BlockLookupList, LeaseState,
+        LeaseState,
     },
     BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlockBlobClientUploadOptions,
 };
@@ -255,154 +255,6 @@ async fn test_set_blob_metadata(ctx: TestContext) -> Result<(), Box<dyn Error>>
     Ok(())
 }
 
-#[recorded::test]
-async fn test_put_block_list(ctx: TestContext) -> Result<(), Box<dyn Error>> {
-    // 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));
-
-    let block_1 = b"AAA";
-    let block_2 = b"BBB";
-    let block_3 = b"CCC";
-
-    let block_1_id: Vec<u8> = b"1".to_vec();
-    let block_2_id: Vec<u8> = b"2".to_vec();
-    let block_3_id: Vec<u8> = b"3".to_vec();
-
-    blob_client
-        .stage_block(
-            block_1_id.clone(),
-            u64::try_from(block_1.len())?,
-            RequestContent::from(block_1.to_vec()),
-            None,
-        )
-        .await?;
-
-    blob_client
-        .stage_block(
-            block_2_id.clone(),
-            u64::try_from(block_2.len())?,
-            RequestContent::from(block_2.to_vec()),
-            None,
-        )
-        .await?;
-    blob_client
-        .stage_block(
-            block_3_id.clone(),
-            u64::try_from(block_3.len())?,
-            RequestContent::from(block_3.to_vec()),
-            None,
-        )
-        .await?;
-
-    let latest_blocks: Vec<Vec<u8>> = vec![block_1_id, block_2_id, block_3_id];
-
-    let block_lookup_list = BlockLookupList {
-        committed: Vec::new(),
-        latest: latest_blocks,
-        uncommitted: Vec::new(),
-    };
-
-    let request_content = RequestContent::try_from(block_lookup_list)?;
-
-    blob_client.commit_block_list(request_content, None).await?;
-
-    let response = blob_client.download(None).await?;
-
-    // Assert
-    let content_length = response.content_length()?;
-    let (status_code, _, response_body) = response.deconstruct();
-    assert!(status_code.is_success());
-    assert_eq!(9, content_length.unwrap());
-    assert_eq!(
-        Bytes::from_static(b"AAABBBCCC"),
-        response_body.collect().await?
-    );
-
-    container_client.delete_container(None).await?;
-    Ok(())
-}
-
-#[recorded::test]
-async fn test_get_block_list(ctx: TestContext) -> Result<(), Box<dyn Error>> {
-    // 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));
-
-    let block_1 = b"AAA";
-    let block_2 = b"BBB";
-    let block_3 = b"CCC";
-
-    let block_1_id: Vec<u8> = b"1".to_vec();
-    let block_2_id: Vec<u8> = b"2".to_vec();
-    let block_3_id: Vec<u8> = b"3".to_vec();
-
-    blob_client
-        .stage_block(
-            block_1_id.clone(),
-            u64::try_from(block_1.len())?,
-            RequestContent::from(block_1.to_vec()),
-            None,
-        )
-        .await?;
-
-    blob_client
-        .stage_block(
-            block_2_id.clone(),
-            u64::try_from(block_2.len())?,
-            RequestContent::from(block_2.to_vec()),
-            None,
-        )
-        .await?;
-    blob_client
-        .stage_block(
-            block_3_id.clone(),
-            u64::try_from(block_3.len())?,
-            RequestContent::from(block_3.to_vec()),
-            None,
-        )
-        .await?;
-
-    // Three Staged Blocks Scenario
-    let block_list = blob_client
-        .get_block_list(BlockListType::All, None)
-        .await?
-        .into_body()
-        .await?;
-
-    // Assert
-    assert_eq!(0, block_list.committed_blocks.len());
-    assert_eq!(3, block_list.uncommitted_blocks.len());
-
-    let latest_blocks: Vec<Vec<u8>> = vec![block_1_id, block_2_id, block_3_id];
-
-    let block_lookup_list = BlockLookupList {
-        committed: Vec::new(),
-        latest: latest_blocks,
-        uncommitted: Vec::new(),
-    };
-
-    let request_content = RequestContent::try_from(block_lookup_list)?;
-
-    blob_client.commit_block_list(request_content, None).await?;
-
-    // Three Committed Blocks Scenario
-    let block_list = blob_client
-        .get_block_list(BlockListType::All, None)
-        .await?
-        .into_body()
-        .await?;
-
-    // Assert
-    assert_eq!(3, block_list.committed_blocks.len());
-    assert_eq!(0, block_list.uncommitted_blocks.len());
-
-    container_client.delete_container(None).await?;
-    Ok(())
-}
-
 #[recorded::test]
 async fn test_set_access_tier(ctx: TestContext) -> Result<(), Box<dyn Error>> {
     // Recording Setup
diff --git a/sdk/storage/azure_storage_blob/tests/block_blob_client.rs b/sdk/storage/azure_storage_blob/tests/block_blob_client.rs
new file mode 100644
index 00000000000..0a72c5ca84e
--- /dev/null
+++ b/sdk/storage/azure_storage_blob/tests/block_blob_client.rs
@@ -0,0 +1,99 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+use azure_core::{http::RequestContent, Bytes};
+use azure_core_test::{recorded, TestContext};
+use azure_storage_blob::models::{BlobClientDownloadResultHeaders, BlockListType, BlockLookupList};
+use azure_storage_blob_test::{get_blob_name, get_container_client};
+use std::error::Error;
+
+#[recorded::test]
+async fn test_block_list(ctx: TestContext) -> Result<(), Box<dyn Error>> {
+    // 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));
+    let block_blob_client = blob_client.block_blob_client();
+
+    let block_1 = b"AAA";
+    let block_2 = b"BBB";
+    let block_3 = b"CCC";
+
+    let block_1_id: Vec<u8> = b"1".to_vec();
+    let block_2_id: Vec<u8> = b"2".to_vec();
+    let block_3_id: Vec<u8> = b"3".to_vec();
+
+    block_blob_client
+        .stage_block(
+            block_1_id.clone(),
+            u64::try_from(block_1.len())?,
+            RequestContent::from(block_1.to_vec()),
+            None,
+        )
+        .await?;
+
+    block_blob_client
+        .stage_block(
+            block_2_id.clone(),
+            u64::try_from(block_2.len())?,
+            RequestContent::from(block_2.to_vec()),
+            None,
+        )
+        .await?;
+    block_blob_client
+        .stage_block(
+            block_3_id.clone(),
+            u64::try_from(block_3.len())?,
+            RequestContent::from(block_3.to_vec()),
+            None,
+        )
+        .await?;
+
+    // Three Staged Blocks Scenario
+    let block_list = block_blob_client
+        .get_block_list(BlockListType::All, None)
+        .await?
+        .into_body()
+        .await?;
+
+    // Assert
+    assert_eq!(0, block_list.committed_blocks.len());
+    assert_eq!(3, block_list.uncommitted_blocks.len());
+
+    let latest_blocks: Vec<Vec<u8>> = vec![block_1_id, block_2_id, block_3_id];
+
+    let block_lookup_list = BlockLookupList {
+        committed: Vec::new(),
+        latest: latest_blocks,
+        uncommitted: Vec::new(),
+    };
+
+    let request_content = RequestContent::try_from(block_lookup_list)?;
+
+    block_blob_client
+        .commit_block_list(request_content, None)
+        .await?;
+
+    // Three Committed Blocks Scenario
+    let block_list = block_blob_client
+        .get_block_list(BlockListType::All, None)
+        .await?
+        .into_body()
+        .await?;
+    let response = blob_client.download(None).await?;
+
+    // Assert
+    let content_length = response.content_length()?;
+    let (status_code, _, response_body) = response.deconstruct();
+    assert!(status_code.is_success());
+    assert_eq!(9, content_length.unwrap());
+    assert_eq!(
+        Bytes::from_static(b"AAABBBCCC"),
+        response_body.collect().await?
+    );
+    assert_eq!(3, block_list.committed_blocks.len());
+    assert_eq!(0, block_list.uncommitted_blocks.len());
+
+    container_client.delete_container(None).await?;
+    Ok(())
+}


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a handwritten implementation for BlockBlobClient to separately manage block blob operations and updates tests accordingly.  
- Introduces BlockBlobClient in the clients module with its own methods for staging blocks, committing block lists, and retrieving block lists.  
- Moves and updates tests from BlobClient to a dedicated BlockBlobClient test file, while removing redundant tests in blob_client.rs.  
- Updates public exports in lib.rs and client modules to include BlockBlobClient.

### Reviewed Changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File                                                            | Description                                                 |
| ---------------------------------------------------------------- | ----------------------------------------------------------- |
| sdk/storage/azure_storage_blob/tests/block_blob_client.rs        | Adds new tests covering block staging, commit, and download.|
| sdk/storage/azure_storage_blob/tests/blob_client.rs               | Removes redundant block blob tests now covered in the new file. |
| sdk/storage/azure_storage_blob/src/lib.rs                          | Updates exports to include BlockBlobClient.               |
| sdk/storage/azure_storage_blob/src/clients/mod.rs                  | Adds the block_blob_client module and respective export.   |
| sdk/storage/azure_storage_blob/src/clients/block_blob_client.rs      | Implements the handwritten BlockBlobClient methods.        |
| sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs    | Updates documentation by removing an unnecessary parameter note. |
| sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs  | Updates documentation by removing an unnecessary parameter note. |
| sdk/storage/azure_storage_blob/src/clients/blob_client.rs            | Adds a new block_blob_client method and removes duplicate implementations. |
</details>


<details>
<summary>Files not reviewed (1)</summary>

* **sdk/storage/assets.json**: Language not supported
</details>



state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T22:46:05Z
user: login: jalauzon-msft
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-30T22:15:14Z
user: login: heaths
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-01T18:25:00Z
user: login: vincenttran-msft
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-01T18:29:45Z
user: login: vincenttran-msft
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-01T18:31:20Z
user: login: heaths
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-01T19:54:18Z

Review comments:
diff_hunk: @@ -74,7 +74,6 @@ impl BlobContainerClient {
     /// # Arguments
     ///
     /// * `blob_name` - The name of the blob.
-    /// * `options` - Optional configuration for the client.
path: sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs
user: login: heaths
body: Good clean up, but should there be an options? Do you do this in other languages? I would presume not - just use the same options, but wanted to ask to be sure.
created_at: 2025-05-01T18:22:03Z
author_association: MEMBER
diff_hunk: @@ -6,7 +6,9 @@
 mod blob_client;
 mod blob_container_client;
 mod blob_service_client;
+mod block_blob_client;
 
 pub use blob_client::BlobClient;
 pub use blob_container_client::BlobContainerClient;
 pub use blob_service_client::BlobServiceClient;
+pub use block_blob_client::BlockBlobClient;
path: sdk/storage/azure_storage_blob/src/clients/mod.rs
user: login: heaths
body: You need to export all the client options as well. See #2523. The `clients` module should always export all clients and their options (the ones you want exported at all, that is...obviously not the generated ones you're wrapping).
created_at: 2025-05-01T18:24:39Z
author_association: MEMBER
diff_hunk: @@ -74,7 +74,6 @@ impl BlobContainerClient {
     /// # Arguments
     ///
     /// * `blob_name` - The name of the blob.
-    /// * `options` - Optional configuration for the client.
path: sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs
user: login: vincenttran-msft
body: Good callout here- we should be using the same options (and functionally that is true), this was just a bad copy and paste of a docstring and I forgot to remove the param. I made the same mistake in `blob_service_client.rs` and also corrected it there.
created_at: 2025-05-01T18:29:45Z
author_association: MEMBER
diff_hunk: @@ -6,7 +6,9 @@
 mod blob_client;
 mod blob_container_client;
 mod blob_service_client;
+mod block_blob_client;
 
 pub use blob_client::BlobClient;
 pub use blob_container_client::BlobContainerClient;
 pub use blob_service_client::BlobServiceClient;
+pub use block_blob_client::BlockBlobClient;
path: sdk/storage/azure_storage_blob/src/clients/mod.rs
user: login: vincenttran-msft
body: Hi Heath, I did see that issue and was holding off on doing this until I got some in-flight PRs in (this would be the last one). The next PR will be regenerating against v14 to address the breaking changes, and to address your concerns raised above. Thanks!
created_at: 2025-05-01T18:31:19Z
author_association: MEMBER

PR comments:
user: login: azure-sdk
created_at: 2025-04-29T22:28:48Z
body: ## API Change Check

APIView identified API level changes in this PR and created the following API reviews

[azure_storage_blob](https://spa.apiview.dev/review/a413cb50d0fb4396a776c69b524ccc9e?activeApiRevisionId=8ad69f86b53d4034a1d67c1b942e3717)
<!-- Fetch URI: https://apiview.dev/api/pullrequests?pullRequestNumber=2539&repoName=Azure/azure-sdk-for-rust&commitSHA=11b630c77b6c100449c204e0c0860539a84a6f76 -->
author_association: COLLABORATOR
user: login: azure-sdk
created_at: 2025-04-29T22:41:42Z
body: **API change check**

APIView has identified API level changes in this PR and created following API reviews.

[azure_storage_blob](https://apiview.dev/Assemblies/Review/a413cb50d0fb4396a776c69b524ccc9e?revisionId=8ad69f86b53d4034a1d67c1b942e3717)


author_association: COLLABORATOR
user: login: vincenttran-msft
created_at: 2025-04-29T22:46:23Z
body: This is ready for review in terms of the contents, but currently failing CI on seemingly a new step related to Core.
author_association: MEMBER
user: login: vincenttran-msft
created_at: 2025-05-01T18:15:31Z
body: @heaths Heath Any advice on these failing CI steps? Seems like these are the new steps that were enabled a couple days ago.
author_association: MEMBER
user: login: heaths
created_at: 2025-05-01T18:29:41Z
body: /azp run
author_association: MEMBER
user: login: azure-pipelines[bot]
created_at: 2025-05-01T18:29:48Z
body: <samp>
You have several pipelines (over 10) configured to build pull requests in this repository. Specify which pipelines you would like to run by using /azp run [pipelines] command. You can specify multiple pipelines using a comma separated list.<br>

</samp>
author_association: NONE