Skip to content
42 changes: 42 additions & 0 deletions sdk/storage/azure_storage_blob/src/clients/blob_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,48 @@ impl BlobClient {
})
}

pub fn from_blob_url(blob_url: &str, options: Option<BlobClientOptions>) -> Result<Self> {
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
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 23 additions & 6 deletions sdk/storage/azure_storage_blob/tests/blob_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -423,3 +426,17 @@ async fn test_leased_blob_operations(ctx: TestContext) -> Result<(), Box<dyn Err
container_client.delete_container(None).await?;
Ok(())
}

#[recorded::test]
async fn test_sassy(ctx: TestContext) -> Result<(), Box<dyn Error>> {
// SAS
let blob_url = "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=e7QwWlj42eY%2FqDMSdSppRqSSqlkinW00%2Bymu03LAuek%3D";

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(())
}
Loading