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