Skip to main content

opendal_service_azdls/
config.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::fmt::Debug;
19
20use serde::Deserialize;
21use serde::Serialize;
22
23use super::AZDLS_SCHEME;
24use super::backend::AzdlsBuilder;
25
26/// Azure Data Lake Storage Gen2 Support.
27#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
28#[serde(default)]
29pub struct AzdlsConfig {
30    /// Root of this backend.
31    pub root: Option<String>,
32    /// Filesystem name of this backend.
33    pub filesystem: String,
34    /// Endpoint of this backend.
35    pub endpoint: Option<String>,
36    /// Account name of this backend.
37    pub account_name: Option<String>,
38    /// Account key of this backend.
39    /// - required for shared_key authentication
40    pub account_key: Option<String>,
41    /// client_secret
42    /// The client secret of the service principal.
43    /// - required for client_credentials authentication
44    pub client_secret: Option<String>,
45    /// tenant_id
46    /// The tenant id of the service principal.
47    /// - required for client_credentials authentication
48    pub tenant_id: Option<String>,
49    /// client_id
50    /// The client id of the service principal.
51    /// - required for client_credentials authentication
52    pub client_id: Option<String>,
53    /// sas_token
54    /// The shared access signature token.
55    /// - required for sas authentication
56    pub sas_token: Option<String>,
57    /// authority_host
58    /// The authority host of the service principal.
59    /// - required for client_credentials authentication
60    /// - default value: `https://login.microsoftonline.com`
61    pub authority_host: Option<String>,
62    /// Whether hierarchical namespace (HNS) is enabled for the storage account.
63    /// When enabled, recursive deletion can use pagination to avoid timeouts on large directories.
64    /// - default value: `false`
65    pub enable_hns: bool,
66}
67
68impl Debug for AzdlsConfig {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        f.debug_struct("AzdlsConfig")
71            .field("root", &self.root)
72            .field("filesystem", &self.filesystem)
73            .field("endpoint", &self.endpoint)
74            .finish_non_exhaustive()
75    }
76}
77
78impl opendal_core::Configurator for AzdlsConfig {
79    type Builder = AzdlsBuilder;
80
81    fn from_uri(uri: &opendal_core::OperatorUri) -> opendal_core::Result<Self> {
82        let mut map = uri.options().clone();
83        if let Some(authority) = uri.authority() {
84            map.insert("endpoint".to_string(), format!("https://{authority}"));
85        }
86
87        if let Some(account) = uri
88            .name()
89            .and_then(|host| host.split('.').next())
90            .filter(|account| !account.is_empty())
91        {
92            map.entry("account_name".to_string())
93                .or_insert_with(|| account.to_string());
94        }
95
96        if let Some(root) = uri.root() {
97            if let Some((filesystem, rest)) = root.split_once('/') {
98                if filesystem.is_empty() {
99                    return Err(opendal_core::Error::new(
100                        opendal_core::ErrorKind::ConfigInvalid,
101                        "filesystem is required in uri path",
102                    )
103                    .with_context("service", AZDLS_SCHEME));
104                }
105                map.insert("filesystem".to_string(), filesystem.to_string());
106                if !rest.is_empty() {
107                    map.insert("root".to_string(), rest.to_string());
108                }
109            } else if !root.is_empty() {
110                map.insert("filesystem".to_string(), root.to_string());
111            }
112        }
113
114        if !map.contains_key("filesystem") {
115            return Err(opendal_core::Error::new(
116                opendal_core::ErrorKind::ConfigInvalid,
117                "filesystem is required",
118            )
119            .with_context("service", AZDLS_SCHEME));
120        }
121
122        Self::from_iter(map)
123    }
124
125    #[allow(deprecated)]
126    fn into_builder(self) -> Self::Builder {
127        AzdlsBuilder { config: self }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use opendal_core::Configurator;
135    use opendal_core::OperatorUri;
136
137    #[test]
138    fn from_uri_sets_endpoint_filesystem_root_and_account() {
139        let uri = OperatorUri::new(
140            "azdls://account.dfs.core.windows.net/fs/data/2024",
141            Vec::<(String, String)>::new(),
142        )
143        .unwrap();
144
145        let cfg = AzdlsConfig::from_uri(&uri).unwrap();
146        assert_eq!(
147            cfg.endpoint.as_deref(),
148            Some("https://account.dfs.core.windows.net")
149        );
150        assert_eq!(cfg.filesystem, "fs".to_string());
151        assert_eq!(cfg.root.as_deref(), Some("data/2024"));
152        assert_eq!(cfg.account_name.as_deref(), Some("account"));
153    }
154
155    #[test]
156    fn from_uri_accepts_filesystem_from_query() {
157        let uri = OperatorUri::new(
158            "azdls://account.dfs.core.windows.net",
159            vec![("filesystem".to_string(), "logs".to_string())],
160        )
161        .unwrap();
162
163        let cfg = AzdlsConfig::from_uri(&uri).unwrap();
164        assert_eq!(cfg.filesystem, "logs".to_string());
165    }
166}