@@ -666,10 +666,26 @@ impl MicrosoftAzureBuilder {
666666 self . container_name = Some ( validate ( host) ?) ;
667667 } else {
668668 match host. split_once ( '.' ) {
669+ // Workspace-level Private Link detection
670+ // "{workspaceid}.z??.(onelake|dfs|blob).fabric.microsoft.com"
671+ Some ( ( workspaceid, rest) )
672+ if rest. starts_with ( 'z' ) && rest. ends_with ( "fabric.microsoft.com" ) =>
673+ {
674+ // Account name for WS-PL is two labels: "{workspaceid}.z{xy}"
675+ let ( zone, _) = rest. split_once ( '.' ) . unwrap_or ( ( rest, "" ) ) ;
676+
677+ self . account_name = Some ( format ! ( "{workspaceid}.{zone}" ) ) ;
678+ self . endpoint = Some ( format ! ( "https://{}" , host) ) ;
679+
680+ self . container_name = Some ( validate ( parsed. username ( ) ) ?) ;
681+ self . use_fabric_endpoint = true . into ( ) ;
682+ }
683+
669684 Some ( ( a, "dfs.core.windows.net" ) ) | Some ( ( a, "blob.core.windows.net" ) ) => {
670685 self . account_name = Some ( validate ( a) ?) ;
671686 self . container_name = Some ( validate ( parsed. username ( ) ) ?) ;
672687 }
688+
673689 Some ( ( a, "dfs.fabric.microsoft.com" ) )
674690 | Some ( ( a, "blob.fabric.microsoft.com" ) ) => {
675691 self . account_name = Some ( validate ( a) ?) ;
@@ -681,6 +697,30 @@ impl MicrosoftAzureBuilder {
681697 }
682698 }
683699 "https" => match host. split_once ( '.' ) {
700+ // Workspace-level Private Link detection
701+ // "{workspaceid}.z??.(onelake|dfs|blob).fabric.microsoft.com"
702+ Some ( ( workspaceid, rest) )
703+ if rest. starts_with ( 'z' ) && rest. ends_with ( "fabric.microsoft.com" ) =>
704+ {
705+ // rest looks like: "z28.dfs.fabric.microsoft.com" / "z28.blob.fabric.microsoft.com" / etc.
706+ // Account name for WS-PL is two labels: "{workspaceid}.z{xy}"
707+ let ( zone, _) = rest. split_once ( '.' ) . unwrap_or ( ( rest, "" ) ) ;
708+
709+ self . account_name = Some ( format ! ( "{workspaceid}.{zone}" ) ) ;
710+ self . endpoint = Some ( format ! ( "https://{}" , host) ) ;
711+
712+ // Attempt to infer the container name from the URL
713+ let container = parsed. path_segments ( ) . unwrap ( ) . next ( ) . expect (
714+ "iterator always contains at least one string (which may be empty)" ,
715+ ) ;
716+
717+ if !container. is_empty ( ) {
718+ self . container_name = Some ( validate ( container) ?) ;
719+ }
720+
721+ self . use_fabric_endpoint = true . into ( ) ;
722+ }
723+
684724 Some ( ( a, "dfs.core.windows.net" ) ) | Some ( ( a, "blob.core.windows.net" ) ) => {
685725 self . account_name = Some ( validate ( a) ?) ;
686726 let container = parsed. path_segments ( ) . unwrap ( ) . next ( ) . expect (
@@ -1204,6 +1244,17 @@ mod tests {
12041244 assert_eq ! ( builder. container_name. as_deref( ) , Some ( "container" ) ) ;
12051245 assert ! ( builder. use_fabric_endpoint. get( ) . unwrap( ) ) ;
12061246
1247+ let mut builder = MicrosoftAzureBuilder :: new ( ) ;
1248+ builder
1249+ . parse_url ( "https://onelake.dfs.fabric.microsoft.com/c047b3e3-4e89-407a-98d7-cf9949ae92a3/9f1a2b3c-4d5e-6f70-8a9b-c0d1e2f3a456.lakehouse/Files/tables/sales/data.parquet" )
1250+ . unwrap ( ) ;
1251+ assert_eq ! ( builder. account_name, Some ( "onelake" . to_string( ) ) ) ;
1252+ assert_eq ! (
1253+ builder. container_name. as_deref( ) ,
1254+ Some ( "c047b3e3-4e89-407a-98d7-cf9949ae92a3" )
1255+ ) ;
1256+ assert ! ( builder. use_fabric_endpoint. get( ) . unwrap( ) ) ;
1257+
12071258 let mut builder = MicrosoftAzureBuilder :: new ( ) ;
12081259 builder
12091260 . parse_url ( "https://account.blob.fabric.microsoft.com/" )
@@ -1235,6 +1286,77 @@ mod tests {
12351286 }
12361287 }
12371288
1289+ #[ test]
1290+ fn azure_test_workspace_private_link ( ) {
1291+ let test_cases: Vec < ( & str , & str , Option < & str > ) > = vec ! [
1292+ (
1293+ "https://Ab000000000000000000000000000000.zAb.dfs.fabric.microsoft.com/" ,
1294+ "ab000000000000000000000000000000.zab" ,
1295+ None ,
1296+ ) ,
1297+ (
1298+ "https://ab000000000000000000000000000000.zab.dfs.fabric.microsoft.com/" ,
1299+ "ab000000000000000000000000000000.zab" ,
1300+ None ,
1301+ ) ,
1302+ (
1303+ "https://c047b3e34e89407a98d7cf9949ae92a3.zc0.blob.fabric.microsoft.com/c047b3e3-4e89-407a-98d7-cf9949ae92a3/9f1a2b3c-4d5e-6f70-8a9b-c0d1e2f3a456/file" ,
1304+ "c047b3e34e89407a98d7cf9949ae92a3.zc0" ,
1305+ Some ( "c047b3e3-4e89-407a-98d7-cf9949ae92a3" ) ,
1306+ ) ,
1307+ (
1308+ "https://c047b3e34e89407a98d7cf9949ae92a3.zc0.dfs.fabric.microsoft.com/c047b3e3-4e89-407a-98d7-cf9949ae92a3/9f1a2b3c-4d5e-6f70-8a9b-c0d1e2f3a456/file" ,
1309+ "c047b3e34e89407a98d7cf9949ae92a3.zc0" ,
1310+ Some ( "c047b3e3-4e89-407a-98d7-cf9949ae92a3" ) ,
1311+ ) ,
1312+ (
1313+ "https://c047b3e34e89407a98d7cf9949ae92a3.zc0.onelake.fabric.microsoft.com/c047b3e3-4e89-407a-98d7-cf9949ae92a3/9f1a2b3c-4d5e-6f70-8a9b-c0d1e2f3a456/file" ,
1314+ "c047b3e34e89407a98d7cf9949ae92a3.zc0" ,
1315+ Some ( "c047b3e3-4e89-407a-98d7-cf9949ae92a3" ) ,
1316+ ) ,
1317+ (
1318+ "https://c047b3e34e89407a98d7cf9949ae92a3.zc0.w.api.fabric.microsoft.com/c047b3e3-4e89-407a-98d7-cf9949ae92a3/9f1a2b3c-4d5e-6f70-8a9b-c0d1e2f3a456/file" ,
1319+ "c047b3e34e89407a98d7cf9949ae92a3.zc0" ,
1320+ Some ( "c047b3e3-4e89-407a-98d7-cf9949ae92a3" ) ,
1321+ ) ,
1322+ (
1323+ "https://c047b3e34e89407a98d7cf9949ae92a3.zc0.c.api.fabric.microsoft.com/c047b3e3-4e89-407a-98d7-cf9949ae92a3/9f1a2b3c-4d5e-6f70-8a9b-c0d1e2f3a456/file" ,
1324+ "c047b3e34e89407a98d7cf9949ae92a3.zc0" ,
1325+ Some ( "c047b3e3-4e89-407a-98d7-cf9949ae92a3" ) ,
1326+ ) ,
1327+ (
1328+ "abfss://c047b3e34e89407a98d7cf9949ae92a3@c047b3e34e89407a98d7cf9949ae92a3.zc0.dfs.fabric.microsoft.com/9f1a2b3c-4d5e-6f70-8a9b-c0d1e2f3a456/file" ,
1329+ "c047b3e34e89407a98d7cf9949ae92a3.zc0" ,
1330+ Some ( "c047b3e34e89407a98d7cf9949ae92a3" ) ,
1331+ ) ,
1332+ (
1333+ "abfss://c047b3e34e89407a98d7cf9949ae92a3@c047b3e34e89407a98d7cf9949ae92a3.zc0.blob.fabric.microsoft.com/9f1a2b3c-4d5e-6f70-8a9b-c0d1e2f3a456/file" ,
1334+ "c047b3e34e89407a98d7cf9949ae92a3.zc0" ,
1335+ Some ( "c047b3e34e89407a98d7cf9949ae92a3" ) ,
1336+ ) ,
1337+ ] ;
1338+
1339+ for ( url, expected_account, expected_container) in & test_cases {
1340+ let mut builder = MicrosoftAzureBuilder :: new ( ) ;
1341+ builder. parse_url ( url) . unwrap ( ) ;
1342+
1343+ assert_eq ! (
1344+ builder. account_name. as_deref( ) ,
1345+ Some ( * expected_account) ,
1346+ "account mismatch for URL: {url}"
1347+ ) ;
1348+ assert_eq ! (
1349+ builder. container_name. as_deref( ) ,
1350+ * expected_container,
1351+ "container mismatch for URL: {url}"
1352+ ) ;
1353+ assert ! (
1354+ builder. use_fabric_endpoint. get( ) . unwrap( ) ,
1355+ "use_fabric_endpoint not set for URL: {url}"
1356+ ) ;
1357+ }
1358+ }
1359+
12381360 #[ test]
12391361 fn azure_test_config_from_map ( ) {
12401362 let azure_client_id = "object_store:fake_access_key_id" ;
0 commit comments