@@ -114,6 +114,12 @@ enum Error {
114114
115115 #[ snafu( display( "Azure credential error: {}" , source) , context( false ) ) ]
116116 Credential { source : credential:: Error } ,
117+
118+ #[ snafu( display(
119+ "Unknown url scheme cannot be parsed into storage location: {}" ,
120+ scheme
121+ ) ) ]
122+ UnknownUrlScheme { scheme : String } ,
117123}
118124
119125impl From < Error > for super :: Error {
@@ -361,6 +367,7 @@ pub struct MicrosoftAzureBuilder {
361367 use_emulator : bool ,
362368 retry_config : RetryConfig ,
363369 client_options : ClientOptions ,
370+ url_parse_error : Option < Error > ,
364371}
365372
366373impl Debug for MicrosoftAzureBuilder {
@@ -379,7 +386,7 @@ impl MicrosoftAzureBuilder {
379386 Default :: default ( )
380387 }
381388
382- /// Create an instance of [MicrosoftAzureBuilder] with values pre-populated from environment variables.
389+ /// Create an instance of [` MicrosoftAzureBuilder` ] with values pre-populated from environment variables.
383390 ///
384391 /// Variables extracted from environment:
385392 /// * AZURE_STORAGE_ACCOUNT_NAME: storage account name
@@ -424,6 +431,78 @@ impl MicrosoftAzureBuilder {
424431 builder
425432 }
426433
434+ /// Parse available connection info form a well-known storage URL.
435+ ///
436+ /// The supported url schemes are:
437+ ///
438+ /// - `abfs[s]://<container>/<path>` (according to [fsspec](https://github.com/fsspec/adlfs))
439+ /// - `abfs[s]://<file_system>@<account_name>.dfs.core.windows.net/<path>`
440+ /// - `az://<container>/<path>` (according to [fsspec](https://github.com/fsspec/adlfs))
441+ /// - `adl://<container>/<path>` (according to [fsspec](https://github.com/fsspec/adlfs))
442+ /// - `azure://<container>/<path>` (custom)
443+ /// - `https://<account>.dfs.core.windows.net`
444+ /// - `https://<account>.blob.core.windows.net`
445+ ///
446+ /// Please note that this is a best effort implementation, and will not fail for malformed URLs,
447+ /// but rather warn and ignore the passed url. The url also has no effect on how the
448+ /// storage is accessed - e.g. which driver or protocol is used for reading from the location.
449+ ///
450+ /// # Example
451+ /// ```
452+ /// use object_store::azure::MicrosoftAzureBuilder;
453+ ///
454+ /// let azure = MicrosoftAzureBuilder::from_env()
455+ /// .with_url("abfss://file_system@account.dfs.core.windows.net/")
456+ /// .build();
457+ /// ```
458+ pub fn with_url ( mut self , url : impl AsRef < str > ) -> Self {
459+ let maybe_parsed = Url :: parse ( url. as_ref ( ) ) ;
460+ match maybe_parsed {
461+ Ok ( parsed) => match parsed. scheme ( ) {
462+ "az" | "adl" | "azure" => {
463+ self . container_name = parsed. host_str ( ) . map ( |host| host. to_owned ( ) ) ;
464+ }
465+ "abfs" | "abfss" => {
466+ // abfs(s) might refer to the fsspec convention abfs://<container>/<path>
467+ // or the convention for the hadoop driver abfs[s]://<file_system>@<account_name>.dfs.core.windows.net/<path>
468+ if parsed. username ( ) . is_empty ( ) {
469+ self . container_name =
470+ parsed. host_str ( ) . map ( |host| host. to_owned ( ) ) ;
471+ } else if let Some ( host) = parsed. host_str ( ) {
472+ let parts = host. splitn ( 2 , '.' ) . collect :: < Vec < & str > > ( ) ;
473+ if parts. len ( ) == 2 && parts[ 1 ] == "dfs.core.windows.net" {
474+ self . container_name = Some ( parsed. username ( ) . to_owned ( ) ) ;
475+ self . account_name = Some ( parts[ 0 ] . to_string ( ) ) ;
476+ }
477+ }
478+ }
479+ "https" => {
480+ if let Some ( host) = parsed. host_str ( ) {
481+ let parts = host. splitn ( 2 , '.' ) . collect :: < Vec < & str > > ( ) ;
482+ if parts. len ( ) == 2
483+ && ( parts[ 1 ] == "dfs.core.windows.net"
484+ || parts[ 1 ] == "blob.core.windows.net" )
485+ {
486+ self . account_name = Some ( parts[ 0 ] . to_string ( ) ) ;
487+ }
488+ }
489+ }
490+ other => {
491+ self . url_parse_error = Some ( Error :: UnknownUrlScheme {
492+ scheme : other. into ( ) ,
493+ } ) ;
494+ }
495+ } ,
496+ Err ( err) => {
497+ self . url_parse_error = Some ( Error :: UnableToParseUrl {
498+ source : err,
499+ url : url. as_ref ( ) . into ( ) ,
500+ } ) ;
501+ }
502+ } ;
503+ self
504+ }
505+
427506 /// Set the Azure Account (required)
428507 pub fn with_account ( mut self , account : impl Into < String > ) -> Self {
429508 self . account_name = Some ( account. into ( ) ) ;
@@ -529,8 +608,13 @@ impl MicrosoftAzureBuilder {
529608 retry_config,
530609 authority_host,
531610 mut client_options,
611+ url_parse_error,
532612 } = self ;
533613
614+ if let Some ( err) = url_parse_error {
615+ return Err ( err. into ( ) ) ;
616+ }
617+
534618 let container = container_name. ok_or ( Error :: MissingContainerName { } ) ?;
535619
536620 let ( is_emulator, storage_url, auth, account) = if use_emulator {
@@ -716,4 +800,29 @@ mod tests {
716800 copy_if_not_exists ( & integration) . await ;
717801 stream_get ( & integration) . await ;
718802 }
803+
804+ #[ test]
805+ fn azure_blob_test_urls ( ) {
806+ let builder = MicrosoftAzureBuilder :: new ( )
807+ . with_url ( "abfss://file_system@account.dfs.core.windows.net/" ) ;
808+ assert_eq ! ( builder. account_name, Some ( "account" . to_string( ) ) ) ;
809+ assert_eq ! ( builder. container_name, Some ( "file_system" . to_string( ) ) ) ;
810+
811+ let builder = MicrosoftAzureBuilder :: new ( ) . with_url ( "abfs://container/path" ) ;
812+ assert_eq ! ( builder. container_name, Some ( "container" . to_string( ) ) ) ;
813+
814+ let builder = MicrosoftAzureBuilder :: new ( ) . with_url ( "az://container" ) ;
815+ assert_eq ! ( builder. container_name, Some ( "container" . to_string( ) ) ) ;
816+
817+ let builder = MicrosoftAzureBuilder :: new ( ) . with_url ( "az://container/path" ) ;
818+ assert_eq ! ( builder. container_name, Some ( "container" . to_string( ) ) ) ;
819+
820+ let builder = MicrosoftAzureBuilder :: new ( )
821+ . with_url ( "https://account.dfs.core.windows.net/" ) ;
822+ assert_eq ! ( builder. account_name, Some ( "account" . to_string( ) ) ) ;
823+
824+ let builder = MicrosoftAzureBuilder :: new ( )
825+ . with_url ( "https://account.blob.core.windows.net/" ) ;
826+ assert_eq ! ( builder. account_name, Some ( "account" . to_string( ) ) )
827+ }
719828}
0 commit comments