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 1c2564d279..7e12904ad7 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -83,6 +83,48 @@ impl BlobClient { }) } + pub fn from_blob_url(blob_url: &str, 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); + + // TODO: We would abstract this out to a helper that is fancier and can handle edge-cases, which Url crate may not have been designed to handle + // Parse out container name and blob name, then remove them from the path + let mut container_name = ""; + let mut blob_name = ""; + let mut url = Url::parse(blob_url)?; + // URL BEFORE: https://vincenttranstock.blob.core.windows.net/acontainer108f32e8/goodbye.txt?sp=racwd&st=2025-07-21T21:22:28Z&se=2025-07-22T05:37:28Z&spr=https&sv=2024-11-04&sr=b&sig + // println!("URL BEFORE: {}", url.clone()); + + let url_for_path_segments = url.clone(); + if let Some(mut segments) = url_for_path_segments.path_segments() { + container_name = segments.next().unwrap(); + blob_name = segments.next().unwrap(); + } + url.set_path(""); + // URL AFTER NULLED PATH: https://vincenttranstock.blob.core.windows.net/?sp=racwd&st=2025-07-21T21:22:28Z&se=2025-07-22T05:37:28Z&spr=https&sv=2024-11-04&sr=b&sig + // println!("URL AFTER NULLED PATH: {}", url.clone()); + + // Therefore, given the current design, the endpoint passed to the generated client will have query parameters on it. + let client = GeneratedBlobClient::new_no_credential( + url.as_str(), + container_name.to_string(), + blob_name.to_string(), + Some(options), + )?; + + // Consequently that means currently endpoint() will have all the query parameters in it. + // We could strip it out here before saving on this client, but that would mean a mismatch of endpoint values between our client and generated. + Ok(Self { + endpoint: url, + client, + }) + } + /// Returns a new instance of AppendBlobClient. /// /// # Arguments 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 9dac2880bf..b0de2925b1 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 @@ -102,6 +102,35 @@ impl BlobClient { }) } + pub fn new_no_credential( + endpoint: &str, + container_name: String, + blob_name: String, + options: Option, + ) -> Result { + let options = options.unwrap_or_default(); + let endpoint = Url::parse(endpoint)?; + if !endpoint.scheme().starts_with("http") { + return Err(azure_core::Error::message( + azure_core::error::ErrorKind::Other, + format!("{endpoint} must use http(s)"), + )); + } + Ok(Self { + blob_name, + container_name, + endpoint, + version: options.version, + pipeline: Pipeline::new( + option_env!("CARGO_PKG_NAME"), + option_env!("CARGO_PKG_VERSION"), + options.client_options, + Vec::default(), + Vec::default(), + ), + }) + } + /// Returns the Url associated with this client. pub fn endpoint(&self) -> &Url { &self.endpoint @@ -770,10 +799,20 @@ impl BlobClient { 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 path = String::from("{containerName}/{blobName}"); + // path = path.replace("{blobName}", &self.blob_name); + // path = path.replace("{containerName}", &self.container_name); + // url = url.join(&path)?; + + // request uri before: https://vincenttranstock.blob.core.windows.net/?sp=racwd&st=2025-07-21T21:22:28Z&se=2025-07-22T05:37:28Z&spr=https&sv=2024-11-04&sr=b&sig= + // println!("request uri before: {}", url); + { + url.path_segments_mut() + .expect("Cannot be base") + .extend([&self.container_name, &self.blob_name]); + } + // request uri after: https://vincenttranstock.blob.core.windows.net/acontainer108f32e8/goodbye.txt?sp=racwd&st=2025-07-21T21:22:28Z&se=2025-07-22T05:37:28Z&spr=https&sv=2024-11-04&sr=b&sig= + // println!("request uri after: {}", url); if let Some(snapshot) = options.snapshot { url.query_pairs_mut().append_pair("snapshot", &snapshot); } diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index 5d5818b2f9..cdff30225c 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -6,12 +6,15 @@ use azure_core::{ Bytes, }; use azure_core_test::{recorded, TestContext}; -use azure_storage_blob::models::{ - AccessTier, BlobClientAcquireLeaseResultHeaders, BlobClientChangeLeaseResultHeaders, - BlobClientDownloadOptions, BlobClientDownloadResultHeaders, BlobClientGetPropertiesOptions, - BlobClientGetPropertiesResultHeaders, BlobClientSetMetadataOptions, - BlobClientSetPropertiesOptions, BlobClientSetTierOptions, BlockBlobClientUploadOptions, - LeaseState, +use azure_storage_blob::{ + models::{ + AccessTier, BlobClientAcquireLeaseResultHeaders, BlobClientChangeLeaseResultHeaders, + BlobClientDownloadOptions, BlobClientDownloadResultHeaders, BlobClientGetPropertiesOptions, + BlobClientGetPropertiesResultHeaders, BlobClientSetMetadataOptions, + BlobClientSetPropertiesOptions, BlobClientSetTierOptions, BlockBlobClientUploadOptions, + LeaseState, + }, + BlobClient, }; use azure_storage_blob_test::{create_test_blob, get_blob_name, get_container_client}; use std::{collections::HashMap, error::Error, time::Duration}; @@ -423,3 +426,17 @@ async fn test_leased_blob_operations(ctx: TestContext) -> Result<(), Box Result<(), Box> { + // SAS + let blob_url = ""; + + let sas_blob_client = BlobClient::from_blob_url(blob_url, None)?; + + let blob_properties = sas_blob_client.get_properties(None).await?; + let content_length = blob_properties.content_length()?; + assert_eq!(11, content_length.unwrap()); + + Ok(()) +}