azure_storage/
cloud_location.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use crate::clients::ServiceType;
use azure_core::Url;

/// The cloud with which you want to interact.
// TODO: Other govt clouds?
#[derive(Debug, Clone)]
pub enum CloudLocation {
    /// Azure public cloud
    Public { account: String },
    /// Azure China cloud
    China { account: String },
    /// Use the well-known emulator
    Emulator { address: String, port: u16 },
    /// A custom base URL
    Custom { account: String, uri: String },
}

impl CloudLocation {
    pub fn account(&self) -> &str {
        match self {
            CloudLocation::Public { account, .. }
            | CloudLocation::China { account, .. }
            | CloudLocation::Custom { account, .. } => account,
            CloudLocation::Emulator { .. } => EMULATOR_ACCOUNT,
        }
    }

    /// the base URL for a given cloud location
    pub fn url(&self, service_type: ServiceType) -> azure_core::Result<Url> {
        let url = match self {
            CloudLocation::Public { account, .. } => {
                format!(
                    "https://{}.{}.core.windows.net",
                    account,
                    service_type.subdomain()
                )
            }
            CloudLocation::China { account, .. } => {
                format!(
                    "https://{}.{}.core.chinacloudapi.cn",
                    account,
                    service_type.subdomain()
                )
            }
            CloudLocation::Custom { uri, .. } => uri.clone(),
            CloudLocation::Emulator { address, port } => {
                format!("http://{address}:{port}/{EMULATOR_ACCOUNT}")
            }
        };
        Ok(Url::parse(&url)?)
    }
}

impl TryFrom<&Url> for CloudLocation {
    type Error = azure_core::Error;

    // TODO: This only works for Public and China clouds.
    // ref: https://github.com/Azure/azure-sdk-for-rust/issues/502
    fn try_from(url: &Url) -> azure_core::Result<Self> {
        let host = url.host_str().ok_or_else(|| {
            azure_core::Error::with_message(azure_core::error::ErrorKind::DataConversion, || {
                "unable to find the target host in the URL"
            })
        })?;

        let mut domain = host.split_terminator('.').collect::<Vec<_>>();
        if domain.len() < 2 {
            return Err(azure_core::Error::with_message(
                azure_core::error::ErrorKind::DataConversion,
                || format!("URL refers to a domain that is not a Public or China domain: {host}"),
            ));
        }

        let account = domain.remove(0).to_string();
        domain.remove(0);
        let rest = domain.join(".");

        match rest.as_str() {
            "core.windows.net" => Ok(CloudLocation::Public { account }),
            "core.chinacloudapi.cn" => Ok(CloudLocation::China { account }),
            _ => Err(azure_core::Error::with_message(
                azure_core::error::ErrorKind::DataConversion,
                || format!("URL refers to a domain that is not a Public or China domain: {host}"),
            )),
        }
    }
}

/// The well-known account used by Azurite and the legacy Azure Storage Emulator.
/// <https://docs.microsoft.com/azure/storage/common/storage-use-azurite#well-known-storage-account-and-key>
pub const EMULATOR_ACCOUNT: &str = "devstoreaccount1";

/// The well-known account key used by Azurite and the legacy Azure Storage Emulator.
/// <https://docs.microsoft.com/azure/storage/common/storage-use-azurite#well-known-storage-account-and-key>
pub const EMULATOR_ACCOUNT_KEY: &str =
    "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_from_url() -> azure_core::Result<()> {
        let public_without_token = Url::parse("https://test.blob.core.windows.net")?;
        let public_with_token = Url::parse("https://test.blob.core.windows.net/?token=1")?;

        let cloud_location: CloudLocation = (&public_with_token).try_into()?;
        assert_eq!(public_without_token, cloud_location.url(ServiceType::Blob)?);

        let file_url = Url::parse("file://tmp/test.txt")?;
        let result: azure_core::Result<CloudLocation> = (&file_url).try_into();
        assert!(result.is_err());

        let missing_account = Url::parse("https://blob.core.windows.net?token=1")?;
        let result: azure_core::Result<CloudLocation> = (&missing_account).try_into();
        assert!(result.is_err());

        let missing_service_type = Url::parse("https://core.windows.net?token=1")?;
        let result: azure_core::Result<CloudLocation> = (&missing_service_type).try_into();
        assert!(result.is_err());

        let china_cloud = Url::parse("https://test.blob.core.chinacloudapi.cn/?token=1")?;
        let china_cloud_without_token = Url::parse("https://test.blob.core.chinacloudapi.cn")?;

        let cloud_location: CloudLocation = (&china_cloud).try_into()?;
        assert_eq!(
            china_cloud_without_token,
            cloud_location.url(ServiceType::Blob)?
        );

        Ok(())
    }
}