1use std::borrow::Cow;
9use std::collections::BTreeMap;
10use std::hash::Hasher;
11use std::ops::Range;
12use std::path::PathBuf;
13use std::str;
14use std::sync::Arc;
15
16use minibytes::Text;
17
18use crate::convert::FromConfig;
19use crate::Error;
20use crate::Result;
21
22#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
23pub struct ContentHash(u64);
24
25#[auto_impl::auto_impl(&, Box, Arc)]
27pub trait Config: Send + Sync {
28 fn keys(&self, section: &str) -> Vec<Text>;
30
31 fn keys_prefixed(&self, section: &str, prefix: &str) -> Vec<Text> {
33 self.keys(section)
34 .into_iter()
35 .filter(|k| k.starts_with(prefix))
36 .collect()
37 }
38
39 fn get(&self, section: &str, name: &str) -> Option<Text> {
42 self.get_considering_unset(section, name)?
43 }
44
45 fn get_considering_unset(&self, section: &str, name: &str) -> Option<Option<Text>>;
50
51 fn get_nonempty(&self, section: &str, name: &str) -> Option<Text> {
54 self.get(section, name).filter(|v| !v.is_empty())
55 }
56
57 fn sections(&self) -> Cow<[Text]>;
59
60 fn get_sources(&self, section: &str, name: &str) -> Cow<[ValueSource]>;
62
63 fn files(&self) -> Cow<[(PathBuf, Option<ContentHash>)]> {
65 Cow::Borrowed(&[])
66 }
67
68 fn layers(&self) -> Vec<Arc<dyn Config>> {
78 Vec::new()
79 }
80
81 fn layer_name(&self) -> Text;
83
84 fn pinned(&self) -> Vec<(Text, Text, Vec<ValueSource>)> {
85 Vec::new()
86 }
87}
88
89pub trait ConfigExt: Config {
91 fn get_opt<T: FromConfig>(&self, section: &str, name: &str) -> Result<Option<T>> {
93 self.get(section, name)
94 .map(|bytes| T::try_from_str_with_config(&self, &bytes))
95 .transpose()
96 }
97
98 fn get_nonempty_opt<T: FromConfig>(&self, section: &str, name: &str) -> Result<Option<T>> {
100 self.get_nonempty(section, name)
101 .map(|bytes| T::try_from_str_with_config(&self, &bytes))
102 .transpose()
103 }
104
105 fn get_or<T: FromConfig>(
109 &self,
110 section: &str,
111 name: &str,
112 default_func: impl Fn() -> T,
113 ) -> Result<T> {
114 Ok(self.get_opt(section, name)?.unwrap_or_else(default_func))
115 }
116
117 fn get_or_default<T: Default + FromConfig>(&self, section: &str, name: &str) -> Result<T> {
121 self.get_or(section, name, Default::default)
122 }
123
124 fn must_get<T: FromConfig>(&self, section: &str, name: &str) -> Result<T> {
128 match self.get_nonempty_opt(section, name)? {
129 Some(val) => Ok(val),
130 None => Err(Error::NotSet(section.to_string(), name.to_string())),
131 }
132 }
133}
134
135impl<T: Config> ConfigExt for T {}
136
137impl Config for BTreeMap<&str, &str> {
138 fn keys(&self, section: &str) -> Vec<Text> {
139 let prefix = format!("{}.", section);
140 BTreeMap::keys(self)
141 .filter_map(|k| k.strip_prefix(&prefix).map(|k| k.to_string().into()))
142 .collect()
143 }
144
145 fn sections(&self) -> Cow<[Text]> {
146 let mut sections = Vec::new();
147 let mut last_section = None;
148 for section in BTreeMap::keys(self).filter_map(|k| k.split('.').next()) {
149 if Some(section) != last_section {
150 last_section = Some(section);
151 sections.push(Text::from(section.to_string()));
152 }
153 }
154 Cow::Owned(sections)
155 }
156
157 fn get_considering_unset(&self, section: &str, name: &str) -> Option<Option<Text>> {
158 let key: &str = &format!("{}.{}", section, name);
159 BTreeMap::get(self, &key).map(|v| Some(v.to_string().into()))
160 }
161
162 fn get_sources(&self, section: &str, name: &str) -> Cow<[ValueSource]> {
163 match Config::get(self, section, name) {
164 None => Cow::Borrowed(&[]),
165 Some(value) => Cow::Owned(vec![ValueSource {
166 value: Some(value),
167 source: Text::from_static("BTreeMap"),
168 location: None,
169 }]),
170 }
171 }
172
173 fn layer_name(&self) -> Text {
174 Text::from_static("BTreeMap")
175 }
176}
177
178impl Config for BTreeMap<String, String> {
179 fn keys(&self, section: &str) -> Vec<Text> {
180 let prefix = format!("{}.", section);
181 BTreeMap::keys(self)
182 .filter_map(|k| k.strip_prefix(&prefix).map(|k| k.to_string().into()))
183 .collect()
184 }
185
186 fn sections(&self) -> Cow<[Text]> {
187 let mut sections = Vec::new();
188 let mut last_section = None;
189 for section in BTreeMap::keys(self).filter_map(|k| k.split('.').next()) {
190 if Some(section) != last_section {
191 last_section = Some(section);
192 sections.push(Text::from(section.to_string()));
193 }
194 }
195 Cow::Owned(sections)
196 }
197
198 fn get_considering_unset(&self, section: &str, name: &str) -> Option<Option<Text>> {
199 BTreeMap::get(self, &format!("{}.{}", section, name)).map(|v| Some(v.clone().into()))
200 }
201
202 fn get_sources(&self, section: &str, name: &str) -> Cow<[ValueSource]> {
203 match Config::get(self, section, name) {
204 None => Cow::Borrowed(&[]),
205 Some(value) => Cow::Owned(vec![ValueSource {
206 value: Some(value),
207 source: Text::from_static("BTreeMap"),
208 location: None,
209 }]),
210 }
211 }
212
213 fn layer_name(&self) -> Text {
214 Text::from_static("BTreeMap")
215 }
216}
217
218impl ContentHash {
219 pub fn from_contents(contents: &[u8]) -> Self {
220 let mut xx = twox_hash::XxHash::default();
221 xx.write(contents);
222 Self(xx.finish())
223 }
224}
225
226#[derive(Clone, Debug)]
228pub struct ValueSource {
229 pub value: Option<Text>,
230 pub source: Text, pub location: Option<ValueLocation>,
232}
233
234#[derive(Clone, Debug)]
237pub struct ValueLocation {
238 pub path: Arc<PathBuf>,
239 pub content: Text,
240 pub location: Range<usize>,
241}
242
243impl ValueSource {
244 pub fn value(&self) -> &Option<Text> {
246 &self.value
247 }
248
249 pub fn source(&self) -> &Text {
252 &self.source
253 }
254
255 pub fn location(&self) -> Option<(PathBuf, Range<usize>)> {
260 self.location
261 .as_ref()
262 .map(|src| (src.path.as_ref().to_path_buf(), src.location.clone()))
263 }
264
265 pub fn file_content(&self) -> Option<Text> {
267 self.location.as_ref().map(|src| src.content.clone())
268 }
269
270 pub fn line_number(&self) -> Option<usize> {
272 let loc = self.location.as_ref()?;
273 let line_no = loc
274 .content
275 .slice(..loc.location.start)
276 .chars()
277 .filter(|&c| c == '\n')
278 .count();
279 Some(line_no + 1)
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 fn wants_impl(_: impl Config) {}
288
289 #[test]
290 fn test_btreemap_config() {
291 let map: BTreeMap<&str, &str> = vec![("foo.bar", "baz")].into_iter().collect();
292 assert_eq!(format!("{:?}", Config::keys(&map, "foo")), "[\"bar\"]");
293 assert_eq!(
294 format!("{:?}", Config::get(&map, "foo", "bar")),
295 "Some(\"baz\")"
296 );
297 assert_eq!(format!("{:?}", Config::get(&map, "foo", "baz")), "None");
298
299 wants_impl(&map);
301 }
302
303 #[test]
304 fn test_must_get() {
305 let map: BTreeMap<&str, &str> = vec![("foo.bar", "baz")].into_iter().collect();
306 assert_eq!(
307 map.must_get::<Vec<String>>("foo", "bar").unwrap(),
308 vec!["baz".to_string()]
309 );
310 assert!(matches!(
311 map.must_get::<Vec<String>>("foo", "nope"),
312 Err(Error::NotSet(_, _))
313 ));
314 }
315}