Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f7626db
Clean building
vincenttran-msft Oct 2, 2025
5442ea3
Use macro with caveat- now pub fn on underlying generated client
vincenttran-msft Oct 3, 2025
7338e3a
Refactor that any URLs are Url type
vincenttran-msft Oct 10, 2025
e48e295
Remaining Qs on decoding in helper, '/' '\' consistency
vincenttran-msft Oct 13, 2025
12d463d
building, tests not building
vincenttran-msft Oct 14, 2025
de1223b
Flat refactor applied to all clients, all tests passing, cargo clippy…
vincenttran-msft Oct 14, 2025
aabed60
Regens, but broken on some http concepts that likely got moved
vincenttran-msft Oct 17, 2025
784cb7c
Merge branch 'main' into vincenttran/flattened_clients
vincenttran-msft Oct 17, 2025
aaaeedd
Main crate no compiler errors
vincenttran-msft Oct 17, 2025
cde3fc5
Fix eventhubs and azure_storage_blob_test code after refactor
vincenttran-msft Oct 17, 2025
f6328f3
Refactor all test code to reflect refactor, all test cases passing
vincenttran-msft Oct 17, 2025
538347b
fix lint
vincenttran-msft Oct 17, 2025
6cb2a9f
Checkpoint store README
vincenttran-msft Oct 17, 2025
40d5149
one more nit
vincenttran-msft Oct 18, 2025
c2f7f5f
EventHubs test working in record, pushing working asset + working in …
vincenttran-msft Oct 20, 2025
507d3c1
Add test recordings for encoding_edge_cases, remove public access tes…
vincenttran-msft Oct 21, 2025
f4944e7
refactor
vincenttran-msft Oct 21, 2025
363147f
Add new changelog entries, PR feedback, and add list_blobs name asser…
vincenttran-msft Oct 22, 2025
8373688
nit on test name
vincenttran-msft Oct 22, 2025
f591773
Merge branch 'main' into vincenttran/flattened_clients
vincenttran-msft Oct 22, 2025
dec3d88
Fix list blobs test after merging main in
vincenttran-msft Oct 22, 2025
ec30aff
Recording to match nit
vincenttran-msft Oct 22, 2025
27d6e5c
PR feedback, improve encoding test case
vincenttran-msft Oct 22, 2025
ca19acd
Address U+FFFF and U+FFFE concerns with try_from helper for easy to u…
vincenttran-msft Oct 24, 2025
6ad4794
Analyze nits, add changelog entry for BlobName TryFrom impl
vincenttran-msft Oct 24, 2025
b951650
Remove unicode edge cases, will address in seperate PR to fix-up list…
vincenttran-msft Oct 24, 2025
c1bb718
nit
vincenttran-msft Oct 24, 2025
3b921e1
Some PR feedbacks
vincenttran-msft Oct 25, 2025
9a55d0e
Regen after merging to feature/blob-tsp-rust
vincenttran-msft Oct 28, 2025
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
392 changes: 192 additions & 200 deletions eng/emitter-package-lock.json

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions eng/emitter-package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"main": "dist/src/index.js",
"dependencies": {
"@azure-tools/typespec-rust": "0.24.0"
"@azure-tools/typespec-rust": "0.24.1"
},
"devDependencies": {
"@azure-tools/typespec-azure-core": "0.60",
"@azure-tools/typespec-azure-rulesets": "0.60",
"@azure-tools/typespec-client-generator-core": "0.60",
"@typespec/compiler": "1.4",
"@typespec/http": "1.4",
"@typespec/openapi": "1.4",
"@typespec/rest": "0.74",
"@typespec/versioning": "0.74",
"@typespec/xml": "0.74"
"@azure-tools/typespec-azure-core": "~0.61",
"@azure-tools/typespec-azure-rulesets": "~0.61",
"@azure-tools/typespec-client-generator-core": ">=0.61.1",
"@typespec/compiler": "^1.4.0",
"@typespec/http": "^1.4.0",
"@typespec/openapi": "^1.4.0",
"@typespec/rest": "~0.75.0",
"@typespec/versioning": "~0.75.0",
"@typespec/xml": "~0.75.0"
}
}
}
2 changes: 1 addition & 1 deletion sdk/eventhubs/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "rust",
"TagPrefix": "rust/eventhubs",
"Tag": "rust/eventhubs_12266aa668"
"Tag": "rust/eventhubs_ad9b84912d"
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let credential = DeveloperToolsCredential::new(None)?;
let blob_client = BlobContainerClient::new(
"https://yourstorageaccount.blob.core.windows.net",
"yourcontainername".to_string(),
credential.clone(),
"yourcontainername",
Some(credential.clone()),
None,
)?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Instantiate a blob client with OpenTelemetry instrumentation enabled
let blob_container_client = BlobContainerClient::new(
&storage_account_url,
container,
credential,
&container,
Some(credential),
Some(BlobContainerClientOptions {
client_options: ClientOptions {
instrumentation: InstrumentationOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let credential = DeveloperToolsCredential::new(None)?;
let blob_container_client = BlobContainerClient::new(
&storage_account_url,
container_name,
credential.clone(),
&container_name,
Some(credential.clone()),
None,
)?;
let consumer = ConsumerClient::builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,7 @@ impl BlobCheckpointStore {
blob_name: &str,
metadata: HashMap<String, String>,
) -> Result<(Option<OffsetDateTime>, Option<Etag>)> {
let blob_client = self
.blob_container_client
.blob_client(blob_name.to_string());
let blob_client = self.blob_container_client.blob_client(blob_name);

let result = blob_client.set_metadata(metadata.clone(), None).await;
match result {
Expand Down Expand Up @@ -106,9 +104,7 @@ impl BlobCheckpointStore {
metadata: Option<HashMap<String, String>>,
etag: Option<Etag>,
) -> Result<(Option<OffsetDateTime>, Option<Etag>)> {
let blob_client = self
.blob_container_client
.blob_client(blob_name.to_string());
let blob_client = self.blob_container_client.blob_client(blob_name);

if etag.is_some() {
debug!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pub fn create_test_checkpoint_store(recording: &Recording) -> Result<Arc<BlobChe
recording.instrument(&mut options.client_options);
let blob_container_client = BlobContainerClient::new(
&recording.var("AZURE_STORAGE_BLOB_ENDPOINT", None),
recording.var("AZURE_STORAGE_BLOB_CONTAINER", None),
credential.clone(),
&recording.var("AZURE_STORAGE_BLOB_CONTAINER", None),
Some(credential),
Some(options),
)?;

Expand Down
5 changes: 3 additions & 2 deletions sdk/storage/.dict.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ appendpos
blockid
blocklist
blocklisttype
Btext
copyid
deletedwithversions
deletetype
Expand All @@ -12,7 +13,9 @@ immutabilitypolicy
incrementalcopy
legalhold
missingcontainer
numofmessages
pagelist
peekonly
policyid
prevsnapshot
RAGRS
Expand All @@ -27,5 +30,3 @@ testcontainer
uncommittedblobs
westus
yourtagname
numofmessages
peekonly
13 changes: 13 additions & 0 deletions sdk/storage/azure_storage_blob/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,21 @@

### Features Added

- Added support for client construction directly from URLs:
- `AppendBlobClient::from_url()`
- `BlobClient::from_url()`
- `BlobContainerClient::from_url()`
- `BlockBlobClient::from_url()`
- `PageBlobClient::from_url()`
- Added support for SAS (shared access signature) URLs via the new `from_url()` methods.

### Breaking Changes

- Removed the `container_name()` and `blob_name()` accessors on relevant clients.
- Removed the `endpoint` struct field on all clients, as this value is now returned directly from the underlying generated client.
- Changed the `container_name` and `blob_name` parameters from owned `String` to `&str` reference on relevant client constructor methods (`new()`).
- The `credential` parameter is now `Optional` on `new()` client constructors, allowing for construction of public access clients.

### Bugs Fixed

### Other Changes
Expand Down
24 changes: 12 additions & 12 deletions sdk/storage/azure_storage_blob/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let credential = DeveloperToolsCredential::new(None)?;
let blob_client = BlobClient::new(
"https://<storage_account_name>.blob.core.windows.net/", // endpoint
"container_name".to_string(), // container name
"blob_name".to_string(), // blob name
credential, // credential
"container_name", // container name
"blob_name", // blob name
Some(credential), // credential
Some(BlobClientOptions::default()), // BlobClient options
)?;
Ok(())
Expand All @@ -75,9 +75,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let credential = DeveloperToolsCredential::new(None)?;
let blob_client = BlobClient::new(
"https://<storage_account_name>.blob.core.windows.net/", // endpoint
"container_name".to_string(), // container name
"blob_name".to_string(), // blob name
credential, // credential
"container_name", // container name
"blob_name", // blob name
Some(credential), // credential
Some(BlobClientOptions::default()), // BlobClient options
)?;
Ok(())
Expand All @@ -96,9 +96,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let credential = DeveloperToolsCredential::new(None)?;
let blob_client = BlobClient::new(
"https://<storage_account_name>.blob.core.windows.net/",
"container_name".to_string(),
"blob_name".to_string(),
credential,
"container_name",
"blob_name",
Some(credential),
Some(BlobClientOptions::default()),
)?;

Expand Down Expand Up @@ -127,9 +127,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let credential = DeveloperToolsCredential::new(None)?;
let blob_client = BlobClient::new(
"https://<storage_account_name>.blob.core.windows.net/",
"container_name".to_string(),
"blob_name".to_string(),
credential,
"container_name",
"blob_name",
Some(credential),
Some(BlobClientOptions::default()),
)?;
let blob_properties = blob_client.get_properties(
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/azure_storage_blob/assets.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "rust",
"Tag": "rust/azure_storage_blob_4dd8ebabce",
"Tag": "rust/azure_storage_blob_1e5e3b2c6c",
"TagPrefix": "rust/azure_storage_blob"
}
4 changes: 2 additions & 2 deletions sdk/storage/azure_storage_blob/perf/list_blob_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl PerfTest for ListBlobTest {
),
};
println!("Using endpoint: {}", endpoint);
let client = BlobContainerClient::new(&endpoint, container_name, credential, None)?;
let client = BlobContainerClient::new(&endpoint, &container_name, Some(credential), None)?;
self.client.set(client).map_err(|_| {
azure_core::Error::with_message(ErrorKind::Other, "Failed to set client")
})?;
Expand All @@ -94,7 +94,7 @@ impl PerfTest for ListBlobTest {
// Create the blobs for the test.
for i in 0..self.count {
let blob_name = format!("blob-{}", i);
let blob_client = container_client.blob_client(blob_name);
let blob_client = container_client.blob_client(&blob_name);
let body = vec![0u8; 1024 * 1024]; // 1 MB blob
let body_bytes = Bytes::from(body);

Expand Down
124 changes: 90 additions & 34 deletions sdk/storage/azure_storage_blob/src/clients/append_blob_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,35 @@ use crate::{
AppendBlobClientSealResult,
},
pipeline::StorageHeadersPolicy,
AppendBlobClientOptions, BlobClientOptions,
AppendBlobClientOptions,
};
use azure_core::{
credentials::TokenCredential,
http::{
policies::{BearerTokenCredentialPolicy, Policy},
NoFormat, RequestContent, Response, Url,
NoFormat, Pipeline, RequestContent, Response, Url,
},
Bytes, Result,
tracing, Bytes, Result,
};
use std::sync::Arc;

/// A client to interact with a specific Azure storage Append blob, although that blob may not yet exist.
pub struct AppendBlobClient {
pub(crate) endpoint: Url,
pub(crate) client: GeneratedAppendBlobClient,
pub(super) client: GeneratedAppendBlobClient,
}

impl AppendBlobClient {
/// Creates a new AppendBlobClient, using Entra ID authentication.
impl GeneratedAppendBlobClient {
/// Creates a new GeneratedAppendBlobClient from a blob URL.
///
/// # Arguments
///
/// * `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 Append blob.
/// * `blob_name` - The name of the Append blob to interact with.
/// * `credential` - An implementation of [`TokenCredential`] that can provide an Entra ID token to use when authenticating.
/// * `blob_url` - The full URL of the Append blob, for example `https://myaccount.blob.core.windows.net/mycontainer/myblob`.
/// * `credential` - An optional 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<dyn TokenCredential>,
#[tracing::new("Storage.Blob.AppendBlob")]
pub fn from_url(
blob_url: Url,
credential: Option<Arc<dyn TokenCredential>>,
options: Option<AppendBlobClientOptions>,
) -> Result<Self> {
let mut options = options.unwrap_or_default();
Expand All @@ -53,32 +49,92 @@ impl AppendBlobClient {
.per_call_policies
.push(storage_headers_policy);

let client = GeneratedAppendBlobClient::new(
endpoint,
credential,
container_name,
blob_name,
Some(options),
)?;
let per_retry_policies = if let Some(token_credential) = credential {
if !blob_url.scheme().starts_with("https") {
return Err(azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
format!("{blob_url} must use https"),
));
}
let auth_policy: Arc<dyn Policy> = Arc::new(BearerTokenCredentialPolicy::new(
token_credential,
vec!["https://storage.azure.com/.default"],
));
vec![auth_policy]
} else {
Vec::default()
};

let pipeline = Pipeline::new(
option_env!("CARGO_PKG_NAME"),
option_env!("CARGO_PKG_VERSION"),
options.client_options.clone(),
Vec::default(),
per_retry_policies,
None,
);

Ok(Self {
endpoint: endpoint.parse()?,
client,
endpoint: blob_url,
version: options.version,
pipeline,
})
}
}

impl AppendBlobClient {
/// Creates a new AppendBlobClient, using Entra ID authentication.
///
/// # Arguments
///
/// * `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 Append blob.
/// * `blob_name` - The name of the Append blob to interact with.
/// * `credential` - An optional 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: &str,
blob_name: &str,
credential: Option<Arc<dyn TokenCredential>>,
options: Option<AppendBlobClientOptions>,
) -> Result<Self> {
let mut url = Url::parse(endpoint)?;

/// Gets the endpoint of the Storage account this client is connected to.
pub fn endpoint(&self) -> &Url {
&self.endpoint
{
let mut path_segments = url.path_segments_mut().map_err(|_| {
azure_core::Error::with_message(
azure_core::error::ErrorKind::Other,
"Invalid endpoint URL: Failed to parse out path segments from provided endpoint URL.",
)
})?;
path_segments.extend([container_name, blob_name]);
}

let client = GeneratedAppendBlobClient::from_url(url, credential, options)?;
Ok(Self { client })
}

/// Gets the container name of the Storage account this client is connected to.
pub fn container_name(&self) -> &str {
&self.client.container_name
/// Creates a new AppendBlobClient from a blob URL.
///
/// # Arguments
///
/// * `blob_url` - The full URL of the Append blob, for example `https://myaccount.blob.core.windows.net/mycontainer/myblob`.
/// * `credential` - An optional implementation of [`TokenCredential`] that can provide an Entra ID token to use when authenticating.
/// * `options` - Optional configuration for the client.
pub fn from_url(
blob_url: Url,
credential: Option<Arc<dyn TokenCredential>>,
options: Option<AppendBlobClientOptions>,
) -> Result<Self> {
let client = GeneratedAppendBlobClient::from_url(blob_url, credential, options)?;

Ok(Self { client })
}

/// Gets the blob name of the Storage account this client is connected to.
pub fn blob_name(&self) -> &str {
&self.client.blob_name
/// Gets the URL of the resource this client is configured for.
pub fn url(&self) -> &Url {
&self.client.endpoint
}

/// Creates a new Append blob.
Expand Down
Loading