azure_storage/
cloud_location.rs

1use crate::clients::ServiceType;
2use azure_core::Url;
3
4/// The cloud with which you want to interact.
5// TODO: Other govt clouds?
6#[derive(Debug, Clone)]
7pub enum CloudLocation {
8    /// Azure public cloud
9    Public { account: String },
10    /// Azure China cloud
11    China { account: String },
12    /// Use the well-known emulator
13    Emulator { address: String, port: u16 },
14    /// A custom base URL
15    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    /// the base URL for a given cloud location
29    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    // TODO: This only works for Public and China clouds.
58    // ref: https://github.com/Azure/azure-sdk-for-rust/issues/502
59    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
89/// The well-known account used by Azurite and the legacy Azure Storage Emulator.
90/// <https://docs.microsoft.com/azure/storage/common/storage-use-azurite#well-known-storage-account-and-key>
91pub const EMULATOR_ACCOUNT: &str = "devstoreaccount1";
92
93/// The well-known account key used by Azurite and the legacy Azure Storage Emulator.
94/// <https://docs.microsoft.com/azure/storage/common/storage-use-azurite#well-known-storage-account-and-key>
95pub 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}