Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -143,6 +143,48 @@ impl BlobClient {
Ok(Self { client })
}

/// Creates a new BlobClient targeting a specific blob version.
///
/// # Arguments
///
/// * `version_id` - The version ID of the blob to target.
pub fn with_version_id(&self, version_id: &str) -> Result<Self> {
let mut versioned_endpoint = self.client.endpoint.clone();
versioned_endpoint
.query_pairs_mut()
.append_pair("versionid", version_id);

Ok(Self {
client: GeneratedBlobClient {
endpoint: versioned_endpoint,
pipeline: self.client.pipeline.clone(),
version: self.client.version.clone(),
tracer: self.client.tracer.clone(),
},
})
}

/// Creates a new BlobClient targeting a specific blob snapshot.
///
/// # Arguments
///
/// * `snapshot` - The snapshot ID of the blob to target.
pub fn with_snapshot(&self, snapshot: &str) -> Result<Self> {
let mut snapshot_endpoint = self.client.endpoint.clone();
snapshot_endpoint
.query_pairs_mut()
.append_pair("snapshot", snapshot);

Ok(Self {
client: GeneratedBlobClient {
endpoint: snapshot_endpoint,
pipeline: self.client.pipeline.clone(),
version: self.client.version.clone(),
tracer: self.client.tracer.clone(),
},
})
}

/// Returns a new instance of AppendBlobClient.
pub fn append_blob_client(&self) -> AppendBlobClient {
AppendBlobClient {
Expand Down
49 changes: 48 additions & 1 deletion sdk/storage/azure_storage_blob/tests/blob_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use azure_core_test::{recorded, Matcher, TestContext};
use azure_storage_blob::{
models::{
AccessTier, AccountKind, BlobClientAcquireLeaseResultHeaders,
BlobClientChangeLeaseResultHeaders, BlobClientDownloadOptions,
BlobClientChangeLeaseResultHeaders, BlobClientDeleteOptions, BlobClientDownloadOptions,
BlobClientDownloadResultHeaders, BlobClientGetAccountInfoResultHeaders,
BlobClientGetPropertiesOptions, BlobClientGetPropertiesResultHeaders,
BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlobClientSetTierOptions,
Expand Down Expand Up @@ -644,3 +644,50 @@ async fn test_encoding_edge_cases(ctx: TestContext) -> Result<(), Box<dyn Error>

Ok(())
}

#[recorded::test]
async fn test_delete_blob_multiple_versions_issue(ctx: TestContext) -> Result<(), Box<dyn Error>> {
// Recording Setup
let recording = ctx.recording();
let container_client = get_container_client(recording, true).await?;
let blob_client = container_client.blob_client(&get_blob_name(recording));

// Create Test Blob
create_test_blob(&blob_client, None, None).await?;

// Get Old Version
let response = blob_client.get_properties(None).await?;
let old_version = response.version_id()?;
println!("Old Version ID: {:?}", old_version); // Some("2025-11-03T20:26:15.3021299Z")

// Generate New Version by Overwriting Blob
create_test_blob(
&blob_client,
Some(RequestContent::from("overwrite original contents".into())),
None,
)
.await?;

// Get New Version
let response = blob_client.get_properties(None).await?;
let new_version = response.version_id()?;
println!("New Version ID: {:?}", new_version); // Some("2025-11-03T20:26:15.3800869Z")

// Get a version-aware BlobClient for Old Version
let old_version_blob_client = blob_client.with_version_id(&old_version.unwrap())?;

// Call delete on old version BlobClient while also providing new version ID in options bag
let blob_client_delete_options = BlobClientDeleteOptions {
version_id: Some(new_version.unwrap()),
..Default::default()
};

// Observe request in Fiddler
old_version_blob_client
.delete(Some(blob_client_delete_options))
.await?;
// DELETE /containernjznmvgq/blobeexqko1s?versionid=2025-11-03T20%3A26%3A15.3021299Z&versionid=2025-11-03T20%3A26%3A15.3800869Z HTTP/1.1

container_client.delete_container(None).await?;
Ok(())
}
Loading