This is an automated email from the ASF dual-hosted git repository.
tustvold pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs-object-store.git
The following commit(s) were added to refs/heads/main by this push:
new 8bb878a feat(azure): expose `split_sas` for custom SAS credential
providers (#721)
8bb878a is described below
commit 8bb878a07b1a0e07521c1374e8302e9a21bb0f62
Author: Aleksandar Tomic <[email protected]>
AuthorDate: Tue Jun 2 11:30:59 2026 +0200
feat(azure): expose `split_sas` for custom SAS credential providers (#721)
`split_sas` parses an Azure Shared Access Signature string into the
`Vec<(String, String)>` query pairs expected by
`AzureCredential::SASToken`. It is what `MicrosoftAzureBuilder` runs
internally when a raw SAS string is supplied via `AzureConfigKey::SasKey`.
Users implementing their own `CredentialProvider<Credential =
AzureCredential>` that fetches SAS tokens at request time (e.g. tokens
refreshed from a secret manager or coordination service) currently have
to reimplement this parser to feed pairs into
`AzureCredential::SASToken`. Reimplementations are easy to get subtly
wrong: in particular, using `form_urlencoded` decodes `+` as space,
which corrupts base64-encoded SAS signatures.
Make `split_sas` public, change its return type to the public
`crate::Result<Vec<(String, String)>>` (the inner `Error` variants
already convert into `crate::Error` via the existing `From` impl, so
the two existing internal call sites are unchanged), document it, and
re-export it from `object_store::azure`. Add tests for the empty/`?`
edge cases and the missing-`=` error path.
---
src/azure/builder.rs | 24 +++++++++++++++++++++++-
src/azure/mod.rs | 2 +-
2 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/src/azure/builder.rs b/src/azure/builder.rs
index afd7a0a..1f57fac 100644
--- a/src/azure/builder.rs
+++ b/src/azure/builder.rs
@@ -1074,7 +1074,8 @@ fn url_from_env(env_name: &str, default_url: &str) ->
Result<Url> {
Ok(url)
}
-fn split_sas(sas: &str) -> Result<Vec<(String, String)>, Error> {
+/// Parse a SAS token string into the query pairs expected by
[`AzureCredential::SASToken`].
+pub fn split_sas(sas: &str) -> Result<Vec<(String, String)>> {
let sas = percent_decode_str(sas)
.decode_utf8()
.map_err(|source| Error::DecodeSasKey { source })?;
@@ -1273,6 +1274,27 @@ mod tests {
assert_eq!(expected, pairs);
}
+ #[test]
+ fn azure_test_split_sas_trims_leading_question_mark_and_skips_empties() {
+ let pairs = split_sas("?&sv=2021-10-04& &sp=r").unwrap();
+ assert_eq!(
+ pairs,
+ vec![
+ ("sv".to_string(), "2021-10-04".to_string()),
+ ("sp".to_string(), "r".to_string()),
+ ],
+ );
+ }
+
+ #[test]
+ fn azure_test_split_sas_rejects_missing_equals() {
+ let err = split_sas("sv=2021-10-04&bogus").unwrap_err();
+ assert!(
+ err.to_string().contains("Missing component"),
+ "unexpected error: {err}",
+ );
+ }
+
#[test]
fn azure_test_client_opts() {
let key = "AZURE_PROXY_URL";
diff --git a/src/azure/mod.rs b/src/azure/mod.rs
index e6b9b9c..1429bec 100644
--- a/src/azure/mod.rs
+++ b/src/azure/mod.rs
@@ -53,7 +53,7 @@ pub type AzureCredentialProvider = Arc<dyn
CredentialProvider<Credential = Azure
use crate::azure::client::AzureClient;
use crate::client::parts::Parts;
use crate::list::{PaginatedListOptions, PaginatedListResult,
PaginatedListStore};
-pub use builder::{AzureConfigKey, MicrosoftAzureBuilder};
+pub use builder::{AzureConfigKey, MicrosoftAzureBuilder, split_sas};
pub use credential::AzureCredential;
const STORE: &str = "MicrosoftAzure";