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 e68e86c532..7b6362abc2 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -4,17 +4,17 @@ use crate::{ generated::clients::BlobClient as GeneratedBlobClient, generated::models::{ - BlobClientDownloadResult, BlobClientGetPropertiesResult, + BlobClientDownloadResult, BlobClientGetPropertiesResult, BlobClientSetTagsResult, BlobTags, BlockBlobClientCommitBlockListResult, BlockBlobClientStageBlockResult, BlockBlobClientUploadResult, }, models::{AccessTier, BlockList, BlockListType, BlockLookupList}, pipeline::StorageHeadersPolicy, BlobClientDeleteOptions, BlobClientDownloadOptions, BlobClientGetPropertiesOptions, - BlobClientOptions, BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, - BlobClientSetTierOptions, BlockBlobClientCommitBlockListOptions, - BlockBlobClientGetBlockListOptions, BlockBlobClientStageBlockOptions, - BlockBlobClientUploadOptions, + BlobClientGetTagsOptions, BlobClientOptions, BlobClientSetMetadataOptions, + BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, BlobClientSetTierOptions, + BlockBlobClientCommitBlockListOptions, BlockBlobClientGetBlockListOptions, + BlockBlobClientStageBlockOptions, BlockBlobClientUploadOptions, }; use azure_core::{ credentials::TokenCredential, @@ -250,4 +250,30 @@ impl BlobClient { ) -> Result> { self.client.set_tier(tier, 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 + /// + /// * `options` - Optional configuration for the request. + pub async fn set_tags( + &self, + tags: RequestContent, + options: Option>, + ) -> Result> { + self.client.set_tags(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 + } } diff --git a/sdk/storage/azure_storage_blob/src/lib.rs b/sdk/storage/azure_storage_blob/src/lib.rs index 75460580b8..517777b0e1 100644 --- a/sdk/storage/azure_storage_blob/src/lib.rs +++ b/sdk/storage/azure_storage_blob/src/lib.rs @@ -17,22 +17,22 @@ pub use crate::generated::clients::{ }; pub use crate::generated::models::{ BlobClientDeleteOptions, BlobClientDownloadOptions, BlobClientGetPropertiesOptions, - BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlobClientSetTierOptions, - BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions, - BlobContainerClientGetPropertiesOptions, BlobContainerClientSetMetadataOptions, - BlobServiceClientGetPropertiesOptions, BlockBlobClientCommitBlockListOptions, - BlockBlobClientGetBlockListOptions, BlockBlobClientStageBlockOptions, - BlockBlobClientUploadOptions, + BlobClientGetTagsOptions, BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, + BlobClientSetTagsOptions, BlobClientSetTierOptions, BlobContainerClientCreateOptions, + BlobContainerClientDeleteOptions, BlobContainerClientGetPropertiesOptions, + BlobContainerClientSetMetadataOptions, BlobServiceClientGetPropertiesOptions, + BlockBlobClientCommitBlockListOptions, BlockBlobClientGetBlockListOptions, + BlockBlobClientStageBlockOptions, BlockBlobClientUploadOptions, }; pub mod models { pub use crate::generated::models::{ AccessTier, ArchiveStatus, BlobClientDownloadResult, BlobClientDownloadResultHeaders, BlobClientGetPropertiesResult, BlobClientGetPropertiesResultHeaders, - BlobContainerClientGetPropertiesResult, BlobContainerClientGetPropertiesResultHeaders, - BlobImmutabilityPolicyMode, BlobType, BlockBlobClientCommitBlockListResult, - BlockBlobClientStageBlockResult, BlockBlobClientUploadResult, BlockList, BlockListType, - BlockLookupList, CopyStatus, LeaseState, LeaseStatus, PublicAccessType, RehydratePriority, - StorageServiceProperties, + BlobClientSetTagsResultHeaders, BlobContainerClientGetPropertiesResult, + BlobContainerClientGetPropertiesResultHeaders, BlobImmutabilityPolicyMode, BlobTag, + BlobTags, BlobType, BlockBlobClientCommitBlockListResult, BlockBlobClientStageBlockResult, + BlockBlobClientUploadResult, BlockList, BlockListType, BlockLookupList, CopyStatus, + LeaseState, LeaseStatus, PublicAccessType, RehydratePriority, StorageServiceProperties, }; } diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index 284d990cb6..e4db9bbdd0 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -2,18 +2,21 @@ // Licensed under the MIT License. use azure_core::{ - http::{RequestContent, StatusCode}, + http::{response, Request, RequestContent, StatusCode}, Bytes, }; use azure_core_test::{recorded, TestContext}; use azure_storage_blob::{ models::{ - AccessTier, BlobClientDownloadResultHeaders, BlobClientGetPropertiesResultHeaders, - BlockListType, BlockLookupList, LeaseState, + AccessTier, BlobClientDownloadResultHeaders, BlobClientGetPropertiesResultHeaders, BlobTag, + BlobTags, BlockListType, BlockLookupList, LeaseState, }, - BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlockBlobClientUploadOptions, + BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, + BlockBlobClientUploadOptions, +}; +use azure_storage_blob_test::{ + create_test_blob, get_blob_name, get_container_client, test_blob_tag_equality, }; -use azure_storage_blob_test::{create_test_blob, get_blob_name, get_container_client}; use std::{collections::HashMap, error::Error}; #[recorded::test] @@ -426,3 +429,49 @@ async fn test_set_access_tier(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_tag_1 = BlobTag { + key: Some("hello".to_string()), + value: Some("world".to_string()), + }; + let blob_tag_2 = BlobTag { + key: Some("ferris".to_string()), + value: Some("crab".to_string()), + }; + let blob_tags = BlobTags { + blob_tag_set: vec![blob_tag_1, blob_tag_2], + }; + blob_client + .set_tags(RequestContent::try_from(blob_tags.clone())?, None) + .await?; + + // Assert + let response_tags = blob_client.get_tags(None).await?.into_body().await?; + assert!(test_blob_tag_equality(blob_tags, response_tags)); + + // Set Tags with No Tags (Clear Tags) + blob_client + .set_tags( + RequestContent::try_from(BlobTags { + blob_tag_set: vec![], + })?, + None, + ) + .await?; + + // Assert + let response_tags = blob_client.get_tags(None).await?.into_body().await?; + assert!(response_tags.blob_tag_set.is_empty()); + + 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 7338c7c57b..ff843d76aa 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::{BlobTag, BlobTags, BlockBlobClientUploadResult}, + BlobClient, BlobContainerClient, BlobContainerClientOptions, BlobServiceClient, + BlobServiceClientOptions, }; +use std::collections::HashMap; /// Takes in a Recording instance and returns an instrumented options bag and endpoint. /// @@ -102,3 +104,22 @@ pub async fn create_test_blob( ) .await } + +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 { + 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 { + // 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() +}