diff --git a/Cargo.lock b/Cargo.lock index 791b60afc6..03a0a29c97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -516,6 +516,7 @@ dependencies = [ "futures", "serde", "serde_json", + "time", "tokio", "tracing", "typespec_client_core", @@ -1637,9 +1638,9 @@ dependencies = [ [[package]] name = "include-file" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b82360a1cfc38a30a967d4e1b58745ff9258c9326772293402d88d6dd201229" +checksum = "4db0c2483923cdc1a04d371af232e93215bef3d6a761b663870dd2db4ca795a9" dependencies = [ "proc-macro2", "syn 2.0.110", 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/CHANGELOG.md b/sdk/storage/azure_storage_blob/CHANGELOG.md index 90a209e915..f8cd30f12f 100644 --- a/sdk/storage/azure_storage_blob/CHANGELOG.md +++ b/sdk/storage/azure_storage_blob/CHANGELOG.md @@ -4,8 +4,13 @@ ### Features Added +- Added support for `set_access_policy` to `BlobContainerClient`. +- Added support for `get_access_policy` to `BlobContainerClient`. + ### 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/Cargo.toml b/sdk/storage/azure_storage_blob/Cargo.toml index 0e0d67aa08..b65190c98d 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 dae255a41b..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_1e5e3b2c6c", + "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 e8175de936..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 @@ -9,12 +9,13 @@ 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, + BlobContainerClientSetMetadataOptions, SignedIdentifiers, }, models::{FilterBlobSegment, ListBlobsFlatSegmentResponse, StorageErrorCode}, pipeline::StorageHeadersPolicy, @@ -25,7 +26,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 +366,51 @@ 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. 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, ignore + /// use azure_core::http::RequestContent; + /// use azure_storage_blob::{format_storage_datetime, models::{AccessPolicy, SignedIdentifiers}}; + /// use typespec_client_core::time::OffsetDateTime; + /// + /// 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?; + /// ``` + 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..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, - SignedIdentifier, + BlobContainerClientSetMetadataOptions, FilterBlobSegment, ListBlobsFlatSegmentResponse, + ListBlobsHierarchySegmentResponse, SignedIdentifiers, }; use azure_core::{ credentials::TokenCredential, @@ -522,40 +521,39 @@ 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); - /// } /// 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::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 + /// * [`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(); @@ -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, XmlFormat>, + 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 1f97273c52..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, @@ -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, @@ -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 @@ -3583,42 +3534,62 @@ 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); +/// } +/// if let Some(access) = response.access()? { +/// println!("x-ms-blob-public-access: {:?}", access); /// } /// Ok(()) /// } /// ``` -pub trait StorageServiceStatsHeaders: private::Sealed { - fn date(&self) -> Result>; +pub trait SignedIdentifiersHeaders: private::Sealed { + fn last_modified(&self) -> Result>; + fn etag(&self) -> Result>; + fn access(&self) -> Result>; } -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())) +impl SignedIdentifiersHeaders for Response { + /// 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 +3597,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 { @@ -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, - SignedIdentifier, 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 {} @@ -3770,7 +3714,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 98b7338004..c2eb3ec4cd 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()` @@ -203,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] @@ -1302,6 +1288,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/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 a33510fc8c..f3cbb851fb 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,16 +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, - BlobContainerClientSetAccessPolicyResult, BlobContainerClientSetAccessPolicyResultHeaders, + BlobContainerClientFindBlobsByTagsOptions, BlobContainerClientGetAccessPolicyOptions, + BlobContainerClientGetAccountInfoOptions, BlobContainerClientGetAccountInfoResult, + BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesOptions, + BlobContainerClientGetPropertiesResult, BlobContainerClientGetPropertiesResultHeaders, + BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions, + BlobContainerClientReleaseLeaseResult, BlobContainerClientReleaseLeaseResultHeaders, + BlobContainerClientRenameResult, BlobContainerClientRenameResultHeaders, + BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult, + BlobContainerClientRenewLeaseResultHeaders, BlobContainerClientRestoreResult, + BlobContainerClientRestoreResultHeaders, BlobContainerClientSetAccessPolicyOptions, BlobContainerClientSetMetadataOptions, BlobCopySourceTags, BlobDeleteType, BlobExpiryOptions, BlobFlatListSegment, BlobImmutabilityPolicyMode, BlobItemInternal, BlobMetadata, BlobName, BlobPropertiesInternal, BlobServiceClientFindBlobsByTagsOptions, @@ -79,7 +79,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, SignedIdentifiersHeaders, 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..cf1105750f 100644 --- a/sdk/storage/azure_storage_blob/src/parsers.rs +++ b/sdk/storage/azure_storage_blob/src/parsers.rs @@ -3,6 +3,10 @@ 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. @@ -59,3 +63,18 @@ 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::InvalidInput, + format!("Failed to format datetime: {}", e), + ) + }) +} 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 78a38b5134..248ebdf662 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs @@ -2,14 +2,15 @@ // 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_test::{recorded, Matcher, TestContext, TestMode, VarOptions}; use azure_storage_blob::models::{ - AccountKind, BlobContainerClientAcquireLeaseResultHeaders, + AccessPolicy, AccountKind, BlobContainerClientAcquireLeaseResultHeaders, BlobContainerClientChangeLeaseResultHeaders, BlobContainerClientGetAccountInfoResultHeaders, BlobContainerClientGetPropertiesResultHeaders, BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientSetMetadataOptions, BlobType, BlockBlobClientUploadOptions, LeaseState, + SignedIdentifiers, }; +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, @@ -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,94 @@ 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/ Multiple Policy Defined + let expiry = recording.var( + "expiry", + Some(VarOptions { + default_value: format_storage_datetime( + OffsetDateTime::now_utc() + Duration::from_secs(10), + ) + .ok() + .map(Into::into), + ..Default::default() + }), + ); + let start = recording.var( + "start", + Some(VarOptions { + default_value: format_storage_datetime(OffsetDateTime::now_utc()) + .ok() + .map(Into::into), + ..Default::default() + }), + ); + let test_id_1: Option = Some("testid_1".into()); + let test_id_2: Option = Some("testid_2".into()); + let access_policy_1 = AccessPolicy { + expiry: Some(expiry.clone()), + permission: Some("rw".to_string()), + start: Some(start.clone()), + }; + let access_policy_2 = AccessPolicy { + expiry: Some(expiry), + permission: Some("cd".to_string()), + start: Some(start), + }; + 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(SignedIdentifiers::from(policies))?, + None, + ) + .await?; + + // Assert + let response = container_client.get_access_policy(None).await?; + let signed_identifiers = response.into_model()?; + + let mut remaining_count = 2; + for signed_identifier in signed_identifiers.items.as_ref().unwrap() { + // Check ID matches one of the expected IDs from set_access_policy + assert!([&test_id_1, &test_id_2].contains(&&signed_identifier.id)); + + if let Some(access_policy) = &signed_identifier.access_policy { + // Check permission, start, and expiry match one of the expected values from set_access_policy + assert!([&access_policy_1.permission, &access_policy_2.permission] + .contains(&&access_policy.permission)); + assert!( + [&access_policy_1.expiry, &access_policy_2.expiry].contains(&&access_policy.expiry) + ); + assert!( + [&access_policy_1.start, &access_policy_2.start].contains(&&access_policy.start) + ); + } + + remaining_count -= 1; + } + assert_eq!(remaining_count, 0); + + // Clear Access Policy + let clear_signed_identifiers: SignedIdentifiers = HashMap::::new().into(); + container_client + .set_access_policy(RequestContent::try_from(clear_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..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: 6b5a0fdadca03abe0b8e91a68a62cd1e639ed55e +commit: 81686312917864317ec105512ab9571e06cd073b repo: Azure/azure-rest-api-specs additionalDirectories: