opendal/services/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)]
28pub struct AzdlsConfig {
29    /// Root of this backend.
30    pub root: Option<String>,
31    /// Filesystem name of this backend.
32    pub filesystem: String,
33    /// Endpoint of this backend.
34    pub endpoint: Option<String>,
35    /// Account name of this backend.
36    pub account_name: Option<String>,
37    /// Account key of this backend.
38    /// - required for shared_key authentication
39    pub account_key: Option<String>,
40    /// client_secret
41    /// The client secret of the service principal.
42    /// - required for client_credentials authentication
43    pub client_secret: Option<String>,
44    /// tenant_id
45    /// The tenant id of the service principal.
46    /// - required for client_credentials authentication
47    pub tenant_id: Option<String>,
48    /// client_id
49    /// The client id of the service principal.
50    /// - required for client_credentials authentication
51    pub client_id: Option<String>,
52    /// sas_token
53    /// The shared access signature token.
54    /// - required for sas authentication
55    pub sas_token: Option<String>,
56    /// authority_host
57    /// The authority host of the service principal.
58    /// - required for client_credentials authentication
59    /// - default value: `https://login.microsoftonline.com`
60    pub authority_host: Option<String>,
61}
62
63impl Debug for AzdlsConfig {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        f.debug_struct("AzdlsConfig")
66            .field("root", &self.root)
67            .field("filesystem", &self.filesystem)
68            .field("endpoint", &self.endpoint)
69            .finish_non_exhaustive()
70    }
71}
72
73impl crate::Configurator for AzdlsConfig {
74    type Builder = AzdlsBuilder;
75
76    fn from_uri(uri: &crate::types::OperatorUri) -> crate::Result<Self> {
77        let authority = uri.authority().ok_or_else(|| {
78            crate::Error::new(crate::ErrorKind::ConfigInvalid, "uri authority is required")
79                .with_context("service", AZDLS_SCHEME)
80        })?;
81
82        let mut map = uri.options().clone();
83        map.insert("endpoint".to_string(), format!("https://{authority}"));
84
85        if let Some(host) = uri.name() {
86            if let Some(account) = host.split('.').next() {
87                if !account.is_empty() {
88                    map.entry("account_name".to_string())
89                        .or_insert_with(|| account.to_string());
90                }
91            }
92        }
93
94        if let Some(root) = uri.root() {
95            if let Some((filesystem, rest)) = root.split_once('/') {
96                if filesystem.is_empty() {
97                    return Err(crate::Error::new(
98                        crate::ErrorKind::ConfigInvalid,
99                        "filesystem is required in uri path",
100                    )
101                    .with_context("service", AZDLS_SCHEME));
102                }
103                map.insert("filesystem".to_string(), filesystem.to_string());
104                if !rest.is_empty() {
105                    map.insert("root".to_string(), rest.to_string());
106                }
107            } else if !root.is_empty() {
108                map.insert("filesystem".to_string(), root.to_string());
109            }
110        }
111
112        if !map.contains_key("filesystem") {
113            return Err(crate::Error::new(
114                crate::ErrorKind::ConfigInvalid,
115                "filesystem is required",
116            )
117            .with_context("service", AZDLS_SCHEME));
118        }
119
120        Self::from_iter(map)
121    }
122
123    #[allow(deprecated)]
124    fn into_builder(self) -> Self::Builder {
125        AzdlsBuilder {
126            config: self,
127            http_client: None,
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use crate::Configurator;
136    use crate::types::OperatorUri;
137
138    #[test]
139    fn from_uri_sets_endpoint_filesystem_root_and_account() {
140        let uri = OperatorUri::new(
141            "azdls://account.dfs.core.windows.net/fs/data/2024",
142            Vec::<(String, String)>::new(),
143        )
144        .unwrap();
145
146        let cfg = AzdlsConfig::from_uri(&uri).unwrap();
147        assert_eq!(
148            cfg.endpoint.as_deref(),
149            Some("https://account.dfs.core.windows.net")
150        );
151        assert_eq!(cfg.filesystem, "fs".to_string());
152        assert_eq!(cfg.root.as_deref(), Some("data/2024"));
153        assert_eq!(cfg.account_name.as_deref(), Some("account"));
154    }
155
156    #[test]
157    fn from_uri_accepts_filesystem_from_query() {
158        let uri = OperatorUri::new(
159            "azdls://account.dfs.core.windows.net",
160            vec![("filesystem".to_string(), "logs".to_string())],
161        )
162        .unwrap();
163
164        let cfg = AzdlsConfig::from_uri(&uri).unwrap();
165        assert_eq!(cfg.filesystem, "logs".to_string());
166    }
167}