diff --git a/Cargo.lock b/Cargo.lock index 9481ceea0b..810a4cb67e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -493,6 +493,7 @@ dependencies = [ "futures", "serde", "serde_json", + "time", "tokio", "tracing", "typespec_client_core", diff --git a/sdk/cosmos/azure_data_cosmos_native/include/cosmosclient.h b/sdk/cosmos/azure_data_cosmos_native/include/cosmosclient.h new file mode 100644 index 0000000000..4cae7dd027 --- /dev/null +++ b/sdk/cosmos/azure_data_cosmos_native/include/cosmosclient.h @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// This file is auto-generated by cbindgen. Do not edit manually. +// cSpell: disable +// Build identifier: $Id: azure_data_cosmos_native, Version: 0.27.0, Commit: unknown, Branch: unknown, Build ID: unknown, Build Number: unknown, Timestamp: 1761095869$ + + +#include +#include +#include +#include + +// Specifies the version of cosmosclient this header file was generated from. +// This should match the version of libcosmosclient you are referencing. +#define COSMOSCLIENT_H_VERSION "0.27.0" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Returns a constant C string containing the version of the Cosmos Client library. + */ +const char *cosmosclient_version(void); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/sdk/storage/.dict.txt b/sdk/storage/.dict.txt index 0bcd38e1b8..b889bb7c94 100644 --- a/sdk/storage/.dict.txt +++ b/sdk/storage/.dict.txt @@ -19,11 +19,13 @@ RAGRS restype ruleid secondtag +subsecond testblob1 testblob2 testblob3 testblob4 testcontainer +testid uncommittedblobs westus yourtagname diff --git a/sdk/storage/azure_storage_blob/Cargo.toml b/sdk/storage/azure_storage_blob/Cargo.toml index ce379e9bea..26b15d20a1 100644 --- a/sdk/storage/azure_storage_blob/Cargo.toml +++ b/sdk/storage/azure_storage_blob/Cargo.toml @@ -21,6 +21,7 @@ async-trait.workspace = true azure_core = { workspace = true, features = ["xml"] } serde.workspace = true serde_json.workspace = true +time.workspace = true typespec_client_core = { workspace = true, features = ["derive"] } url.workspace = true uuid.workspace = true diff --git a/sdk/storage/azure_storage_blob/assets.json b/sdk/storage/azure_storage_blob/assets.json index bad8863f67..07da66676b 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_094782fa40", + "Tag": "rust/azure_storage_blob_c8c2bbe44d", "TagPrefix": "rust/azure_storage_blob" } \ No newline at end of file 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 2853763a0c..d10d4c2c4e 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 @@ -2,21 +2,24 @@ // Licensed under the MIT License. use crate::{ - generated::clients::BlobContainerClient as GeneratedBlobContainerClient, - generated::models::{ - BlobContainerClientAcquireLeaseResult, BlobContainerClientBreakLeaseResult, - BlobContainerClientChangeLeaseResult, BlobContainerClientGetAccountInfoResult, - BlobContainerClientGetPropertiesResult, BlobContainerClientReleaseLeaseResult, - BlobContainerClientRenewLeaseResult, + generated::{ + clients::BlobContainerClient as GeneratedBlobContainerClient, + models::{ + BlobContainerClientAcquireLeaseResult, BlobContainerClientBreakLeaseResult, + BlobContainerClientChangeLeaseResult, BlobContainerClientGetAccountInfoResult, + BlobContainerClientGetPropertiesResult, BlobContainerClientReleaseLeaseResult, + BlobContainerClientRenewLeaseResult, SignedIdentifier, + }, }, models::{ - BlobContainerClientAcquireLeaseOptions, BlobContainerClientBreakLeaseOptions, - BlobContainerClientChangeLeaseOptions, BlobContainerClientCreateOptions, - BlobContainerClientDeleteOptions, BlobContainerClientFindBlobsByTagsOptions, - BlobContainerClientGetAccountInfoOptions, BlobContainerClientGetPropertiesOptions, - BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions, - BlobContainerClientRenewLeaseOptions, BlobContainerClientSetMetadataOptions, - FilterBlobSegment, ListBlobsFlatSegmentResponse, + format_signed_identifiers, BlobContainerClientAcquireLeaseOptions, + BlobContainerClientBreakLeaseOptions, BlobContainerClientChangeLeaseOptions, + BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions, + BlobContainerClientGetAccessPolicyOptions, BlobContainerClientGetAccountInfoOptions, + BlobContainerClientGetPropertiesOptions, BlobContainerClientListBlobFlatSegmentOptions, + BlobContainerClientReleaseLeaseOptions, BlobContainerClientRenewLeaseOptions, + BlobContainerClientSetAccessPolicyOptions, BlobContainerClientSetAccessPolicyResult, + BlobContainerClientSetMetadataOptions, FilterBlobSegment, ListBlobsFlatSegmentResponse, }, pipeline::StorageHeadersPolicy, BlobClient, BlobContainerClientOptions, @@ -25,7 +28,7 @@ use azure_core::{ credentials::TokenCredential, http::{ policies::{BearerTokenCredentialPolicy, Policy}, - NoFormat, PageIterator, Pager, Response, Url, XmlFormat, + NoFormat, PageIterator, Pager, RequestContent, Response, Url, XmlFormat, }, Result, }; @@ -160,31 +163,6 @@ impl BlobContainerClient { self.client.list_blob_flat_segment(options) } - /// Returns a list of blobs in the container whose tags match a given search expression. - /// - /// # Arguments - /// - /// * `filter_expression` - The expression to find blobs whose tags matches the specified condition. - /// eg. - /// ```text - /// "\"yourtagname\"='firsttag' and \"yourtagname2\"='secondtag'" - /// ``` - /// To specify a container, eg. - /// ```text - /// "@container='containerName' and \"Name\"='C'" - /// ``` - /// See [`format_filter_expression()`](crate::format_filter_expression) for help with the expected String format. - /// * `options` - Optional parameters for the request. - pub async fn find_blobs_by_tags( - &self, - filter_expression: &str, - options: Option>, - ) -> Result> { - self.client - .find_blobs_by_tags(filter_expression, options) - .await - } - /// Requests a new lease on a container. The lease lock duration can be 15 to 60 seconds, or can be infinite. /// /// # Arguments @@ -275,4 +253,34 @@ impl BlobContainerClient { ) -> Result> { self.client.get_account_info(options).await } + + /// Sets the permissions for the specified container. The permissions indicate whether blobs in a + /// container may be accessed publicly. + /// + /// # Arguments + /// + /// * `container_acl` - The access control list for the container. + /// * `options` - Optional configuration for the request. + pub async fn set_access_policy( + &self, + container_acl: Vec, + options: Option>, + ) -> Result> { + self.client + .set_access_policy(format_signed_identifiers(container_acl)?, options) + .await + } + + /// Gets the permissions for the specified container. The permissions indicate whether container data + /// may be accessed publicly. + /// + /// # Arguments + /// + /// * `options` - Optional configuration for the request. + pub async fn get_access_policy( + &self, + options: Option>, + ) -> Result, XmlFormat>> { + self.client.get_access_policy(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 08648d7ca5..f5f9d96ac0 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,10 +5,9 @@ use crate::{ generated::clients::BlobServiceClient as GeneratedBlobServiceClient, generated::models::BlobServiceClientGetAccountInfoResult, models::{ - BlobServiceClientFindBlobsByTagsOptions, BlobServiceClientGetAccountInfoOptions, - BlobServiceClientGetPropertiesOptions, BlobServiceClientListContainersSegmentOptions, - BlobServiceClientSetPropertiesOptions, BlobServiceProperties, FilterBlobSegment, - ListContainersSegmentResponse, + BlobServiceClientGetAccountInfoOptions, BlobServiceClientGetPropertiesOptions, + BlobServiceClientListContainersSegmentOptions, BlobServiceClientSetPropertiesOptions, + BlobServiceProperties, FilterBlobSegment, ListContainersSegmentResponse, }, pipeline::StorageHeadersPolicy, BlobContainerClient, BlobServiceClientOptions, @@ -99,31 +98,6 @@ impl BlobServiceClient { self.client.list_containers_segment(options) } - /// Returns a list of blobs across all containers whose tags match a given search expression. - /// - /// # Arguments - /// - /// * `filter_expression` - The expression to find blobs whose tags matches the specified condition. - /// eg. - /// ```text - /// "\"yourtagname\"='firsttag' and \"yourtagname2\"='secondtag'" - /// ``` - /// To specify a container, eg. - /// ```text - /// "@container='containerName' and \"Name\"='C'" - /// ``` - /// See [`format_filter_expression()`](crate::format_filter_expression) for help with the expected String format. - /// * `options` - Optional parameters for the request. - pub async fn find_blobs_by_tags( - &self, - filter_expression: &str, - options: Option>, - ) -> Result> { - self.client - .find_blobs_by_tags(filter_expression, options) - .await - } - /// Sets properties for a Storage account's Blob service endpoint, including properties for Storage Analytics and CORS rules. /// /// # Arguments diff --git a/sdk/storage/azure_storage_blob/src/generated/clients/blob_container_client.rs b/sdk/storage/azure_storage_blob/src/generated/clients/blob_container_client.rs index de82cd9747..6dc9190164 100644 --- a/sdk/storage/azure_storage_blob/src/generated/clients/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/src/generated/clients/blob_container_client.rs @@ -10,7 +10,7 @@ use crate::generated::{ BlobContainerClientBreakLeaseOptions, BlobContainerClientBreakLeaseResult, BlobContainerClientChangeLeaseOptions, BlobContainerClientChangeLeaseResult, BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions, - BlobContainerClientFindBlobsByTagsOptions, BlobContainerClientGetAccessPolicyOptions, + BlobContainerClientFilterBlobsOptions, BlobContainerClientGetAccessPolicyOptions, BlobContainerClientGetAccountInfoOptions, BlobContainerClientGetAccountInfoResult, BlobContainerClientGetPropertiesOptions, BlobContainerClientGetPropertiesResult, BlobContainerClientListBlobFlatSegmentOptions, @@ -429,13 +429,34 @@ impl BlobContainerClient { /// /// # Arguments /// - /// * `filter_expression` - Filters the results to return only to return only blobs whose tags match the specified expression. /// * `options` - Optional parameters for the request. - #[tracing::function("Storage.Blob.Container.findBlobsByTags")] - pub async fn find_blobs_by_tags( + /// + /// ## Response Headers + /// + /// The returned [`Response`](azure_core::http::Response) implements the [`FilterBlobSegmentHeaders`] trait, which provides + /// access to response headers. For example: + /// + /// ```no_run + /// use azure_core::{Result, http::{Response, XmlFormat}}; + /// use azure_storage_blob::models::{FilterBlobSegment, FilterBlobSegmentHeaders}; + /// async fn example() -> Result<()> { + /// let response: Response = unimplemented!(); + /// // Access response headers + /// if let Some(date) = response.date()? { + /// println!("Date: {:?}", date); + /// } + /// Ok(()) + /// } + /// ``` + /// + /// ### Available headers + /// * [`date`()](crate::generated::models::FilterBlobSegmentHeaders::date) - Date + /// + /// [`FilterBlobSegmentHeaders`]: crate::generated::models::FilterBlobSegmentHeaders + #[tracing::function("Storage.Blob.Container.filterBlobs")] + pub async fn filter_blobs( &self, - filter_expression: &str, - options: Option>, + options: Option>, ) -> Result> { let options = options.unwrap_or_default(); let ctx = options.method_options.context.to_borrowed(); @@ -465,8 +486,9 @@ impl BlobContainerClient { url.query_pairs_mut() .append_pair("timeout", &timeout.to_string()); } - url.query_pairs_mut() - .append_pair("where", filter_expression); + if let Some(where_param) = options.where_param { + url.query_pairs_mut().append_pair("where", &where_param); + } let mut request = Request::new(url, Method::Get); request.insert_header("accept", "application/xml"); request.insert_header("content-type", "application/xml"); diff --git a/sdk/storage/azure_storage_blob/src/generated/clients/blob_service_client.rs b/sdk/storage/azure_storage_blob/src/generated/clients/blob_service_client.rs index 23d38f45a1..48048ba6db 100644 --- a/sdk/storage/azure_storage_blob/src/generated/clients/blob_service_client.rs +++ b/sdk/storage/azure_storage_blob/src/generated/clients/blob_service_client.rs @@ -6,7 +6,7 @@ use crate::generated::{ clients::BlobContainerClient, models::{ - BlobServiceClientFindBlobsByTagsOptions, BlobServiceClientGetAccountInfoOptions, + BlobServiceClientFilterBlobsOptions, BlobServiceClientGetAccountInfoOptions, BlobServiceClientGetAccountInfoResult, BlobServiceClientGetPropertiesOptions, BlobServiceClientGetStatisticsOptions, BlobServiceClientGetUserDelegationKeyOptions, BlobServiceClientListContainersSegmentOptions, BlobServiceClientSetPropertiesOptions, @@ -94,13 +94,34 @@ impl BlobServiceClient { /// /// # Arguments /// - /// * `filter_expression` - Filters the results to return only to return only blobs whose tags match the specified expression. /// * `options` - Optional parameters for the request. - #[tracing::function("Storage.Blob.findBlobsByTags")] - pub async fn find_blobs_by_tags( + /// + /// ## Response Headers + /// + /// The returned [`Response`](azure_core::http::Response) implements the [`FilterBlobSegmentHeaders`] trait, which provides + /// access to response headers. For example: + /// + /// ```no_run + /// use azure_core::{Result, http::{Response, XmlFormat}}; + /// use azure_storage_blob::models::{FilterBlobSegment, FilterBlobSegmentHeaders}; + /// async fn example() -> Result<()> { + /// let response: Response = unimplemented!(); + /// // Access response headers + /// if let Some(date) = response.date()? { + /// println!("Date: {:?}", date); + /// } + /// Ok(()) + /// } + /// ``` + /// + /// ### Available headers + /// * [`date`()](crate::generated::models::FilterBlobSegmentHeaders::date) - Date + /// + /// [`FilterBlobSegmentHeaders`]: crate::generated::models::FilterBlobSegmentHeaders + #[tracing::function("Storage.Blob.filterBlobs")] + pub async fn filter_blobs( &self, - filter_expression: &str, - options: Option>, + options: Option>, ) -> Result> { let options = options.unwrap_or_default(); let ctx = options.method_options.context.to_borrowed(); @@ -127,8 +148,9 @@ impl BlobServiceClient { url.query_pairs_mut() .append_pair("timeout", &timeout.to_string()); } - url.query_pairs_mut() - .append_pair("where", filter_expression); + if let Some(where_param) = options.where_param { + url.query_pairs_mut().append_pair("where", &where_param); + } let mut request = Request::new(url, Method::Get); request.insert_header("accept", "application/xml"); request.insert_header("content-type", "application/xml"); 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 4f68f79aae..438b88c15d 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 @@ -21,7 +21,7 @@ use super::{ BlockBlobClientCommitBlockListResult, BlockBlobClientQueryResult, BlockBlobClientStageBlockFromUrlResult, BlockBlobClientStageBlockResult, BlockBlobClientUploadBlobFromUrlResult, BlockBlobClientUploadResult, BlockList, CopyStatus, - LeaseDuration, LeaseState, LeaseStatus, ListBlobsFlatSegmentResponse, + FilterBlobSegment, LeaseDuration, LeaseState, LeaseStatus, ListBlobsFlatSegmentResponse, ListBlobsHierarchySegmentResponse, PageBlobClientClearPagesResult, PageBlobClientCopyIncrementalResult, PageBlobClientCreateResult, PageBlobClientResizeResult, PageBlobClientSetSequenceNumberResult, PageBlobClientUploadPagesFromUrlResult, @@ -3006,6 +3006,20 @@ impl BlockListHeaders for Response { } } +/// Provides access to typed response headers for the following methods: +/// * `BlobContainerClient::filter_blobs()` +/// * `BlobServiceClient::filter_blobs()` +pub trait FilterBlobSegmentHeaders: private::Sealed { + fn date(&self) -> Result>; +} + +impl FilterBlobSegmentHeaders 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 `BlobContainerClient::list_blob_flat_segment()` /// /// # Examples @@ -3685,7 +3699,7 @@ mod private { BlobServiceClientGetAccountInfoResult, BlobTags, BlockBlobClientCommitBlockListResult, BlockBlobClientQueryResult, BlockBlobClientStageBlockFromUrlResult, BlockBlobClientStageBlockResult, BlockBlobClientUploadBlobFromUrlResult, - BlockBlobClientUploadResult, BlockList, ListBlobsFlatSegmentResponse, + BlockBlobClientUploadResult, BlockList, FilterBlobSegment, ListBlobsFlatSegmentResponse, ListBlobsHierarchySegmentResponse, PageBlobClientClearPagesResult, PageBlobClientCopyIncrementalResult, PageBlobClientCreateResult, PageBlobClientResizeResult, PageBlobClientSetSequenceNumberResult, @@ -3736,6 +3750,7 @@ 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/method_options.rs b/sdk/storage/azure_storage_blob/src/generated/models/method_options.rs index ff71846b0c..aa0b2ca494 100644 --- a/sdk/storage/azure_storage_blob/src/generated/models/method_options.rs +++ b/sdk/storage/azure_storage_blob/src/generated/models/method_options.rs @@ -1245,9 +1245,9 @@ pub struct BlobContainerClientDeleteOptions<'a> { pub timeout: Option, } -/// Options to be passed to `BlobContainerClient::find_blobs_by_tags()` +/// Options to be passed to `BlobContainerClient::filter_blobs()` #[derive(Clone, Default, SafeDebug)] -pub struct BlobContainerClientFindBlobsByTagsOptions<'a> { +pub struct BlobContainerClientFilterBlobsOptions<'a> { /// An opaque, globally-unique, client-generated string identifier for the request. pub client_request_id: Option, @@ -1269,6 +1269,9 @@ pub struct BlobContainerClientFindBlobsByTagsOptions<'a> { /// The timeout parameter is expressed in seconds. For more information, see [Setting Timeouts for Blob Service Operations.](https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations) pub timeout: Option, + + /// Filters the results to return only to return only blobs whose tags match the specified expression. + pub where_param: Option, } /// Options to be passed to `BlobContainerClient::get_access_policy()` @@ -1523,9 +1526,9 @@ pub struct BlobContainerClientSetMetadataOptions<'a> { pub timeout: Option, } -/// Options to be passed to `BlobServiceClient::find_blobs_by_tags()` +/// Options to be passed to `BlobServiceClient::filter_blobs()` #[derive(Clone, Default, SafeDebug)] -pub struct BlobServiceClientFindBlobsByTagsOptions<'a> { +pub struct BlobServiceClientFilterBlobsOptions<'a> { /// An opaque, globally-unique, client-generated string identifier for the request. pub client_request_id: Option, @@ -1547,6 +1550,9 @@ pub struct BlobServiceClientFindBlobsByTagsOptions<'a> { /// The timeout parameter is expressed in seconds. For more information, see [Setting Timeouts for Blob Service Operations.](https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/setting-timeouts-for-blob-service-operations) pub timeout: Option, + + /// Filters the results to return only to return only blobs whose tags match the specified expression. + pub where_param: Option, } /// Options to be passed to `BlobServiceClient::get_account_info()` 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 feba35cbf9..bf3dd8cb25 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 @@ -26,26 +26,16 @@ use std::collections::HashMap; #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] pub struct AccessPolicy { /// The date-time the policy expires. - #[serde( - default, - rename = "Expiry", - skip_serializing_if = "Option::is_none", - with = "azure_core::time::rfc7231::option" - )] - pub expiry: Option, + #[serde(rename = "Expiry", skip_serializing_if = "Option::is_none")] + pub expiry: Option, /// The permissions for acl the policy. #[serde(rename = "Permission", skip_serializing_if = "Option::is_none")] pub permission: Option, /// The date-time the policy is active. - #[serde( - default, - rename = "Start", - skip_serializing_if = "Option::is_none", - with = "azure_core::time::rfc7231::option" - )] - pub start: Option, + #[serde(rename = "Start", skip_serializing_if = "Option::is_none")] + pub start: Option, } /// Contains results for `AppendBlobClient::append_block_from_url()` diff --git a/sdk/storage/azure_storage_blob/src/models/extensions.rs b/sdk/storage/azure_storage_blob/src/models/extensions.rs index 0aec332293..9477c77d7a 100644 --- a/sdk/storage/azure_storage_blob/src/models/extensions.rs +++ b/sdk/storage/azure_storage_blob/src/models/extensions.rs @@ -1,9 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use azure_core::{ + http::{RequestContent, XmlFormat}, + xml::to_xml_with_root, +}; +use serde::Serialize; + use crate::models::{ AppendBlobClientCreateOptions, BlobTag, BlobTags, BlockBlobClientUploadBlobFromUrlOptions, - BlockBlobClientUploadOptions, PageBlobClientCreateOptions, + BlockBlobClientUploadOptions, PageBlobClientCreateOptions, SignedIdentifier, }; use azure_core::error::ErrorKind; use std::collections::HashMap; @@ -109,3 +115,18 @@ impl From> for BlobTags { } } } + +// SignedIdentifiers wrapper for correct XML serialization. +#[derive(Serialize)] +struct SignedIdentifiersWrapper { + #[serde(rename = "SignedIdentifier")] + items: Vec, +} + +// Converts a `Vec` into `RequestContent, XmlFormat>`. +pub(crate) fn format_signed_identifiers( + value: Vec, +) -> Result, XmlFormat>, azure_core::Error> { + let wrapper = SignedIdentifiersWrapper { items: value }; + Ok(to_xml_with_root("SignedIdentifiers", &wrapper)?.into()) +} diff --git a/sdk/storage/azure_storage_blob/src/models/mod.rs b/sdk/storage/azure_storage_blob/src/models/mod.rs index c9090c6c09..c4419bca34 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, AccountKind, AppendBlobClientAppendBlockFromUrlOptions, + AccessPolicy, AccessTier, AccountKind, AppendBlobClientAppendBlockFromUrlOptions, AppendBlobClientAppendBlockFromUrlResult, AppendBlobClientAppendBlockFromUrlResultHeaders, AppendBlobClientAppendBlockOptions, AppendBlobClientAppendBlockResult, AppendBlobClientAppendBlockResultHeaders, AppendBlobClientCreateOptions, @@ -36,7 +36,7 @@ pub use crate::generated::models::{ BlobContainerClientBreakLeaseResultHeaders, BlobContainerClientChangeLeaseOptions, BlobContainerClientChangeLeaseResult, BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions, - BlobContainerClientFindBlobsByTagsOptions, BlobContainerClientGetAccountInfoOptions, + BlobContainerClientGetAccessPolicyOptions, BlobContainerClientGetAccountInfoOptions, BlobContainerClientGetAccountInfoResult, BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesOptions, BlobContainerClientGetPropertiesResult, BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, @@ -45,32 +45,32 @@ pub use crate::generated::models::{ BlobContainerClientRenameResultHeaders, BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, BlobContainerClientRenewLeaseResultHeaders, BlobContainerClientRestoreResult, BlobContainerClientRestoreResultHeaders, - BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetAccessPolicyResultHeaders, - BlobContainerClientSetMetadataOptions, BlobCopySourceTags, BlobDeleteType, BlobFlatListSegment, - BlobImmutabilityPolicyMode, BlobItemInternal, BlobMetadata, BlobName, BlobPropertiesInternal, - BlobServiceClientFindBlobsByTagsOptions, BlobServiceClientGetAccountInfoOptions, - BlobServiceClientGetAccountInfoResult, BlobServiceClientGetAccountInfoResultHeaders, - BlobServiceClientGetPropertiesOptions, BlobServiceClientListContainersSegmentOptions, - BlobServiceClientSetPropertiesOptions, BlobServiceProperties, BlobTag, BlobTags, - BlobTagsHeaders, BlobType, Block, BlockBlobClientCommitBlockListOptions, - BlockBlobClientCommitBlockListResult, BlockBlobClientCommitBlockListResultHeaders, - BlockBlobClientGetBlockListOptions, BlockBlobClientQueryResult, - BlockBlobClientQueryResultHeaders, BlockBlobClientStageBlockFromUrlResult, - BlockBlobClientStageBlockFromUrlResultHeaders, BlockBlobClientStageBlockOptions, - BlockBlobClientStageBlockResult, BlockBlobClientStageBlockResultHeaders, - BlockBlobClientUploadBlobFromUrlOptions, BlockBlobClientUploadBlobFromUrlResult, - BlockBlobClientUploadBlobFromUrlResultHeaders, BlockBlobClientUploadOptions, - BlockBlobClientUploadResult, BlockBlobClientUploadResultHeaders, BlockList, BlockListHeaders, - BlockListType, BlockLookupList, ContainerItem, CopyStatus, CorsRule, DeleteSnapshotsOptionType, - EncryptionAlgorithmType, FileShareTokenIntent, FilterBlobItem, FilterBlobSegment, - ImmutabilityPolicyMode, LeaseDuration, LeaseState, LeaseStatus, ListBlobsFlatSegmentResponse, - ListBlobsFlatSegmentResponseHeaders, ListBlobsHierarchySegmentResponse, - ListBlobsHierarchySegmentResponseHeaders, ListBlobsIncludeItem, ListContainersIncludeType, - ListContainersSegmentResponse, Logging, Metrics, ObjectReplicationMetadata, - PageBlobClientClearPagesOptions, PageBlobClientClearPagesResult, - PageBlobClientClearPagesResultHeaders, PageBlobClientCopyIncrementalResult, - PageBlobClientCopyIncrementalResultHeaders, PageBlobClientCreateOptions, - PageBlobClientCreateResult, PageBlobClientCreateResultHeaders, + BlobContainerClientSetAccessPolicyOptions, BlobContainerClientSetAccessPolicyResult, + BlobContainerClientSetAccessPolicyResultHeaders, BlobContainerClientSetMetadataOptions, + BlobCopySourceTags, BlobDeleteType, BlobFlatListSegment, BlobImmutabilityPolicyMode, + BlobItemInternal, BlobMetadata, BlobName, BlobPropertiesInternal, + BlobServiceClientGetAccountInfoOptions, BlobServiceClientGetAccountInfoResult, + BlobServiceClientGetAccountInfoResultHeaders, BlobServiceClientGetPropertiesOptions, + BlobServiceClientListContainersSegmentOptions, BlobServiceClientSetPropertiesOptions, + BlobServiceProperties, BlobTag, BlobTags, BlobTagsHeaders, BlobType, Block, + BlockBlobClientCommitBlockListOptions, BlockBlobClientCommitBlockListResult, + BlockBlobClientCommitBlockListResultHeaders, BlockBlobClientGetBlockListOptions, + BlockBlobClientQueryResult, BlockBlobClientQueryResultHeaders, + BlockBlobClientStageBlockFromUrlResult, BlockBlobClientStageBlockFromUrlResultHeaders, + BlockBlobClientStageBlockOptions, BlockBlobClientStageBlockResult, + BlockBlobClientStageBlockResultHeaders, BlockBlobClientUploadBlobFromUrlOptions, + BlockBlobClientUploadBlobFromUrlResult, BlockBlobClientUploadBlobFromUrlResultHeaders, + BlockBlobClientUploadOptions, BlockBlobClientUploadResult, BlockBlobClientUploadResultHeaders, + BlockList, BlockListHeaders, BlockListType, BlockLookupList, ContainerItem, CopyStatus, + CorsRule, DeleteSnapshotsOptionType, EncryptionAlgorithmType, FileShareTokenIntent, + FilterBlobItem, FilterBlobSegment, ImmutabilityPolicyMode, LeaseDuration, LeaseState, + LeaseStatus, ListBlobsFlatSegmentResponse, ListBlobsFlatSegmentResponseHeaders, + ListBlobsHierarchySegmentResponse, ListBlobsHierarchySegmentResponseHeaders, + ListBlobsIncludeItem, ListContainersIncludeType, ListContainersSegmentResponse, Logging, + Metrics, ObjectReplicationMetadata, PageBlobClientClearPagesOptions, + PageBlobClientClearPagesResult, PageBlobClientClearPagesResultHeaders, + PageBlobClientCopyIncrementalResult, PageBlobClientCopyIncrementalResultHeaders, + PageBlobClientCreateOptions, PageBlobClientCreateResult, PageBlobClientCreateResultHeaders, PageBlobClientGetPageRangesOptions, PageBlobClientResizeOptions, PageBlobClientResizeResult, PageBlobClientResizeResultHeaders, PageBlobClientSetSequenceNumberOptions, PageBlobClientSetSequenceNumberResult, PageBlobClientSetSequenceNumberResultHeaders, diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index 9bfed276b1..9dcd9d4a41 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -1,9 +1,20 @@ // 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}; +use azure_core::time::{parse_rfc3339, OffsetDateTime}; +use std::{ + collections::HashMap, + io::{Error, ErrorKind}, +}; +use time::{ + format_description::{well_known::Rfc3339, FormatItem}, + macros::format_description, + UtcOffset, +}; + +// Compile-time format description: RFC3339 with exactly 7 fractional digits and a Z suffix. +static RFC3339_7: &[FormatItem<'_>] = + format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:7]Z"); /// Takes in an offset and a length, verifies alignment to a 512-byte boundary, and /// returns the HTTP range in String format. @@ -60,3 +71,83 @@ pub fn format_filter_expression(tags: &HashMap) -> Result Result { + let utc = datetime.to_offset(UtcOffset::UTC); + utc.format(&RFC3339_7) + .map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string())) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_format_datetime_with_7_decimals() -> Result<(), Error> { + // Test with microsecond precision (6 digits) - should pad to 7 + let dt = parse_rfc3339("2025-09-22T19:20:10.622383Z").unwrap(); + let formatted = format_datetime(dt)?; + assert_eq!(formatted, "2025-09-22T19:20:10.6223830Z"); + + // Test with nanosecond precision (9 digits) - should truncate to 7 + let dt = parse_rfc3339("2025-09-22T19:20:00.622429456Z").unwrap(); + let formatted = format_datetime(dt)?; + assert_eq!(formatted, "2025-09-22T19:20:00.6224294Z"); + + // Test with no fractional seconds - should pad with zeros + let dt = parse_rfc3339("2025-09-22T19:20:00Z").unwrap(); + let formatted = format_datetime(dt)?; + assert_eq!(formatted, "2025-09-22T19:20:00.0000000Z"); + + // Test with millisecond precision (3 digits) - should pad to 7 + let dt = parse_rfc3339("2025-09-22T19:20:00.123Z").unwrap(); + let formatted = format_datetime(dt)?; + assert_eq!(formatted, "2025-09-22T19:20:00.1230000Z"); + + Ok(()) + } + + #[test] + fn test_format_datetime_no_fractional_seconds() -> Result<(), Error> { + // Test with no fractional seconds in UTC - exercises the else branch + let dt = parse_rfc3339("2025-09-22T19:20:00Z").unwrap(); + let formatted = format_datetime(dt)?; + assert_eq!(formatted, "2025-09-22T19:20:00.0000000Z"); + + // Test with no fractional seconds and offset - should convert to UTC + let dt = parse_rfc3339("2025-09-22T19:20:00-05:00").unwrap(); + let formatted = format_datetime(dt)?; + assert_eq!(formatted, "2025-09-23T00:20:00.0000000Z"); + + // Test with no fractional seconds and positive offset - should convert to UTC + let dt = parse_rfc3339("2025-09-22T19:20:00+03:30").unwrap(); + let formatted = format_datetime(dt)?; + assert_eq!(formatted, "2025-09-22T15:50:00.0000000Z"); + + Ok(()) + } + + #[test] + fn test_format_datetime_edge_cases() -> Result<(), Error> { + // Test with exactly 7 fractional digits - should not truncate or pad + let dt = parse_rfc3339("2025-09-22T19:20:00.1234567Z").unwrap(); + let formatted = format_datetime(dt)?; + assert_eq!(formatted, "2025-09-22T19:20:00.1234567Z"); + + // Test with single fractional digit - should pad to 7 + let dt = parse_rfc3339("2025-09-22T19:20:00.1Z").unwrap(); + let formatted = format_datetime(dt)?; + assert_eq!(formatted, "2025-09-22T19:20:00.1000000Z"); + + // Test with boundary timezone offset + let dt = parse_rfc3339("2025-09-22T19:20:00.123+14:00").unwrap(); + let formatted = format_datetime(dt)?; + assert_eq!(formatted, "2025-09-22T05:20:00.1230000Z"); + + 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 1b372ca5e2..3505b1f6f4 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs @@ -1,21 +1,23 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use azure_core::http::{RequestContent, StatusCode}; -use azure_core_test::{recorded, Matcher, TestContext, TestMode}; -use azure_storage_blob::format_filter_expression; +use azure_core::{ + http::StatusCode, + time::{Duration, OffsetDateTime}, +}; +use azure_core_test::{recorded, TestContext}; +use azure_storage_blob::format_datetime; use azure_storage_blob::models::{ - AccountKind, BlobContainerClientAcquireLeaseResultHeaders, + AccessPolicy, AccountKind, BlobContainerClientAcquireLeaseResultHeaders, BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, - BlobContainerClientSetMetadataOptions, BlobType, BlockBlobClientUploadOptions, LeaseState, + BlobContainerClientSetMetadataOptions, BlobType, LeaseState, SignedIdentifier, }; use azure_storage_blob_test::{ - create_test_blob, get_blob_name, get_blob_service_client, get_container_client, - get_container_name, + create_test_blob, get_blob_service_client, get_container_client, get_container_name, }; use futures::{StreamExt, TryStreamExt}; -use std::{collections::HashMap, error::Error, time::Duration}; +use std::{collections::HashMap, error::Error}; use tokio::time; #[recorded::test] @@ -275,7 +277,7 @@ async fn test_container_lease_operations(ctx: TestContext) -> Result<(), Box Result<(), Box> { } #[recorded::test] -async fn test_find_blobs_by_tags_container(ctx: TestContext) -> Result<(), Box> { +async fn test_container_access_policy(ctx: TestContext) -> Result<(), Box> { // Recording Setup + + use azure_core::time::parse_rfc3339; let recording = ctx.recording(); - recording.set_matcher(Matcher::HeaderlessMatcher).await?; - let container_client = get_container_client(recording, true).await?; + let container_client = get_container_client(recording, false).await?; + container_client.create_container(None).await?; - // Create Test Blobs with Tags - let blob1_name = get_blob_name(recording); - create_test_blob( - &container_client.blob_client(blob1_name.clone()), - Some(RequestContent::from("hello world".as_bytes().into())), - Some( - BlockBlobClientUploadOptions::default().with_tags(HashMap::from([ - ("foo".to_string(), "bar".to_string()), - ("alice".to_string(), "bob".to_string()), - ])), + // // Set Access Policy w/ Policy Defined + // let access_policy = AccessPolicy { + // expiry: Some(format_datetime( + // OffsetDateTime::now_utc() + Duration::seconds(10), + // )?), + // permission: Some("rw".to_string()), + // start: Some(format_datetime(OffsetDateTime::now_utc())?), + // }; + // let signed_identifier = SignedIdentifier { + // access_policy: Some(access_policy), + // id: Some("testid".into()), + // }; + + // Set Access Policy w/ Policy Defined + let dt_start = parse_rfc3339("2025-07-22T19:01:10.622383-05:00").unwrap(); + let formatted_start = format_datetime(dt_start)?; + let dt_expiry = parse_rfc3339("2025-12-22T19:01:20.622383-05:00").unwrap(); + let formatted_expiry = format_datetime(dt_expiry)?; + let access_policy = AccessPolicy { + expiry: Some(formatted_expiry), + permission: Some("rw".to_string()), + start: Some(formatted_start), + }; + let signed_identifier = SignedIdentifier { + access_policy: Some(access_policy), + id: Some("testid".into()), + }; + + container_client + .set_access_policy(vec![signed_identifier], None) + .await?; + + container_client.delete_container(None).await?; + Ok(()) +} + +#[recorded::test] +async fn test_container_access_policy_datetime_comprehensive( + ctx: TestContext, +) -> Result<(), Box> { + // Recording Setup - comprehensive test covering all datetime scenarios from parsers + use azure_core::time::parse_rfc3339; + let recording = ctx.recording(); + let container_client = get_container_client(recording, false).await?; + container_client.create_container(None).await?; + + // Comprehensive test cases that match ALL scenarios from the parsers unit tests + let comprehensive_test_cases = [ + // Precision levels (from test_format_datetime_with_7_decimals) + ( + "no-fractional-utc", + "2025-09-22T19:20:00Z", + "2025-09-22T19:20:00.0000000Z", + "r", ), - ) - .await?; - let blob2_name = get_blob_name(recording); - let blob2_tags = HashMap::from([("fizz".to_string(), "buzz".to_string())]); - create_test_blob( - &container_client.blob_client(blob2_name.clone()), - Some(RequestContent::from("ferris the crab".as_bytes().into())), - Some(BlockBlobClientUploadOptions::default().with_tags(blob2_tags.clone())), - ) - .await?; + ( + "single-digit-padding", + "2025-09-22T19:20:00.1Z", + "2025-09-22T19:20:00.1000000Z", + "w", + ), + ( + "millisecond-padding", + "2025-09-22T19:20:00.123Z", + "2025-09-22T19:20:00.1230000Z", + "d", + ), + ( + "microsecond-padding", + "2025-09-22T19:20:10.622383Z", + "2025-09-22T19:20:10.6223830Z", + "l", + ), + ( + "exact-7-digits", + "2025-09-22T19:20:00.1234567Z", + "2025-09-22T19:20:00.1234567Z", + "rw", + ), + ( + "nanosecond-truncation", + "2025-09-22T19:20:00.622429456Z", + "2025-09-22T19:20:00.6224294Z", + "rd", + ), + // Timezone offset conversions (from test_format_datetime_no_fractional_seconds) + ( + "negative-offset-conversion", + "2025-09-22T19:20:00-05:00", + "2025-09-23T00:20:00.0000000Z", + "rwl", + ), + ( + "positive-offset-conversion", + "2025-09-22T19:20:00+03:30", + "2025-09-22T15:50:00.0000000Z", + "wdl", + ), + // Edge cases (from test_format_datetime_edge_cases) + ( + "boundary-timezone-positive", + "2025-09-22T19:20:00.123+14:00", + "2025-09-22T05:20:00.1230000Z", + "rwdl", + ), + ( + "boundary-timezone-negative", + "2019-10-12T00:20:50.52-08:00", + "2019-10-12T08:20:50.5200000Z", + "wd", + ), + // High precision with timezone offsets (from parser edge cases) + ( + "microseconds-positive-offset", + "1999-09-10T03:05:07.3845533+01:00", + "1999-09-10T02:05:07.3845533Z", + "dl", + ), + // UTC variants with different precisions + ( + "utc-basic-fractional", + "2019-10-12T07:20:50.52Z", + "2019-10-12T07:20:50.5200000Z", + "rwd", + ), + // Edge year (RFC 3339 example) + ( + "rfc3339-example-year", + "1985-04-12T23:20:50.52Z", + "1985-04-12T23:20:50.5200000Z", + "rl", + ), + // Equivalent time representations (same UTC moment in different formats) + ( + "equiv-utc", + "2022-08-26T18:38:00Z", + "2022-08-26T18:38:00.0000000Z", + "r", + ), + ( + "equiv-neg-offset", + "2022-08-26T10:38:00-08:00", + "2022-08-26T18:38:00.0000000Z", + "w", + ), + ( + "equiv-pos-offset", + "2022-08-26T20:38:00+02:00", + "2022-08-26T18:38:00.0000000Z", + "d", + ), + ( + "equiv-microseconds", + "2022-08-26T18:38:00.000000Z", + "2022-08-26T18:38:00.0000000Z", + "l", + ), + ]; - // Sleep in live mode to allow tags to be indexed on the service - if recording.test_mode() == TestMode::Live { - time::sleep(Duration::from_secs(5)).await; + for (test_name, input_datetime, expected_output, permission) in comprehensive_test_cases { + // Test each scenario individually to ensure service accepts all formats + let dt_start = parse_rfc3339(input_datetime).unwrap(); + let formatted_start = format_datetime(dt_start)?; + let dt_expiry = parse_rfc3339("2025-12-31T23:59:59Z").unwrap(); + let formatted_expiry = format_datetime(dt_expiry)?; + + // Verify the formatter produces the expected precision/conversion + assert_eq!( + formatted_start, expected_output, + "Failed for test case: {} with input: {}", + test_name, input_datetime + ); + + let access_policy = AccessPolicy { + expiry: Some(formatted_expiry), + permission: Some(permission.to_string()), + start: Some(formatted_start), + }; + let signed_identifier = SignedIdentifier { + access_policy: Some(access_policy), + id: Some(format!("comprehensive-{}", test_name)), + }; + + // Test that the service accepts each datetime format + container_client + .set_access_policy(vec![signed_identifier], None) + .await?; } - // Find "hello world" blob by its tag {"foo": "bar"} - let response = container_client - .find_blobs_by_tags("\"foo\"='bar'", None) - .await?; - let filter_blob_segment = response.into_body().await?; - let blobs = filter_blob_segment.blobs.unwrap(); - assert!( - blobs - .iter() - .any(|blob| blob.name.as_ref().unwrap() == &blob1_name), - "Failed to find \"{blob1_name}\" in filtered blob results." - ); - - // Find "ferris the crab" blob by its tag {"fizz": "buzz"} - let response = container_client - .find_blobs_by_tags(&format_filter_expression(&blob2_tags)?, None) - .await?; - let filter_blob_segment = response.into_body().await?; - let blobs = filter_blob_segment.blobs.unwrap(); - assert!( - blobs - .iter() - .any(|blob| blob.name.as_ref().unwrap() == &blob2_name), - "Failed to find \"{blob2_name}\" in filtered blob results." - ); - 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 12d754f455..10e7e007d5 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_service_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_service_client.rs @@ -172,90 +172,3 @@ async fn test_get_account_info(ctx: TestContext) -> Result<(), Box> { Ok(()) } - -#[recorded::test] -async fn test_find_blobs_by_tags_service(ctx: TestContext) -> Result<(), Box> { - // Recording Setup - let recording = ctx.recording(); - let service_client = get_blob_service_client(recording)?; - let container_client_1 = get_container_client(recording, true).await?; - let container_client_2 = get_container_client(recording, true).await?; - - // Create Test Blobs with Tags - let blob1_name = get_blob_name(recording); - create_test_blob( - &container_client_1.blob_client(blob1_name.clone()), - Some(RequestContent::from("hello world".as_bytes().into())), - Some( - BlockBlobClientUploadOptions::default() - .with_tags(HashMap::from([("foo".to_string(), "bar".to_string())])), - ), - ) - .await?; - let blob2_name = get_blob_name(recording); - create_test_blob( - &container_client_1.blob_client(blob2_name.clone()), - Some(RequestContent::from("ferris the crab".as_bytes().into())), - Some( - BlockBlobClientUploadOptions::default() - .with_tags(HashMap::from([("fizz".to_string(), "buzz".to_string())])), - ), - ) - .await?; - let blob3_name = get_blob_name(recording); - let blob3_tags = HashMap::from([("tagged".to_string(), "true".to_string())]); - create_test_blob( - &container_client_1.blob_client(blob3_name.clone()), - Some(RequestContent::from("six seven".as_bytes().into())), - Some(BlockBlobClientUploadOptions::default().with_tags(blob3_tags.clone())), - ) - .await?; - - // Sleep in live mode to allow tags to be indexed on the service - if recording.test_mode() == TestMode::Live { - time::sleep(Duration::from_secs(5)).await; - } - - // Find "hello world" blob by its tag {"foo": "bar"} - let response = service_client - .find_blobs_by_tags("\"foo\"='bar'", None) - .await?; - let filter_blob_segment = response.into_body().await?; - let blobs = filter_blob_segment.blobs.unwrap(); - assert!( - blobs - .iter() - .any(|blob| blob.name.as_ref().unwrap() == &blob1_name), - "Failed to find \"{blob1_name}\" in filtered blob results." - ); - - // Find "ferris the crab" blob by its tag {"fizz": "buzz"} - let response = service_client - .find_blobs_by_tags("\"fizz\"='buzz'", None) - .await?; - let filter_blob_segment = response.into_body().await?; - let blobs = filter_blob_segment.blobs.unwrap(); - assert!( - blobs - .iter() - .any(|blob| blob.name.as_ref().unwrap() == &blob2_name), - "Failed to find \"{blob2_name}\" in filtered blob results." - ); - - // Find "six seven" blob by its tag {"tagged": "true"} - let response = service_client - .find_blobs_by_tags(&format_filter_expression(&blob3_tags)?, None) - .await?; - let filter_blob_segment = response.into_body().await?; - let blobs = filter_blob_segment.blobs.unwrap(); - assert!( - blobs - .iter() - .any(|blob| blob.name.as_ref().unwrap() == &blob3_name), - "Failed to find \"{blob3_name}\" in filtered blob results." - ); - - container_client_1.delete_container(None).await?; - container_client_2.delete_container(None).await?; - Ok(()) -} diff --git a/sdk/storage/azure_storage_blob/tsp-location.yaml b/sdk/storage/azure_storage_blob/tsp-location.yaml index 89427203d9..0179a78b10 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: 2e3571e7b2c729281b6819574cf358fd87ded3ab +commit: 5319b14dc2f84f99d57682a275c44026f4312ae9 repo: Azure/azure-rest-api-specs additionalDirectories: