azure_storage/
cloud_location.rs
1use crate::clients::ServiceType;
2use azure_core::Url;
3
4#[derive(Debug, Clone)]
7pub enum CloudLocation {
8 Public { account: String },
10 China { account: String },
12 Emulator { address: String, port: u16 },
14 Custom { account: String, uri: String },
16}
17
18impl CloudLocation {
19 pub fn account(&self) -> &str {
20 match self {
21 CloudLocation::Public { account, .. }
22 | CloudLocation::China { account, .. }
23 | CloudLocation::Custom { account, .. } => account,
24 CloudLocation::Emulator { .. } => EMULATOR_ACCOUNT,
25 }
26 }
27
28 pub fn url(&self, service_type: ServiceType) -> azure_core::Result<Url> {
30 let url = match self {
31 CloudLocation::Public { account, .. } => {
32 format!(
33 "https://{}.{}.core.windows.net",
34 account,
35 service_type.subdomain()
36 )
37 }
38 CloudLocation::China { account, .. } => {
39 format!(
40 "https://{}.{}.core.chinacloudapi.cn",
41 account,
42 service_type.subdomain()
43 )
44 }
45 CloudLocation::Custom { uri, .. } => uri.clone(),
46 CloudLocation::Emulator { address, port } => {
47 format!("http://{address}:{port}/{EMULATOR_ACCOUNT}")
48 }
49 };
50 Ok(Url::parse(&url)?)
51 }
52}
53
54impl TryFrom<&Url> for CloudLocation {
55 type Error = azure_core::Error;
56
57 fn try_from(url: &Url) -> azure_core::Result<Self> {
60 let host = url.host_str().ok_or_else(|| {
61 azure_core::Error::with_message(azure_core::error::ErrorKind::DataConversion, || {
62 "unable to find the target host in the URL"
63 })
64 })?;
65
66 let mut domain = host.split_terminator('.').collect::<Vec<_>>();
67 if domain.len() < 2 {
68 return Err(azure_core::Error::with_message(
69 azure_core::error::ErrorKind::DataConversion,
70 || format!("URL refers to a domain that is not a Public or China domain: {host}"),
71 ));
72 }
73
74 let account = domain.remove(0).to_string();
75 domain.remove(0);
76 let rest = domain.join(".");
77
78 match rest.as_str() {
79 "core.windows.net" => Ok(CloudLocation::Public { account }),
80 "core.chinacloudapi.cn" => Ok(CloudLocation::China { account }),
81 _ => Err(azure_core::Error::with_message(
82 azure_core::error::ErrorKind::DataConversion,
83 || format!("URL refers to a domain that is not a Public or China domain: {host}"),
84 )),
85 }
86 }
87}
88
89pub const EMULATOR_ACCOUNT: &str = "devstoreaccount1";
92
93pub const EMULATOR_ACCOUNT_KEY: &str =
96 "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_from_url() -> azure_core::Result<()> {
104 let public_without_token = Url::parse("https://test.blob.core.windows.net")?;
105 let public_with_token = Url::parse("https://test.blob.core.windows.net/?token=1")?;
106
107 let cloud_location: CloudLocation = (&public_with_token).try_into()?;
108 assert_eq!(public_without_token, cloud_location.url(ServiceType::Blob)?);
109
110 let file_url = Url::parse("file://tmp/test.txt")?;
111 let result: azure_core::Result<CloudLocation> = (&file_url).try_into();
112 assert!(result.is_err());
113
114 let missing_account = Url::parse("https://blob.core.windows.net?token=1")?;
115 let result: azure_core::Result<CloudLocation> = (&missing_account).try_into();
116 assert!(result.is_err());
117
118 let missing_service_type = Url::parse("https://core.windows.net?token=1")?;
119 let result: azure_core::Result<CloudLocation> = (&missing_service_type).try_into();
120 assert!(result.is_err());
121
122 let china_cloud = Url::parse("https://test.blob.core.chinacloudapi.cn/?token=1")?;
123 let china_cloud_without_token = Url::parse("https://test.blob.core.chinacloudapi.cn")?;
124
125 let cloud_location: CloudLocation = (&china_cloud).try_into()?;
126 assert_eq!(
127 china_cloud_without_token,
128 cloud_location.url(ServiceType::Blob)?
129 );
130
131 Ok(())
132 }
133}