opendal/services/lakefs/
config.rs1use std::fmt::Debug;
19use std::fmt::Formatter;
20
21use super::backend::LakefsBuilder;
22use serde::Deserialize;
23use serde::Serialize;
24
25#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
27#[serde(default)]
28#[non_exhaustive]
29pub struct LakefsConfig {
30    pub endpoint: Option<String>,
34    pub username: Option<String>,
38    pub password: Option<String>,
42    pub root: Option<String>,
46
47    pub repository: Option<String>,
51    pub branch: Option<String>,
55}
56
57impl Debug for LakefsConfig {
58    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
59        let mut ds = f.debug_struct("LakefsConfig");
60
61        if let Some(endpoint) = &self.endpoint {
62            ds.field("endpoint", &endpoint);
63        }
64        if let Some(_username) = &self.username {
65            ds.field("username", &"<redacted>");
66        }
67        if let Some(_password) = &self.password {
68            ds.field("password", &"<redacted>");
69        }
70        if let Some(root) = &self.root {
71            ds.field("root", &root);
72        }
73        if let Some(repository) = &self.repository {
74            ds.field("repository", &repository);
75        }
76        if let Some(branch) = &self.branch {
77            ds.field("branch", &branch);
78        }
79
80        ds.finish()
81    }
82}
83
84impl crate::Configurator for LakefsConfig {
85    type Builder = LakefsBuilder;
86
87    fn from_uri(uri: &crate::types::OperatorUri) -> crate::Result<Self> {
88        let authority = uri.authority().ok_or_else(|| {
89            crate::Error::new(crate::ErrorKind::ConfigInvalid, "uri authority is required")
90                .with_context("service", crate::Scheme::Lakefs)
91        })?;
92
93        let raw_path = uri.root().ok_or_else(|| {
94            crate::Error::new(
95                crate::ErrorKind::ConfigInvalid,
96                "uri path must contain repository",
97            )
98            .with_context("service", crate::Scheme::Lakefs)
99        })?;
100
101        let (repository, remainder) = match raw_path.split_once('/') {
102            Some((repo, rest)) => (repo, Some(rest)),
103            None => (raw_path, None),
104        };
105
106        let repository = if repository.is_empty() {
107            None
108        } else {
109            Some(repository)
110        }
111        .ok_or_else(|| {
112            crate::Error::new(
113                crate::ErrorKind::ConfigInvalid,
114                "repository is required in uri path",
115            )
116            .with_context("service", crate::Scheme::Lakefs)
117        })?;
118
119        let mut map = uri.options().clone();
120        map.insert("endpoint".to_string(), format!("https://{authority}"));
121        map.insert("repository".to_string(), repository.to_string());
122
123        if let Some(rest) = remainder {
124            if map.contains_key("branch") {
125                if !rest.is_empty() {
126                    map.insert("root".to_string(), rest.to_string());
127                }
128            } else {
129                let (branch, maybe_root) = match rest.split_once('/') {
130                    Some((branch_part, root_part)) => (branch_part, Some(root_part)),
131                    None => (rest, None),
132                };
133
134                if !branch.is_empty() {
135                    map.insert("branch".to_string(), branch.to_string());
136                }
137
138                if let Some(root_part) = maybe_root {
139                    if !root_part.is_empty() {
140                        map.insert("root".to_string(), root_part.to_string());
141                    }
142                }
143            }
144        }
145
146        Self::from_iter(map)
147    }
148
149    fn into_builder(self) -> Self::Builder {
150        LakefsBuilder { config: self }
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use crate::Configurator;
158    use crate::types::OperatorUri;
159
160    #[test]
161    fn from_uri_sets_endpoint_repository_branch_and_root() {
162        let uri = OperatorUri::new(
163            "lakefs://api.example.com/sample/main/data/dir",
164            Vec::<(String, String)>::new(),
165        )
166        .unwrap();
167
168        let cfg = LakefsConfig::from_uri(&uri).unwrap();
169        assert_eq!(cfg.endpoint.as_deref(), Some("https://api.example.com"));
170        assert_eq!(cfg.repository.as_deref(), Some("sample"));
171        assert_eq!(cfg.branch.as_deref(), Some("main"));
172        assert_eq!(cfg.root.as_deref(), Some("data/dir"));
173    }
174
175    #[test]
176    fn from_uri_requires_repository() {
177        let uri =
178            OperatorUri::new("lakefs://api.example.com", Vec::<(String, String)>::new()).unwrap();
179
180        assert!(LakefsConfig::from_uri(&uri).is_err());
181    }
182
183    #[test]
184    fn from_uri_respects_branch_override_and_sets_root() {
185        let uri = OperatorUri::new(
186            "lakefs://api.example.com/sample/content",
187            vec![("branch".to_string(), "develop".to_string())],
188        )
189        .unwrap();
190
191        let cfg = LakefsConfig::from_uri(&uri).unwrap();
192        assert_eq!(cfg.branch.as_deref(), Some("develop"));
193        assert_eq!(cfg.root.as_deref(), Some("content"));
194    }
195}