From ff0f6b0316005adb3d288688f1fd4e64ec45fb2d Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 26 Aug 2025 13:58:47 -0700 Subject: [PATCH 1/2] First draft --- .../src/clients/blob_client.rs | 105 ++++++++++++++---- .../src/generated/clients/blob_client.rs | 23 ++-- 2 files changed, 92 insertions(+), 36 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index 877d7bf871..5c37644a6b 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -26,16 +26,55 @@ use azure_core::{ credentials::TokenCredential, http::{ policies::{BearerTokenCredentialPolicy, Policy}, - JsonFormat, NoFormat, RequestContent, Response, Url, XmlFormat, + JsonFormat, NoFormat, Pipeline, RequestContent, Response, Url, XmlFormat, }, Bytes, Result, }; use std::collections::HashMap; use std::sync::Arc; +impl GeneratedBlobClient { + fn from_url( + blob_url: &str, + credential: Option>, + options: Option, + ) -> Result { + let options = options.unwrap_or_default(); + + let pipeline = match credential { + Some(cred) => { + let auth_policy: Arc = Arc::new(BearerTokenCredentialPolicy::new( + cred, + vec!["https://storage.azure.com/.default"], + )); + Pipeline::new( + option_env!("CARGO_PKG_NAME"), + option_env!("CARGO_PKG_VERSION"), + options.client_options, + Vec::default(), + vec![auth_policy], + ) + } + None => Pipeline::new( + option_env!("CARGO_PKG_NAME"), + option_env!("CARGO_PKG_VERSION"), + options.client_options, + Vec::default(), + Vec::default(), + ), + }; + + Ok(Self { + blob_url: Url::parse(blob_url)?, + version: options.version, + pipeline, + }) + } +} + /// A client to interact with a specific Azure storage blob, although that blob may not yet exist. pub struct BlobClient { - pub(super) endpoint: Url, + pub(super) blob_url: Url, pub(super) client: GeneratedBlobClient, } @@ -47,13 +86,13 @@ impl BlobClient { /// * `endpoint` - The full URL of the Azure storage account, for example `https://myaccount.blob.core.windows.net/` /// * `container_name` - The name of the container containing this blob. /// * `blob_name` - The name of the blob to interact with. - /// * `credential` - An implementation of [`TokenCredential`] that can provide an Entra ID token to use when authenticating. + /// * `credential` - Optional. An implementation of [`TokenCredential`] that can provide an Entra ID token to use when authenticating. /// * `options` - Optional configuration for the client. pub fn new( endpoint: &str, container_name: String, blob_name: String, - credential: Arc, + credential: Option>, options: Option, ) -> Result { let mut options = options.unwrap_or_default(); @@ -64,15 +103,41 @@ impl BlobClient { .per_call_policies .push(storage_headers_policy); - let client = GeneratedBlobClient::new( - endpoint, - credential, - container_name, - blob_name, - Some(options), - )?; + let mut url = Url::parse(endpoint)?; + if !url.scheme().starts_with("http") { + return Err(azure_core::Error::message( + azure_core::error::ErrorKind::Other, + format!("{url} must use http(s)"), + )); + } + + // Build Blob URL, Url crate handles encoding only path params + url.path_segments_mut() + .expect("Cannot be base") + .extend([&container_name, &blob_name]); + + let client = GeneratedBlobClient::from_url(url.as_str(), credential, Some(options))?; + Ok(Self { + blob_url: client.blob_url().clone(), + client, + }) + } + + pub fn from_blob_url( + blob_url: &str, + credential: Option>, + options: Option, + ) -> Result { + let mut options = options.unwrap_or_default(); + let storage_headers_policy = Arc::new(StorageHeadersPolicy); + options + .client_options + .per_call_policies + .push(storage_headers_policy); + let url = Url::parse(blob_url)?; + let client = GeneratedBlobClient::from_url(url.as_str(), credential, Some(options))?; Ok(Self { - endpoint: endpoint.parse()?, + blob_url: client.blob_url().clone(), client, }) } @@ -83,7 +148,7 @@ impl BlobClient { /// pub fn append_blob_client(&self) -> AppendBlobClient { AppendBlobClient { - endpoint: self.client.endpoint.clone(), + blob_url: self.client.blob_url.clone(), client: self.client.get_append_blob_client(), } } @@ -94,7 +159,7 @@ impl BlobClient { /// pub fn block_blob_client(&self) -> BlockBlobClient { BlockBlobClient { - endpoint: self.client.endpoint.clone(), + blob_url: self.client.blob_url.clone(), client: self.client.get_block_blob_client(), } } @@ -105,24 +170,24 @@ impl BlobClient { /// pub fn page_blob_client(&self) -> PageBlobClient { PageBlobClient { - endpoint: self.client.endpoint.clone(), + blob_url: self.client.blob_url.clone(), client: self.client.get_page_blob_client(), } } - /// Gets the endpoint of the Storage account this client is connected to. - pub fn endpoint(&self) -> &Url { - &self.endpoint + /// Gets the blob_url of the Storage blob this client is connected to. + pub fn blob_url(&self) -> &Url { + &self.blob_url } /// Gets the container name of the Storage account this client is connected to. pub fn container_name(&self) -> &str { - &self.client.container_name + // Add logic that would parse out container name from blob_url } /// Gets the blob name of the Storage account this client is connected to. pub fn blob_name(&self) -> &str { - &self.client.blob_name + // Add logic that would parse out blob_name name from blob_url } /// Returns all user-defined metadata, standard HTTP properties, and system properties for the blob. diff --git a/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs index 8fa0188d8f..814d9a4e2e 100644 --- a/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs @@ -37,13 +37,11 @@ use azure_core::{ time::to_rfc7231, tracing, Error, Result, }; -use std::sync::Arc; +use std::{str::FromStr, sync::Arc}; #[tracing::client] pub struct BlobClient { - pub(crate) blob_name: String, - pub(crate) container_name: String, - pub(crate) endpoint: Url, + pub(crate) blob_url: Url, pub(crate) pipeline: Pipeline, pub(crate) version: String, } @@ -72,8 +70,7 @@ impl BlobClient { pub fn new( endpoint: &str, credential: Arc, - container_name: String, - blob_name: String, + blob_url: String, options: Option, ) -> Result { let options = options.unwrap_or_default(); @@ -90,9 +87,7 @@ impl BlobClient { vec!["https://storage.azure.com/.default"], )); Ok(Self { - blob_name, - container_name, - endpoint, + blob_url: Url::from_str(&blob_url)?, version: options.version, pipeline: Pipeline::new( option_env!("CARGO_PKG_NAME"), @@ -105,8 +100,8 @@ impl BlobClient { } /// Returns the Url associated with this client. - pub fn endpoint(&self) -> &Url { - &self.endpoint + pub fn blob_url(&self) -> &Url { + &self.blob_url } /// The Abort Copy From URL operation aborts a pending Copy From URL operation, and leaves a destination blob with zero length @@ -885,11 +880,7 @@ impl BlobClient { ) -> Result> { let options = options.unwrap_or_default(); let ctx = Context::with_context(&options.method_options.context); - let mut url = self.endpoint.clone(); - let mut path = String::from("{containerName}/{blobName}"); - path = path.replace("{blobName}", &self.blob_name); - path = path.replace("{containerName}", &self.container_name); - url = url.join(&path)?; + let mut url = self.blob_url.clone(); if let Some(snapshot) = options.snapshot { url.query_pairs_mut().append_pair("snapshot", &snapshot); } From 8b66679b489630b54ecd3ad52977578c7129f442 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 26 Aug 2025 15:44:44 -0700 Subject: [PATCH 2/2] Clear up confusion in generated diff --- .../azure_storage_blob/src/generated/clients/blob_client.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs index 814d9a4e2e..25ce7268ce 100644 --- a/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs @@ -37,7 +37,7 @@ use azure_core::{ time::to_rfc7231, tracing, Error, Result, }; -use std::{str::FromStr, sync::Arc}; +use std::sync::Arc; #[tracing::client] pub struct BlobClient { @@ -67,6 +67,8 @@ impl BlobClient { /// * `blob_name` - The name of the blob. /// * `options` - Optional configuration for the client. #[tracing::new("azure_storage_blob")] + // Ideally we would like to remove this entirely / obscure it, unless we can replace it with just a matching implementation to from_url() we impl to this client from our handwritten BlobClient + // We will not use it. pub fn new( endpoint: &str, credential: Arc, @@ -87,7 +89,7 @@ impl BlobClient { vec!["https://storage.azure.com/.default"], )); Ok(Self { - blob_url: Url::from_str(&blob_url)?, + blob_url: Url::parse(&blob_url)?, version: options.version, pipeline: Pipeline::new( option_env!("CARGO_PKG_NAME"),