From 6cb4e421f2fece2991c2d2df70f1f77f9efe9261 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Thu, 6 Nov 2025 17:19:43 -0800 Subject: [PATCH 1/8] Generate + set/get_access_policy --- Cargo.lock | 1 + sdk/storage/.dict.txt | 1 + sdk/storage/azure_storage_blob/Cargo.toml | 1 + .../src/clients/blob_container_client.rs | 44 ++++++++-- .../clients/blob_container_client.rs | 22 ++--- .../src/generated/models/header_traits.rs | 88 +++++++++---------- .../src/generated/models/models_impl.rs | 9 +- .../src/generated/models/pub_models.rs | 26 +++--- .../azure_storage_blob/src/models/mod.rs | 27 +++--- sdk/storage/azure_storage_blob/src/parsers.rs | 26 +++++- .../tests/blob_container_client.rs | 69 ++++++++++++++- .../azure_storage_blob/tsp-location.yaml | 2 +- 12 files changed, 219 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57b0ee7380..b6e19f27c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -512,6 +512,7 @@ dependencies = [ "futures", "serde", "serde_json", + "time", "tokio", "tracing", "typespec_client_core", diff --git a/sdk/storage/.dict.txt b/sdk/storage/.dict.txt index 7c5493ce8a..2b62121318 100644 --- a/sdk/storage/.dict.txt +++ b/sdk/storage/.dict.txt @@ -22,6 +22,7 @@ RAGRS restype ruleid secondtag +subsecond testblob1 testblob2 testblob3 diff --git a/sdk/storage/azure_storage_blob/Cargo.toml b/sdk/storage/azure_storage_blob/Cargo.toml index 56a9b27b0b..807746a90a 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/src/clients/blob_container_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs index e8175de936..8e2e544aa1 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 @@ -9,12 +9,14 @@ use crate::{ BlobContainerClientBreakLeaseOptions, BlobContainerClientBreakLeaseResult, BlobContainerClientChangeLeaseOptions, BlobContainerClientChangeLeaseResult, BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions, - BlobContainerClientFindBlobsByTagsOptions, BlobContainerClientGetAccountInfoOptions, - BlobContainerClientGetAccountInfoResult, BlobContainerClientGetPropertiesOptions, - BlobContainerClientGetPropertiesResult, BlobContainerClientListBlobFlatSegmentOptions, - BlobContainerClientReleaseLeaseOptions, BlobContainerClientReleaseLeaseResult, - BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, - BlobContainerClientSetMetadataOptions, + BlobContainerClientFindBlobsByTagsOptions, BlobContainerClientGetAccessPolicyOptions, + BlobContainerClientGetAccountInfoOptions, BlobContainerClientGetAccountInfoResult, + BlobContainerClientGetPropertiesOptions, BlobContainerClientGetPropertiesResult, + BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions, + BlobContainerClientReleaseLeaseResult, BlobContainerClientRenewLeaseOptions, + BlobContainerClientRenewLeaseResult, BlobContainerClientSetAccessPolicyOptions, + BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetMetadataOptions, + SignedIdentifiers, }, models::{FilterBlobSegment, ListBlobsFlatSegmentResponse, StorageErrorCode}, pipeline::StorageHeadersPolicy, @@ -25,7 +27,7 @@ use azure_core::{ error::ErrorKind, http::{ policies::{BearerTokenAuthorizationPolicy, Policy}, - NoFormat, Pager, Pipeline, Response, StatusCode, Url, XmlFormat, + NoFormat, Pager, Pipeline, RequestContent, Response, StatusCode, Url, XmlFormat, }, tracing, Result, }; @@ -365,4 +367,32 @@ impl BlobContainerClient { Err(e) => Err(e), } } + + /// 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: RequestContent, + options: Option>, + ) -> Result> { + self.client.set_access_policy(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> { + self.client.get_access_policy(options).await + } } 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 5112db0986..05fde69182 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 @@ -19,7 +19,7 @@ use crate::generated::models::{ BlobContainerClientRestoreResult, BlobContainerClientSetAccessPolicyOptions, BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetMetadataOptions, FilterBlobSegment, ListBlobsFlatSegmentResponse, ListBlobsHierarchySegmentResponse, - SignedIdentifier, + SignedIdentifiers, }; use azure_core::{ credentials::TokenCredential, @@ -522,14 +522,14 @@ impl BlobContainerClient { /// /// ## Response Headers /// - /// The returned [`Response`](azure_core::http::Response) implements the [`VecSignedIdentifierHeaders`] trait, which provides + /// The returned [`Response`](azure_core::http::Response) implements the [`SignedIdentifiersHeaders`] trait, which provides /// access to response headers. For example: /// /// ```no_run /// use azure_core::{Result, http::{Response, XmlFormat}}; - /// use azure_storage_blob::models::{SignedIdentifier, VecSignedIdentifierHeaders}; + /// use azure_storage_blob::models::{SignedIdentifiers, SignedIdentifiersHeaders}; /// async fn example() -> Result<()> { - /// let response: Response, XmlFormat> = unimplemented!(); + /// let response: Response = unimplemented!(); /// // Access response headers /// if let Some(date) = response.date()? { /// println!("Date: {:?}", date); @@ -545,17 +545,17 @@ impl BlobContainerClient { /// ``` /// /// ### Available headers - /// * [`date`()](crate::generated::models::VecSignedIdentifierHeaders::date) - Date - /// * [`last_modified`()](crate::generated::models::VecSignedIdentifierHeaders::last_modified) - Last-Modified - /// * [`etag`()](crate::generated::models::VecSignedIdentifierHeaders::etag) - etag - /// * [`access`()](crate::generated::models::VecSignedIdentifierHeaders::access) - x-ms-blob-public-access + /// * [`date`()](crate::generated::models::SignedIdentifiersHeaders::date) - Date + /// * [`last_modified`()](crate::generated::models::SignedIdentifiersHeaders::last_modified) - Last-Modified + /// * [`etag`()](crate::generated::models::SignedIdentifiersHeaders::etag) - etag + /// * [`access`()](crate::generated::models::SignedIdentifiersHeaders::access) - x-ms-blob-public-access /// - /// [`VecSignedIdentifierHeaders`]: crate::generated::models::VecSignedIdentifierHeaders + /// [`SignedIdentifiersHeaders`]: crate::generated::models::SignedIdentifiersHeaders #[tracing::function("Storage.Blob.Container.getAccessPolicy")] pub async fn get_access_policy( &self, options: Option>, - ) -> Result, XmlFormat>> { + ) -> Result> { let options = options.unwrap_or_default(); let ctx = options.method_options.context.to_borrowed(); let mut url = self.endpoint.clone(); @@ -1319,7 +1319,7 @@ impl BlobContainerClient { #[tracing::function("Storage.Blob.Container.setAccessPolicy")] pub async fn set_access_policy( &self, - container_acl: RequestContent, XmlFormat>, + container_acl: RequestContent, options: Option>, ) -> Result> { let options = options.unwrap_or_default(); 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 1f97273c52..3be791a314 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 @@ -26,7 +26,7 @@ use super::{ PageBlobClientCopyIncrementalResult, PageBlobClientCreateResult, PageBlobClientResizeResult, PageBlobClientSetSequenceNumberResult, PageBlobClientUploadPagesFromUrlResult, PageBlobClientUploadPagesResult, PageList, PublicAccessType, RehydratePriority, - SignedIdentifier, SkuName, StorageServiceStats, UserDelegationKey, + SignedIdentifiers, SkuName, StorageServiceStats, UserDelegationKey, }; use azure_core::{ base64::decode, @@ -3583,42 +3583,68 @@ impl PageListHeaders for Response { } } -/// Provides access to typed response headers for `BlobServiceClient::get_statistics()` +/// Provides access to typed response headers for `BlobContainerClient::get_access_policy()` /// /// # Examples /// /// ```no_run /// use azure_core::{Result, http::{Response, XmlFormat}}; -/// use azure_storage_blob::models::{StorageServiceStats, StorageServiceStatsHeaders}; +/// use azure_storage_blob::models::{SignedIdentifiers, SignedIdentifiersHeaders}; /// async fn example() -> Result<()> { -/// let response: Response = unimplemented!(); +/// let response: Response = unimplemented!(); /// // Access response headers /// if let Some(date) = response.date()? { /// println!("Date: {:?}", date); /// } +/// if let Some(last_modified) = response.last_modified()? { +/// println!("Last-Modified: {:?}", last_modified); +/// } +/// if let Some(etag) = response.etag()? { +/// println!("etag: {:?}", etag); +/// } /// Ok(()) /// } /// ``` -pub trait StorageServiceStatsHeaders: private::Sealed { +pub trait SignedIdentifiersHeaders: private::Sealed { fn date(&self) -> Result>; + fn last_modified(&self) -> Result>; + fn etag(&self) -> Result>; + fn access(&self) -> Result>; } -impl StorageServiceStatsHeaders for Response { +impl SignedIdentifiersHeaders 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| { + parse_rfc7231(h.as_str()) + }) + } + + /// The ETag contains a value that you can use to perform operations conditionally. + fn etag(&self) -> Result> { + Headers::get_optional_as(self.headers(), &ETAG) + } + + /// The public access setting for the container. + fn access(&self) -> Result> { + Headers::get_optional_as(self.headers(), &BLOB_PUBLIC_ACCESS) + } } -/// Provides access to typed response headers for `BlobServiceClient::get_user_delegation_key()` +/// Provides access to typed response headers for `BlobServiceClient::get_statistics()` /// /// # Examples /// /// ```no_run /// use azure_core::{Result, http::{Response, XmlFormat}}; -/// use azure_storage_blob::models::{UserDelegationKey, UserDelegationKeyHeaders}; +/// use azure_storage_blob::models::{StorageServiceStats, StorageServiceStatsHeaders}; /// async fn example() -> Result<()> { -/// let response: Response = unimplemented!(); +/// let response: Response = unimplemented!(); /// // Access response headers /// if let Some(date) = response.date()? { /// println!("Date: {:?}", date); @@ -3626,68 +3652,42 @@ impl StorageServiceStatsHeaders for Response { /// Ok(()) /// } /// ``` -pub trait UserDelegationKeyHeaders: private::Sealed { +pub trait StorageServiceStatsHeaders: private::Sealed { fn date(&self) -> Result>; } -impl UserDelegationKeyHeaders for Response { +impl StorageServiceStatsHeaders 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::get_access_policy()` +/// Provides access to typed response headers for `BlobServiceClient::get_user_delegation_key()` /// /// # Examples /// /// ```no_run /// use azure_core::{Result, http::{Response, XmlFormat}}; -/// use azure_storage_blob::models::{SignedIdentifier, VecSignedIdentifierHeaders}; +/// use azure_storage_blob::models::{UserDelegationKey, UserDelegationKeyHeaders}; /// async fn example() -> Result<()> { -/// let response: Response, XmlFormat> = unimplemented!(); +/// let response: Response = unimplemented!(); /// // Access response headers /// if let Some(date) = response.date()? { /// println!("Date: {:?}", date); /// } -/// if let Some(last_modified) = response.last_modified()? { -/// println!("Last-Modified: {:?}", last_modified); -/// } -/// if let Some(etag) = response.etag()? { -/// println!("etag: {:?}", etag); -/// } /// Ok(()) /// } /// ``` -pub trait VecSignedIdentifierHeaders: private::Sealed { +pub trait UserDelegationKeyHeaders: private::Sealed { fn date(&self) -> Result>; - fn last_modified(&self) -> Result>; - fn etag(&self) -> Result>; - fn access(&self) -> Result>; } -impl VecSignedIdentifierHeaders for Response, XmlFormat> { +impl UserDelegationKeyHeaders 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| { - parse_rfc7231(h.as_str()) - }) - } - - /// The ETag contains a value that you can use to perform operations conditionally. - fn etag(&self) -> Result> { - Headers::get_optional_as(self.headers(), &ETAG) - } - - /// The public access setting for the container. - fn access(&self) -> Result> { - Headers::get_optional_as(self.headers(), &BLOB_PUBLIC_ACCESS) - } } mod private { @@ -3714,7 +3714,7 @@ mod private { PageBlobClientCopyIncrementalResult, PageBlobClientCreateResult, PageBlobClientResizeResult, PageBlobClientSetSequenceNumberResult, PageBlobClientUploadPagesFromUrlResult, PageBlobClientUploadPagesResult, PageList, - SignedIdentifier, StorageServiceStats, UserDelegationKey, + SignedIdentifiers, StorageServiceStats, UserDelegationKey, }; use azure_core::http::{AsyncResponse, NoFormat, Response, XmlFormat}; @@ -3770,7 +3770,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, XmlFormat> {} } diff --git a/sdk/storage/azure_storage_blob/src/generated/models/models_impl.rs b/sdk/storage/azure_storage_blob/src/generated/models/models_impl.rs index 41d2372850..0888b6df09 100644 --- a/sdk/storage/azure_storage_blob/src/generated/models/models_impl.rs +++ b/sdk/storage/azure_storage_blob/src/generated/models/models_impl.rs @@ -6,7 +6,7 @@ use super::{ BlobItemInternal, BlobServiceProperties, BlobTags, BlockLookupList, ContainerItem, KeyInfo, ListBlobsFlatSegmentResponse, ListBlobsHierarchySegmentResponse, ListContainersSegmentResponse, - QueryRequest, + QueryRequest, SignedIdentifiers, }; use async_trait::async_trait; use azure_core::{ @@ -79,3 +79,10 @@ impl TryFrom for RequestContent { Ok(to_xml(&value)?.into()) } } + +impl TryFrom for RequestContent { + type Error = azure_core::Error; + fn try_from(value: SignedIdentifiers) -> Result { + Ok(to_xml(&value)?.into()) + } +} 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 44a18a2f40..39b578e269 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()` @@ -1302,6 +1292,14 @@ pub struct SignedIdentifier { pub id: Option, } +/// Represents an array of signed identifiers +#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +pub struct SignedIdentifiers { + /// The array of signed identifiers. + #[serde(rename = "SignedIdentifier", skip_serializing_if = "Option::is_none")] + pub items: Option>, +} + /// The properties that enable an account to host a static website #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] pub struct StaticWebsite { diff --git a/sdk/storage/azure_storage_blob/src/models/mod.rs b/sdk/storage/azure_storage_blob/src/models/mod.rs index a33510fc8c..a902ac3430 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,15 +36,16 @@ pub use crate::generated::models::{ BlobContainerClientBreakLeaseResultHeaders, BlobContainerClientChangeLeaseOptions, BlobContainerClientChangeLeaseResult, BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions, - BlobContainerClientFindBlobsByTagsOptions, BlobContainerClientGetAccountInfoOptions, - BlobContainerClientGetAccountInfoResult, BlobContainerClientGetAccountInfoResultHeaders, - BlobContainerClientGetPropertiesOptions, BlobContainerClientGetPropertiesResult, - BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, - BlobContainerClientReleaseLeaseOptions, BlobContainerClientReleaseLeaseResult, - BlobContainerClientReleaseLeaseResultHeaders, BlobContainerClientRenameResult, - BlobContainerClientRenameResultHeaders, BlobContainerClientRenewLeaseOptions, - BlobContainerClientRenewLeaseResult, BlobContainerClientRenewLeaseResultHeaders, - BlobContainerClientRestoreResult, BlobContainerClientRestoreResultHeaders, + BlobContainerClientFindBlobsByTagsOptions, BlobContainerClientGetAccessPolicyOptions, + BlobContainerClientGetAccountInfoOptions, BlobContainerClientGetAccountInfoResult, + BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesOptions, + BlobContainerClientGetPropertiesResult, BlobContainerClientGetPropertiesResultHeaders, + BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions, + BlobContainerClientReleaseLeaseResult, BlobContainerClientReleaseLeaseResultHeaders, + BlobContainerClientRenameResult, BlobContainerClientRenameResultHeaders, + BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, + BlobContainerClientRenewLeaseResultHeaders, BlobContainerClientRestoreResult, + BlobContainerClientRestoreResultHeaders, BlobContainerClientSetAccessPolicyOptions, BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetAccessPolicyResultHeaders, BlobContainerClientSetMetadataOptions, BlobCopySourceTags, BlobDeleteType, BlobExpiryOptions, BlobFlatListSegment, BlobImmutabilityPolicyMode, BlobItemInternal, BlobMetadata, BlobName, @@ -79,7 +80,7 @@ pub use crate::generated::models::{ PageBlobClientUploadPagesFromUrlResultHeaders, PageBlobClientUploadPagesOptions, PageBlobClientUploadPagesResult, PageBlobClientUploadPagesResultHeaders, PageList, PageListHeaders, PremiumPageBlobAccessTier, PublicAccessType, QueryRequestType, QueryType, - RehydratePriority, RetentionPolicy, SequenceNumberActionType, SignedIdentifier, SkuName, - StaticWebsite, StorageErrorCode, StorageServiceStats, StorageServiceStatsHeaders, - UserDelegationKey, UserDelegationKeyHeaders, VecSignedIdentifierHeaders, + RehydratePriority, RetentionPolicy, SequenceNumberActionType, SignedIdentifier, + SignedIdentifiers, SkuName, StaticWebsite, StorageErrorCode, StorageServiceStats, + StorageServiceStatsHeaders, UserDelegationKey, UserDelegationKeyHeaders, }; diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index c86f39f0e8..ee51b0c66a 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -1,8 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use azure_core::{error::ErrorKind, Error}; use std::collections::HashMap; -use std::io::{Error, ErrorKind}; +use time::{format_description::FormatItem, macros::format_description, OffsetDateTime, UtcOffset}; + +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. @@ -16,7 +20,7 @@ use std::io::{Error, ErrorKind}; pub fn format_page_range(offset: u64, length: u64) -> Result { if offset % 512 != 0 { return Err(Error::new( - ErrorKind::InvalidInput, + ErrorKind::DataConversion, format!( "provided offset {} is not aligned to a 512-byte boundary.", offset @@ -25,7 +29,7 @@ pub fn format_page_range(offset: u64, length: u64) -> Result { } if length % 512 != 0 { return Err(Error::new( - ErrorKind::InvalidInput, + ErrorKind::DataConversion, format!( "provided length {} is not aligned to a 512-byte boundary.", offset @@ -47,7 +51,7 @@ pub fn format_page_range(offset: u64, length: u64) -> Result { pub fn format_filter_expression(tags: &HashMap) -> Result { if tags.is_empty() { return Err(Error::new( - ErrorKind::InvalidInput, + ErrorKind::DataConversion, "Tags HashMap cannot be empty.".to_string(), )); } @@ -59,3 +63,17 @@ 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::DataConversion, + format!("Failed to format datetime: {}", e), + ) + }) +} 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 78a38b5134..5709dad082 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs @@ -3,13 +3,14 @@ use azure_core::http::{RequestContent, StatusCode}; use azure_core_test::{recorded, Matcher, TestContext, TestMode}; -use azure_storage_blob::format_filter_expression; use azure_storage_blob::models::{ - AccountKind, BlobContainerClientAcquireLeaseResultHeaders, + AccessPolicy, AccountKind, BlobContainerClientAcquireLeaseResultHeaders, BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientSetMetadataOptions, BlobType, BlockBlobClientUploadOptions, LeaseState, + SignedIdentifier, SignedIdentifiers, }; +use azure_storage_blob::{format_datetime, format_filter_expression}; use azure_storage_blob_test::{ create_test_blob, get_blob_name, get_blob_service_client, get_container_client, get_container_name, @@ -17,6 +18,7 @@ use azure_storage_blob_test::{ use futures::{StreamExt, TryStreamExt}; use std::{collections::HashMap, error::Error, time::Duration}; use tokio::time; +use typespec_client_core::time::OffsetDateTime; #[recorded::test] async fn test_create_container(ctx: TestContext) -> Result<(), Box> { @@ -400,3 +402,66 @@ async fn test_find_blobs_by_tags_container(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, false).await?; + container_client.create_container(None).await?; + + // Set Access Policy w/ Policy Defined + let test_id: Option = Some("testid".into()); + let expiry = Some(format_datetime( + OffsetDateTime::now_utc() + Duration::from_secs(10), + )?); + let start = Some(format_datetime(OffsetDateTime::now_utc())?); + let access_policy = AccessPolicy { + expiry: expiry.clone(), + permission: Some("rw".to_string()), + start: start.clone(), + }; + let signed_identifier = SignedIdentifier { + access_policy: Some(access_policy.clone()), + id: test_id.clone(), + }; + let signed_identifiers = SignedIdentifiers { + items: Some(vec![signed_identifier]), + }; + container_client + .set_access_policy(RequestContent::try_from(signed_identifiers)?, None) + .await?; + + // Assert + let response = container_client.get_access_policy(None).await?; + let signed_identifiers = response.into_model()?; + let response_id = signed_identifiers.items.clone().unwrap()[0].id.clone(); + let response_access_policy = signed_identifiers.items.clone().unwrap()[0] + .access_policy + .clone(); + + assert_eq!(signed_identifiers.items.clone().unwrap().len(), 1); + assert_eq!(response_id, test_id); + assert_eq!( + response_access_policy.clone().unwrap().permission, + access_policy.permission + ); + assert_eq!(response_access_policy.clone().unwrap().expiry, expiry); + assert_eq!(response_access_policy.clone().unwrap().start, start); + + // Clear Access Policy + let cleared_signed_identifiers = SignedIdentifiers { items: None }; + + container_client + .set_access_policy(RequestContent::try_from(cleared_signed_identifiers)?, None) + .await?; + + // Assert + let cleared_response = container_client.get_access_policy(None).await?; + let cleared_signed_identifiers = cleared_response.into_model()?; + assert!(cleared_signed_identifiers.items.is_none()); + + Ok(()) +} diff --git a/sdk/storage/azure_storage_blob/tsp-location.yaml b/sdk/storage/azure_storage_blob/tsp-location.yaml index 2f51fc4061..4c1adbecf7 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: 6b5a0fdadca03abe0b8e91a68a62cd1e639ed55e +commit: f38fa651becb53a5af9354b7d75948af995d1d5d repo: Azure/azure-rest-api-specs additionalDirectories: From b4e964a623e00fd3efc9971d8a20629de2f7d468 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Thu, 6 Nov 2025 18:10:20 -0800 Subject: [PATCH 2/8] Add a multiple SignedIdentifier case to test, add helper and utilize helper --- sdk/storage/azure_storage_blob/assets.json | 2 +- .../src/clients/blob_container_client.rs | 2 +- sdk/storage/azure_storage_blob/src/parsers.rs | 30 +++++++++- .../tests/blob_container_client.rs | 57 +++++++++++++++++-- 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/sdk/storage/azure_storage_blob/assets.json b/sdk/storage/azure_storage_blob/assets.json index dae255a41b..7d883c4494 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_1e5e3b2c6c", + "Tag": "rust/azure_storage_blob_dae663daf3", "TagPrefix": "rust/azure_storage_blob" } 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 8e2e544aa1..6aa57bdeaf 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 @@ -373,7 +373,7 @@ impl BlobContainerClient { /// /// # Arguments /// - /// * `container_acl` - The access control list for the container. + /// * `container_acl` - The access control list for the container. See [`format_signed_identifiers()`](crate::format_signed_identifiers) for help with the expected format. /// * `options` - Optional configuration for the request. pub async fn set_access_policy( &self, diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index ee51b0c66a..b5f85f5741 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -5,6 +5,8 @@ use azure_core::{error::ErrorKind, Error}; use std::collections::HashMap; use time::{format_description::FormatItem, macros::format_description, OffsetDateTime, UtcOffset}; +use crate::models::{AccessPolicy, SignedIdentifier, SignedIdentifiers}; + static RFC3339_7: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:7]Z"); @@ -64,9 +66,10 @@ pub fn format_filter_expression(tags: &HashMap) -> Result Result { let utc = datetime.to_offset(UtcOffset::UTC); @@ -77,3 +80,28 @@ pub fn format_datetime(datetime: OffsetDateTime) -> Result { ) }) } + +/// Takes a HashMap where the key is the policy ID and the value is the AccessPolicy and converts it to SignedIdentifiers. +/// +/// # Arguments +/// +/// * `policies` - A HashMap where keys are policy identifiers and values are AccessPolicy objects. +pub fn format_signed_identifiers( + policies: HashMap, +) -> Result { + if policies.is_empty() { + return Ok(SignedIdentifiers { items: None }); + } + + let signed_identifiers: Vec = policies + .into_iter() + .map(|(id, access_policy)| SignedIdentifier { + id: Some(id), + access_policy: Some(access_policy), + }) + .collect(); + + Ok(SignedIdentifiers { + items: Some(signed_identifiers), + }) +} 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 5709dad082..58a0bbe7ea 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs @@ -10,7 +10,7 @@ use azure_storage_blob::models::{ BlobContainerClientSetMetadataOptions, BlobType, BlockBlobClientUploadOptions, LeaseState, SignedIdentifier, SignedIdentifiers, }; -use azure_storage_blob::{format_datetime, format_filter_expression}; +use azure_storage_blob::{format_datetime, format_filter_expression, format_signed_identifiers}; use azure_storage_blob_test::{ create_test_blob, get_blob_name, get_blob_service_client, get_container_client, get_container_name, @@ -406,7 +406,6 @@ async fn test_find_blobs_by_tags_container(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, false).await?; @@ -441,7 +440,6 @@ async fn test_container_access_policy(ctx: TestContext) -> Result<(), Box Result<(), Box = Some("testid_1".into()); + let test_id_2: Option = Some("testid_2".into()); + let access_policy_1 = AccessPolicy { + expiry: expiry.clone(), + permission: Some("rw".to_string()), + start: start.clone(), + }; + let access_policy_2 = AccessPolicy { + expiry: expiry.clone(), + permission: Some("cd".to_string()), + start: start.clone(), + }; + let policies: HashMap = HashMap::from([ + (test_id_1.clone().unwrap(), access_policy_1.clone()), + (test_id_2.clone().unwrap(), access_policy_2.clone()), + ]); + container_client + .set_access_policy( + RequestContent::try_from(format_signed_identifiers(policies)?)?, + None, + ) + .await?; + // Assert + let response = container_client.get_access_policy(None).await?; + let signed_identifiers = response.into_model()?; + let mut ids = Vec::new(); + let mut permissions = Vec::new(); + for signed_identifier in signed_identifiers.items.as_ref().unwrap() { + ids.push(signed_identifier.id.clone()); + if let Some(access_policy) = &signed_identifier.access_policy { + permissions.push(access_policy.permission.clone()); + } + } + assert_eq!(ids.len(), 2); + assert_eq!(permissions.len(), 2); + assert!(ids.contains(&test_id_1)); + assert!(ids.contains(&test_id_2)); + assert!(permissions.contains(&access_policy_1.permission)); + assert!(permissions.contains(&access_policy_2.permission)); + + // Clear Access Policy container_client - .set_access_policy(RequestContent::try_from(cleared_signed_identifiers)?, None) + .set_access_policy( + RequestContent::try_from(format_signed_identifiers(HashMap::new())?)?, + None, + ) .await?; // Assert From 6dd2d4bdf72c0759b59232e0bf32ebb14dd6bfb6 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Thu, 6 Nov 2025 18:31:53 -0800 Subject: [PATCH 3/8] nit export SignedIdentifiersHeaders --- sdk/storage/azure_storage_blob/src/models/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/models/mod.rs b/sdk/storage/azure_storage_blob/src/models/mod.rs index a902ac3430..cb7eecaa2d 100644 --- a/sdk/storage/azure_storage_blob/src/models/mod.rs +++ b/sdk/storage/azure_storage_blob/src/models/mod.rs @@ -81,6 +81,6 @@ pub use crate::generated::models::{ PageBlobClientUploadPagesResult, PageBlobClientUploadPagesResultHeaders, PageList, PageListHeaders, PremiumPageBlobAccessTier, PublicAccessType, QueryRequestType, QueryType, RehydratePriority, RetentionPolicy, SequenceNumberActionType, SignedIdentifier, - SignedIdentifiers, SkuName, StaticWebsite, StorageErrorCode, StorageServiceStats, - StorageServiceStatsHeaders, UserDelegationKey, UserDelegationKeyHeaders, + SignedIdentifiers, SignedIdentifiersHeaders, SkuName, StaticWebsite, StorageErrorCode, + StorageServiceStats, StorageServiceStatsHeaders, UserDelegationKey, UserDelegationKeyHeaders, }; From f391027cd00a6c2195d9ae5d73fb9a24bec1f58f Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Wed, 12 Nov 2025 13:01:20 -0800 Subject: [PATCH 4/8] Use default values to get the datetime objects in the recording vars --- sdk/storage/azure_storage_blob/assets.json | 2 +- .../tests/blob_container_client.rs | 66 ++++++++++++++----- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/sdk/storage/azure_storage_blob/assets.json b/sdk/storage/azure_storage_blob/assets.json index 7d883c4494..ed10a1d317 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_dae663daf3", + "Tag": "rust/azure_storage_blob_a768f5011e", "TagPrefix": "rust/azure_storage_blob" } 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 58a0bbe7ea..37394ed646 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use azure_core::http::{RequestContent, StatusCode}; -use azure_core_test::{recorded, Matcher, TestContext, TestMode}; +use azure_core_test::{recorded, Matcher, TestContext, TestMode, VarOptions}; use azure_storage_blob::models::{ AccessPolicy, AccountKind, BlobContainerClientAcquireLeaseResultHeaders, BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientGetAccountInfoResultHeaders, @@ -411,16 +411,31 @@ async fn test_container_access_policy(ctx: TestContext) -> Result<(), Box = Some("testid".into()); - let expiry = Some(format_datetime( - OffsetDateTime::now_utc() + Duration::from_secs(10), - )?); - let start = Some(format_datetime(OffsetDateTime::now_utc())?); let access_policy = AccessPolicy { - expiry: expiry.clone(), + expiry: Some(expiry_1.clone()), permission: Some("rw".to_string()), - start: start.clone(), + start: Some(start_1.clone()), }; let signed_identifier = SignedIdentifier { access_policy: Some(access_policy.clone()), @@ -446,25 +461,42 @@ async fn test_container_access_policy(ctx: TestContext) -> Result<(), Box = Some("testid_1".into()); let test_id_2: Option = Some("testid_2".into()); let access_policy_1 = AccessPolicy { - expiry: expiry.clone(), + expiry: Some(expiry_2.clone()), permission: Some("rw".to_string()), - start: start.clone(), + start: Some(start_2.clone()), }; let access_policy_2 = AccessPolicy { - expiry: expiry.clone(), + expiry: Some(expiry_2), permission: Some("cd".to_string()), - start: start.clone(), + start: Some(start_2), }; let policies: HashMap = HashMap::from([ (test_id_1.clone().unwrap(), access_policy_1.clone()), From 7bc000743096c36083ef72db62c53b3b170672ce Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Fri, 14 Nov 2025 14:07:13 -0800 Subject: [PATCH 5/8] PR feedback --- sdk/storage/azure_storage_blob/assets.json | 2 +- .../src/clients/blob_container_client.rs | 26 +++- .../clients/blob_container_client.rs | 45 +------ .../src/generated/models/header_traits.rs | 86 +++----------- .../src/generated/models/pub_models.rs | 4 - .../src/models/extensions.rs | 47 +++++--- .../azure_storage_blob/src/models/mod.rs | 1 - sdk/storage/azure_storage_blob/src/parsers.rs | 37 +----- .../azure_storage_blob/tests/blob_client.rs | 4 +- .../tests/blob_container_client.rs | 111 +++++------------- .../azure_storage_blob/tsp-location.yaml | 2 +- 11 files changed, 111 insertions(+), 254 deletions(-) diff --git a/sdk/storage/azure_storage_blob/assets.json b/sdk/storage/azure_storage_blob/assets.json index ed10a1d317..d21272c4a9 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_a768f5011e", + "Tag": "rust/azure_storage_blob_dd4a0a3a13", "TagPrefix": "rust/azure_storage_blob" } 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 6aa57bdeaf..494bf64c81 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 @@ -15,8 +15,7 @@ use crate::{ BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions, BlobContainerClientReleaseLeaseResult, BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, BlobContainerClientSetAccessPolicyOptions, - BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetMetadataOptions, - SignedIdentifiers, + BlobContainerClientSetMetadataOptions, SignedIdentifiers, }, models::{FilterBlobSegment, ListBlobsFlatSegmentResponse, StorageErrorCode}, pipeline::StorageHeadersPolicy, @@ -373,13 +372,32 @@ impl BlobContainerClient { /// /// # Arguments /// - /// * `container_acl` - The access control list for the container. See [`format_signed_identifiers()`](crate::format_signed_identifiers) for help with the expected format. + /// * `container_acl` - The access control list for the container. You can create this from a + /// [`HashMap`] using [`SignedIdentifiers::from()`] and then wrapping it into a RequestContent. /// * `options` - Optional configuration for the request. + /// + /// # Example + /// + /// ```rust,no_run + /// use std::collections::HashMap; + /// use azure_core::http::RequestContent; + /// use azure_storage_blob::models::{AccessPolicy, SignedIdentifiers}; + /// + /// let mut policies = HashMap::new(); + /// policies.insert("some_policy_id".to_string(), AccessPolicy { + /// start: Some("2035-01-01T00:00:00Z".to_string()), + /// expiry: Some("2035-12-31T00:00:00Z".to_string()), + /// permission: Some("rwd".to_string()), + /// }); + /// + /// container_client.set_access_policy(RequestContent::try_from(SignedIdentifiers::from(policies))?, None).await?; + /// # Ok::<(), Box>(()) + /// ``` pub async fn set_access_policy( &self, container_acl: RequestContent, options: Option>, - ) -> Result> { + ) -> Result> { self.client.set_access_policy(container_acl, options).await } 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 05fde69182..bce37fbb14 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 @@ -17,9 +17,8 @@ use crate::generated::models::{ BlobContainerClientRenameResult, BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, BlobContainerClientRestoreOptions, BlobContainerClientRestoreResult, BlobContainerClientSetAccessPolicyOptions, - BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetMetadataOptions, - FilterBlobSegment, ListBlobsFlatSegmentResponse, ListBlobsHierarchySegmentResponse, - SignedIdentifiers, + BlobContainerClientSetMetadataOptions, FilterBlobSegment, ListBlobsFlatSegmentResponse, + ListBlobsHierarchySegmentResponse, SignedIdentifiers, }; use azure_core::{ credentials::TokenCredential, @@ -531,21 +530,20 @@ impl BlobContainerClient { /// async fn example() -> Result<()> { /// let response: Response = unimplemented!(); /// // Access response headers - /// if let Some(date) = response.date()? { - /// println!("Date: {:?}", date); - /// } /// if let Some(last_modified) = response.last_modified()? { /// println!("Last-Modified: {:?}", last_modified); /// } /// if let Some(etag) = response.etag()? { /// println!("etag: {:?}", etag); /// } + /// if let Some(access) = response.access()? { + /// println!("x-ms-blob-public-access: {:?}", access); + /// } /// Ok(()) /// } /// ``` /// /// ### Available headers - /// * [`date`()](crate::generated::models::SignedIdentifiersHeaders::date) - Date /// * [`last_modified`()](crate::generated::models::SignedIdentifiersHeaders::last_modified) - Last-Modified /// * [`etag`()](crate::generated::models::SignedIdentifiersHeaders::etag) - etag /// * [`access`()](crate::generated::models::SignedIdentifiersHeaders::access) - x-ms-blob-public-access @@ -1285,43 +1283,12 @@ impl BlobContainerClient { /// /// * `container_acl` - The access control list for the container. /// * `options` - Optional parameters for the request. - /// - /// ## Response Headers - /// - /// The returned [`Response`](azure_core::http::Response) implements the [`BlobContainerClientSetAccessPolicyResultHeaders`] trait, which provides - /// access to response headers. For example: - /// - /// ```no_run - /// use azure_core::{Result, http::{Response, NoFormat}}; - /// use azure_storage_blob::models::{BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetAccessPolicyResultHeaders}; - /// async fn example() -> Result<()> { - /// let response: Response = unimplemented!(); - /// // Access response headers - /// if let Some(date) = response.date()? { - /// println!("Date: {:?}", date); - /// } - /// if let Some(last_modified) = response.last_modified()? { - /// println!("Last-Modified: {:?}", last_modified); - /// } - /// if let Some(etag) = response.etag()? { - /// println!("etag: {:?}", etag); - /// } - /// Ok(()) - /// } - /// ``` - /// - /// ### Available headers - /// * [`date`()](crate::generated::models::BlobContainerClientSetAccessPolicyResultHeaders::date) - Date - /// * [`last_modified`()](crate::generated::models::BlobContainerClientSetAccessPolicyResultHeaders::last_modified) - Last-Modified - /// * [`etag`()](crate::generated::models::BlobContainerClientSetAccessPolicyResultHeaders::etag) - etag - /// - /// [`BlobContainerClientSetAccessPolicyResultHeaders`]: crate::generated::models::BlobContainerClientSetAccessPolicyResultHeaders #[tracing::function("Storage.Blob.Container.setAccessPolicy")] pub async fn set_access_policy( &self, container_acl: RequestContent, options: Option>, - ) -> Result> { + ) -> Result> { let options = options.unwrap_or_default(); let ctx = options.method_options.context.to_borrowed(); 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 3be791a314..71b54faedc 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 @@ -16,8 +16,8 @@ use super::{ BlobContainerClientChangeLeaseResult, BlobContainerClientGetAccountInfoResult, BlobContainerClientGetPropertiesResult, BlobContainerClientReleaseLeaseResult, BlobContainerClientRenameResult, BlobContainerClientRenewLeaseResult, - BlobContainerClientRestoreResult, BlobContainerClientSetAccessPolicyResult, - BlobImmutabilityPolicyMode, BlobServiceClientGetAccountInfoResult, BlobTags, BlobType, + BlobContainerClientRestoreResult, BlobImmutabilityPolicyMode, + BlobServiceClientGetAccountInfoResult, BlobTags, BlobType, BlockBlobClientCommitBlockListResult, BlockBlobClientQueryResult, BlockBlobClientStageBlockFromUrlResult, BlockBlobClientStageBlockResult, BlockBlobClientUploadBlobFromUrlResult, BlockBlobClientUploadResult, BlockList, CopyStatus, @@ -2237,55 +2237,6 @@ impl BlobContainerClientRestoreResultHeaders } } -/// Provides access to typed response headers for `BlobContainerClient::set_access_policy()` -/// -/// # Examples -/// -/// ```no_run -/// use azure_core::{Result, http::{Response, NoFormat}}; -/// use azure_storage_blob::models::{BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetAccessPolicyResultHeaders}; -/// async fn example() -> Result<()> { -/// let response: Response = unimplemented!(); -/// // Access response headers -/// if let Some(date) = response.date()? { -/// println!("Date: {:?}", date); -/// } -/// if let Some(last_modified) = response.last_modified()? { -/// println!("Last-Modified: {:?}", last_modified); -/// } -/// if let Some(etag) = response.etag()? { -/// println!("etag: {:?}", etag); -/// } -/// Ok(()) -/// } -/// ``` -pub trait BlobContainerClientSetAccessPolicyResultHeaders: private::Sealed { - fn date(&self) -> Result>; - fn last_modified(&self) -> Result>; - fn etag(&self) -> Result>; -} - -impl BlobContainerClientSetAccessPolicyResultHeaders - 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| { - parse_rfc7231(h.as_str()) - }) - } - - /// The ETag contains a value that you can use to perform operations conditionally. - fn etag(&self) -> Result> { - Headers::get_optional_as(self.headers(), &ETAG) - } -} - /// Provides access to typed response headers for `BlobServiceClient::get_account_info()` /// /// # Examples @@ -3593,31 +3544,25 @@ impl PageListHeaders for Response { /// async fn example() -> Result<()> { /// let response: Response = unimplemented!(); /// // Access response headers -/// if let Some(date) = response.date()? { -/// println!("Date: {:?}", date); -/// } /// if let Some(last_modified) = response.last_modified()? { /// println!("Last-Modified: {:?}", last_modified); /// } /// if let Some(etag) = response.etag()? { /// println!("etag: {:?}", etag); /// } +/// if let Some(access) = response.access()? { +/// println!("x-ms-blob-public-access: {:?}", access); +/// } /// Ok(()) /// } /// ``` pub trait SignedIdentifiersHeaders: private::Sealed { - fn date(&self) -> Result>; fn last_modified(&self) -> Result>; fn etag(&self) -> Result>; fn access(&self) -> Result>; } impl SignedIdentifiersHeaders 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| { @@ -3705,16 +3650,16 @@ mod private { BlobContainerClientChangeLeaseResult, BlobContainerClientGetAccountInfoResult, BlobContainerClientGetPropertiesResult, BlobContainerClientReleaseLeaseResult, BlobContainerClientRenameResult, BlobContainerClientRenewLeaseResult, - BlobContainerClientRestoreResult, BlobContainerClientSetAccessPolicyResult, - BlobServiceClientGetAccountInfoResult, BlobTags, BlockBlobClientCommitBlockListResult, - BlockBlobClientQueryResult, BlockBlobClientStageBlockFromUrlResult, - BlockBlobClientStageBlockResult, BlockBlobClientUploadBlobFromUrlResult, - BlockBlobClientUploadResult, BlockList, ListBlobsFlatSegmentResponse, - ListBlobsHierarchySegmentResponse, PageBlobClientClearPagesResult, - PageBlobClientCopyIncrementalResult, PageBlobClientCreateResult, - PageBlobClientResizeResult, PageBlobClientSetSequenceNumberResult, - PageBlobClientUploadPagesFromUrlResult, PageBlobClientUploadPagesResult, PageList, - SignedIdentifiers, StorageServiceStats, UserDelegationKey, + BlobContainerClientRestoreResult, BlobServiceClientGetAccountInfoResult, BlobTags, + BlockBlobClientCommitBlockListResult, BlockBlobClientQueryResult, + BlockBlobClientStageBlockFromUrlResult, BlockBlobClientStageBlockResult, + BlockBlobClientUploadBlobFromUrlResult, BlockBlobClientUploadResult, BlockList, + ListBlobsFlatSegmentResponse, ListBlobsHierarchySegmentResponse, + PageBlobClientClearPagesResult, PageBlobClientCopyIncrementalResult, + PageBlobClientCreateResult, PageBlobClientResizeResult, + PageBlobClientSetSequenceNumberResult, PageBlobClientUploadPagesFromUrlResult, + PageBlobClientUploadPagesResult, PageList, SignedIdentifiers, StorageServiceStats, + UserDelegationKey, }; use azure_core::http::{AsyncResponse, NoFormat, Response, XmlFormat}; @@ -3751,7 +3696,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 39b578e269..ee983929ce 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 @@ -193,10 +193,6 @@ pub struct BlobContainerClientRenewLeaseResult; #[derive(SafeDebug)] pub struct BlobContainerClientRestoreResult; -/// Contains results for `BlobContainerClient::set_access_policy()` -#[derive(SafeDebug)] -pub struct BlobContainerClientSetAccessPolicyResult; - /// The blob flat list segment. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] #[non_exhaustive] diff --git a/sdk/storage/azure_storage_blob/src/models/extensions.rs b/sdk/storage/azure_storage_blob/src/models/extensions.rs index 6a3a0683db..7e0833226b 100644 --- a/sdk/storage/azure_storage_blob/src/models/extensions.rs +++ b/sdk/storage/azure_storage_blob/src/models/extensions.rs @@ -2,8 +2,9 @@ // Licensed under the MIT License. use crate::models::{ - AppendBlobClientCreateOptions, BlobTag, BlobTags, BlockBlobClientUploadBlobFromUrlOptions, - BlockBlobClientUploadOptions, PageBlobClientCreateOptions, + AccessPolicy, AppendBlobClientCreateOptions, BlobTag, BlobTags, + BlockBlobClientUploadBlobFromUrlOptions, BlockBlobClientUploadOptions, + PageBlobClientCreateOptions, SignedIdentifier, SignedIdentifiers, }; use std::collections::HashMap; @@ -67,29 +68,18 @@ impl BlockBlobClientUploadOptions<'_> { } /// Converts a `BlobTags` struct into `HashMap`. -impl TryFrom for HashMap { - type Error = azure_core::Error; - - fn try_from(blob_tags: BlobTags) -> Result { +impl From for HashMap { + fn from(blob_tags: BlobTags) -> Self { 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(azure_core::Error::with_message( - azure_core::error::ErrorKind::DataConversion, - "BlobTag missing key or value", - )); - } + if let (Some(key), Some(value)) = (tag.key, tag.value) { + map.insert(key, value); } } } - - Ok(map) + map } } @@ -108,3 +98,24 @@ impl From> for BlobTags { } } } + +/// Converts a `HashMap` into a `SignedIdentifiers` struct. +impl From> for SignedIdentifiers { + fn from(policies: HashMap) -> Self { + if policies.is_empty() { + return SignedIdentifiers { items: None }; + } + + let signed_identifiers: Vec = policies + .into_iter() + .map(|(id, access_policy)| SignedIdentifier { + id: Some(id), + access_policy: Some(access_policy), + }) + .collect(); + + SignedIdentifiers { + items: Some(signed_identifiers), + } + } +} diff --git a/sdk/storage/azure_storage_blob/src/models/mod.rs b/sdk/storage/azure_storage_blob/src/models/mod.rs index cb7eecaa2d..f3cbb851fb 100644 --- a/sdk/storage/azure_storage_blob/src/models/mod.rs +++ b/sdk/storage/azure_storage_blob/src/models/mod.rs @@ -46,7 +46,6 @@ pub use crate::generated::models::{ BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, BlobContainerClientRenewLeaseResultHeaders, BlobContainerClientRestoreResult, BlobContainerClientRestoreResultHeaders, BlobContainerClientSetAccessPolicyOptions, - BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetAccessPolicyResultHeaders, BlobContainerClientSetMetadataOptions, BlobCopySourceTags, BlobDeleteType, BlobExpiryOptions, BlobFlatListSegment, BlobImmutabilityPolicyMode, BlobItemInternal, BlobMetadata, BlobName, BlobPropertiesInternal, BlobServiceClientFindBlobsByTagsOptions, diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index b5f85f5741..0a5c71931d 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -1,12 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use azure_core::{error::ErrorKind, Error}; use std::collections::HashMap; +use std::io::{Error, ErrorKind}; use time::{format_description::FormatItem, macros::format_description, OffsetDateTime, UtcOffset}; -use crate::models::{AccessPolicy, SignedIdentifier, SignedIdentifiers}; - static RFC3339_7: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:7]Z"); @@ -22,7 +20,7 @@ static RFC3339_7: &[FormatItem<'_>] = pub fn format_page_range(offset: u64, length: u64) -> Result { if offset % 512 != 0 { return Err(Error::new( - ErrorKind::DataConversion, + ErrorKind::InvalidInput, format!( "provided offset {} is not aligned to a 512-byte boundary.", offset @@ -31,7 +29,7 @@ pub fn format_page_range(offset: u64, length: u64) -> Result { } if length % 512 != 0 { return Err(Error::new( - ErrorKind::DataConversion, + ErrorKind::InvalidInput, format!( "provided length {} is not aligned to a 512-byte boundary.", offset @@ -53,7 +51,7 @@ pub fn format_page_range(offset: u64, length: u64) -> Result { pub fn format_filter_expression(tags: &HashMap) -> Result { if tags.is_empty() { return Err(Error::new( - ErrorKind::DataConversion, + ErrorKind::InvalidInput, "Tags HashMap cannot be empty.".to_string(), )); } @@ -75,33 +73,8 @@ pub fn format_datetime(datetime: OffsetDateTime) -> Result { let utc = datetime.to_offset(UtcOffset::UTC); utc.format(RFC3339_7).map_err(|e| { Error::new( - ErrorKind::DataConversion, + ErrorKind::InvalidInput, format!("Failed to format datetime: {}", e), ) }) } - -/// Takes a HashMap where the key is the policy ID and the value is the AccessPolicy and converts it to SignedIdentifiers. -/// -/// # Arguments -/// -/// * `policies` - A HashMap where keys are policy identifiers and values are AccessPolicy objects. -pub fn format_signed_identifiers( - policies: HashMap, -) -> Result { - if policies.is_empty() { - return Ok(SignedIdentifiers { items: None }); - } - - let signed_identifiers: Vec = policies - .into_iter() - .map(|(id, access_policy)| SignedIdentifier { - id: Some(id), - access_policy: Some(access_policy), - }) - .collect(); - - Ok(SignedIdentifiers { - items: Some(signed_identifiers), - }) -} diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index e969dd5cad..9caec54b99 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -456,7 +456,7 @@ async fn test_blob_tags(ctx: TestContext) -> Result<(), Box> { // Assert let response_tags = blob_client.get_tags(None).await?.into_model()?; - let map: HashMap = response_tags.try_into()?; + let map: HashMap = response_tags.into(); assert_eq!(blob_tags, map); // Set Tags with No Tags (Clear Tags) @@ -464,7 +464,7 @@ async fn test_blob_tags(ctx: TestContext) -> Result<(), Box> { // Assert let response_tags = blob_client.get_tags(None).await?.into_model()?; - let map: HashMap = response_tags.try_into()?; + let map: HashMap = response_tags.into(); assert_eq!(HashMap::new(), map); container_client.delete_container(None).await?; 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 37394ed646..c612fa5fcf 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs @@ -8,9 +8,9 @@ use azure_storage_blob::models::{ BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientSetMetadataOptions, BlobType, BlockBlobClientUploadOptions, LeaseState, - SignedIdentifier, SignedIdentifiers, + SignedIdentifiers, }; -use azure_storage_blob::{format_datetime, format_filter_expression, format_signed_identifiers}; +use azure_storage_blob::{format_datetime, format_filter_expression}; use azure_storage_blob_test::{ create_test_blob, get_blob_name, get_blob_service_client, get_container_client, get_container_name, @@ -411,65 +411,9 @@ async fn test_container_access_policy(ctx: TestContext) -> Result<(), Box = Some("testid".into()); - let access_policy = AccessPolicy { - expiry: Some(expiry_1.clone()), - permission: Some("rw".to_string()), - start: Some(start_1.clone()), - }; - let signed_identifier = SignedIdentifier { - access_policy: Some(access_policy.clone()), - id: test_id.clone(), - }; - let signed_identifiers = SignedIdentifiers { - items: Some(vec![signed_identifier]), - }; - container_client - .set_access_policy(RequestContent::try_from(signed_identifiers)?, None) - .await?; - - // Assert - let response = container_client.get_access_policy(None).await?; - let signed_identifiers = response.into_model()?; - let response_id = signed_identifiers.items.clone().unwrap()[0].id.clone(); - let response_access_policy = signed_identifiers.items.clone().unwrap()[0] - .access_policy - .clone(); - assert_eq!(signed_identifiers.items.clone().unwrap().len(), 1); - assert_eq!(response_id, test_id); - assert_eq!( - response_access_policy.clone().unwrap().permission, - access_policy.permission - ); - assert_eq!( - response_access_policy.clone().unwrap().expiry, - Some(expiry_1) - ); - assert_eq!(response_access_policy.clone().unwrap().start, Some(start_1)); - // Set Access Policy w/ Multiple Policy Defined - let expiry_2 = recording.var( - "expiry_2", + let expiry = recording.var( + "expiry", Some(VarOptions { default_value: format_datetime(OffsetDateTime::now_utc() + Duration::from_secs(10)) .ok() @@ -477,8 +421,8 @@ async fn test_container_access_policy(ctx: TestContext) -> Result<(), Box Result<(), Box = Some("testid_1".into()); let test_id_2: Option = Some("testid_2".into()); let access_policy_1 = AccessPolicy { - expiry: Some(expiry_2.clone()), + expiry: Some(expiry.clone()), permission: Some("rw".to_string()), - start: Some(start_2.clone()), + start: Some(start.clone()), }; let access_policy_2 = AccessPolicy { - expiry: Some(expiry_2), + expiry: Some(expiry), permission: Some("cd".to_string()), - start: Some(start_2), + start: Some(start), }; let policies: HashMap = HashMap::from([ (test_id_1.clone().unwrap(), access_policy_1.clone()), @@ -504,7 +448,7 @@ async fn test_container_access_policy(ctx: TestContext) -> Result<(), Box Result<(), Box::new().into(); container_client - .set_access_policy( - RequestContent::try_from(format_signed_identifiers(HashMap::new())?)?, - None, - ) + .set_access_policy(RequestContent::try_from(clear_signed_identifiers)?, None) .await?; // Assert diff --git a/sdk/storage/azure_storage_blob/tsp-location.yaml b/sdk/storage/azure_storage_blob/tsp-location.yaml index 4c1adbecf7..4e6f96d309 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: f38fa651becb53a5af9354b7d75948af995d1d5d +commit: da41fad95d462d3f8c1536146c935432784a4622 repo: Azure/azure-rest-api-specs additionalDirectories: From bd4a5e65c5e4b442700f6025138e27e7aa9ead16 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Fri, 14 Nov 2025 14:54:04 -0800 Subject: [PATCH 6/8] Regen against feature branch, CI clean docstring example, changelog entry --- sdk/storage/azure_storage_blob/CHANGELOG.md | 3 ++ .../src/clients/blob_container_client.rs | 33 ++++++++++++------- .../azure_storage_blob/tsp-location.yaml | 2 +- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/sdk/storage/azure_storage_blob/CHANGELOG.md b/sdk/storage/azure_storage_blob/CHANGELOG.md index 90a209e915..d0df36844a 100644 --- a/sdk/storage/azure_storage_blob/CHANGELOG.md +++ b/sdk/storage/azure_storage_blob/CHANGELOG.md @@ -4,6 +4,9 @@ ### Features Added +- Added support for `set_access_policy` to `BlobContainerClient`. +- Added support for `get_access_policy` to `BlobContainerClient`. + ### Breaking Changes ### Bugs Fixed 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 494bf64c81..92bc14d43c 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 @@ -380,18 +380,27 @@ impl BlobContainerClient { /// /// ```rust,no_run /// use std::collections::HashMap; - /// use azure_core::http::RequestContent; - /// use azure_storage_blob::models::{AccessPolicy, SignedIdentifiers}; - /// - /// let mut policies = HashMap::new(); - /// policies.insert("some_policy_id".to_string(), AccessPolicy { - /// start: Some("2035-01-01T00:00:00Z".to_string()), - /// expiry: Some("2035-12-31T00:00:00Z".to_string()), - /// permission: Some("rwd".to_string()), - /// }); - /// - /// container_client.set_access_policy(RequestContent::try_from(SignedIdentifiers::from(policies))?, None).await?; - /// # Ok::<(), Box>(()) + /// use azure_core::{http::RequestContent, Result}; + /// use azure_storage_blob::{BlobContainerClient, models::{AccessPolicy, SignedIdentifiers}}; + /// + /// async fn example() -> Result<()> { + /// let container_client = BlobContainerClient::new( + /// "https://storageaccount.blob.core.windows.net/", + /// "some_container_name", + /// None, + /// None, + /// )?; + /// + /// let mut policies = HashMap::new(); + /// policies.insert("some_policy_id".to_string(), AccessPolicy { + /// start: Some("2035-01-01T00:00:00Z".to_string()), + /// expiry: Some("2035-12-31T00:00:00Z".to_string()), + /// permission: Some("rwd".to_string()), + /// }); + /// + /// container_client.set_access_policy(RequestContent::try_from(SignedIdentifiers::from(policies))?, None).await?; + /// Ok(()) + /// } /// ``` pub async fn set_access_policy( &self, diff --git a/sdk/storage/azure_storage_blob/tsp-location.yaml b/sdk/storage/azure_storage_blob/tsp-location.yaml index 4e6f96d309..71fe8f43f9 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: da41fad95d462d3f8c1536146c935432784a4622 +commit: 81686312917864317ec105512ab9571e06cd073b repo: Azure/azure-rest-api-specs additionalDirectories: From e3661faf970ea3d10cc4be4be52ca9bfaba17729 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Fri, 14 Nov 2025 16:29:41 -0800 Subject: [PATCH 7/8] PR feedback --- sdk/storage/azure_storage_blob/CHANGELOG.md | 2 ++ .../src/clients/blob_container_client.rs | 24 +++++++++---------- sdk/storage/azure_storage_blob/src/parsers.rs | 2 +- .../tests/blob_container_client.rs | 12 ++++++---- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/sdk/storage/azure_storage_blob/CHANGELOG.md b/sdk/storage/azure_storage_blob/CHANGELOG.md index d0df36844a..f8cd30f12f 100644 --- a/sdk/storage/azure_storage_blob/CHANGELOG.md +++ b/sdk/storage/azure_storage_blob/CHANGELOG.md @@ -9,6 +9,8 @@ ### Breaking Changes +- Changed conversion implementation from `BlobTags` to `HashMap` from `TryFrom` to `From`. + ### Bugs Fixed ### Other Changes 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 92bc14d43c..5683cfcbd7 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 @@ -378,27 +378,25 @@ impl BlobContainerClient { /// /// # Example /// - /// ```rust,no_run + /// ```rust, no_run /// use std::collections::HashMap; /// use azure_core::{http::RequestContent, Result}; - /// use azure_storage_blob::{BlobContainerClient, models::{AccessPolicy, SignedIdentifiers}}; - /// - /// async fn example() -> Result<()> { - /// let container_client = BlobContainerClient::new( - /// "https://storageaccount.blob.core.windows.net/", - /// "some_container_name", - /// None, - /// None, - /// )?; + /// use azure_storage_blob::{format_storage_datetime, models::{AccessPolicy, SignedIdentifiers}}; + /// use typespec_client_core::time::OffsetDateTime; + /// use std::time::Duration; /// + /// fn example() -> Result<()> { /// let mut policies = HashMap::new(); /// policies.insert("some_policy_id".to_string(), AccessPolicy { - /// start: Some("2035-01-01T00:00:00Z".to_string()), - /// expiry: Some("2035-12-31T00:00:00Z".to_string()), + /// start: Some(format_storage_datetime(OffsetDateTime::now_utc())?), + /// expiry: Some(format_storage_datetime(OffsetDateTime::now_utc() + Duration::from_secs(10))?), /// permission: Some("rwd".to_string()), /// }); /// - /// container_client.set_access_policy(RequestContent::try_from(SignedIdentifiers::from(policies))?, None).await?; + /// let _request_content = RequestContent::try_from(SignedIdentifiers::from(policies))?; + /// + /// // container_client.set_access_policy(request_content, None).await?; // RequestContent is ready to be used here + /// /// Ok(()) /// } /// ``` diff --git a/sdk/storage/azure_storage_blob/src/parsers.rs b/sdk/storage/azure_storage_blob/src/parsers.rs index 0a5c71931d..cf1105750f 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -69,7 +69,7 @@ pub fn format_filter_expression(tags: &HashMap) -> Result Result { +pub fn format_storage_datetime(datetime: OffsetDateTime) -> Result { let utc = datetime.to_offset(UtcOffset::UTC); utc.format(RFC3339_7).map_err(|e| { Error::new( 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 c612fa5fcf..248ebdf662 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs @@ -10,7 +10,7 @@ use azure_storage_blob::models::{ BlobContainerClientSetMetadataOptions, BlobType, BlockBlobClientUploadOptions, LeaseState, SignedIdentifiers, }; -use azure_storage_blob::{format_datetime, format_filter_expression}; +use azure_storage_blob::{format_filter_expression, format_storage_datetime}; use azure_storage_blob_test::{ create_test_blob, get_blob_name, get_blob_service_client, get_container_client, get_container_name, @@ -415,16 +415,18 @@ async fn test_container_access_policy(ctx: TestContext) -> Result<(), Box Date: Fri, 14 Nov 2025 17:02:09 -0800 Subject: [PATCH 8/8] Make snippet more pointed, add ignore --- .../src/clients/blob_container_client.rs | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) 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 5683cfcbd7..32357892f5 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 @@ -378,27 +378,20 @@ impl BlobContainerClient { /// /// # Example /// - /// ```rust, no_run - /// use std::collections::HashMap; - /// use azure_core::{http::RequestContent, Result}; + /// ```rust, ignore + /// use azure_core::http::RequestContent; /// use azure_storage_blob::{format_storage_datetime, models::{AccessPolicy, SignedIdentifiers}}; /// use typespec_client_core::time::OffsetDateTime; - /// use std::time::Duration; /// - /// fn example() -> Result<()> { - /// let mut policies = HashMap::new(); - /// policies.insert("some_policy_id".to_string(), AccessPolicy { - /// start: Some(format_storage_datetime(OffsetDateTime::now_utc())?), - /// expiry: Some(format_storage_datetime(OffsetDateTime::now_utc() + Duration::from_secs(10))?), - /// permission: Some("rwd".to_string()), - /// }); + /// let mut policies = HashMap::new(); + /// policies.insert("some_policy_id".to_string(), AccessPolicy { + /// start: Some(format_storage_datetime(OffsetDateTime::now_utc())?), + /// expiry: Some(format_storage_datetime(OffsetDateTime::now_utc() + Duration::from_secs(10))?), + /// permission: Some("rwd".to_string()), + /// }); /// - /// let _request_content = RequestContent::try_from(SignedIdentifiers::from(policies))?; - /// - /// // container_client.set_access_policy(request_content, None).await?; // RequestContent is ready to be used here - /// - /// Ok(()) - /// } + /// let request_content = RequestContent::try_from(SignedIdentifiers::from(policies))?; + /// container_client.set_access_policy(request_content, None).await?; /// ``` pub async fn set_access_policy( &self,