1use std::collections::BTreeMap;
20use std::sync::Mutex;
21
22use crate::value::Value;
23
24type Tree = BTreeMap<String, Value>;
26use serde::Serialize;
27
28use crate::error::{Error, ErrorKind};
29
30#[derive(Default)]
51pub struct Layer {
52 entries: Mutex<Tree>,
53}
54
55impl Layer {
56 #[must_use]
58 pub const fn new() -> Self {
59 Self {
60 entries: Mutex::new(BTreeMap::new()),
61 }
62 }
63
64 pub fn set<T: Serialize>(&self, path: &str, value: T) -> Result<(), Error> {
76 check_path(path)?;
77
78 let value = crate::ser::to_value(&value)
79 .map_err(|error| Error::new(ErrorKind::Type, error.to_string()).prepend_key(path))?;
80
81 self.lock().insert(path.to_owned(), value);
82
83 Ok(())
84 }
85
86 pub fn set_struct<T: serde::Serialize>(&self, value: &T) -> Result<(), Error> {
98 let serialized = crate::ser::to_value(value).map_err(|error| {
99 Error::new(
100 crate::ErrorKind::Type,
101 format!("the defaults struct did not serialize: {error}"),
102 )
103 })?;
104
105 let Value::Table(entries) = serialized else {
106 return Err(Error::new(
107 crate::ErrorKind::Type,
108 "defaults must be a struct or a map; a bare value has no field name to live under",
109 ));
110 };
111
112 let mut layer = self.lock();
113
114 for (path, value) in entries {
115 layer.insert(path, value);
116 }
117
118 Ok(())
119 }
120
121 pub fn set_text(&self, path: &str, text: &str) -> Result<(), Error> {
132 check_path(path)?;
133
134 let value = crate::text_value::from_text(text);
138
139 self.lock().insert(path.to_owned(), value);
140
141 Ok(())
142 }
143
144 pub fn set_assignments<I, S>(&self, assignments: I) -> Result<(), Error>
154 where
155 I: IntoIterator<Item = S>,
156 S: AsRef<str>,
157 {
158 for assignment in assignments {
159 let assignment = assignment.as_ref();
160
161 let Some((path, value)) = assignment.split_once('=') else {
162 return Err(Error::new(
163 ErrorKind::Type,
164 format!("`{assignment}` is not a `key=value` assignment"),
165 ));
166 };
167
168 self.set_text(path.trim(), value)?;
169 }
170
171 Ok(())
172 }
173
174 #[cfg(feature = "clap")]
199 #[cfg_attr(docsrs, doc(cfg(feature = "clap")))]
200 pub fn bind_clap(
201 &self,
202 matches: &clap::ArgMatches,
203 bindings: &[(&str, &str)],
204 ) -> Result<(), Error> {
205 for (argument, path) in bindings {
206 if matches.value_source(argument) != Some(clap::parser::ValueSource::CommandLine) {
207 continue;
208 }
209
210 let Some(mut values) = matches.get_raw(argument) else {
211 continue;
212 };
213
214 let raw: Vec<&std::ffi::OsStr> = values.by_ref().collect();
217
218 let text = match raw.as_slice() {
219 [] => continue,
220 [single] => utf8(single, argument)?.to_owned(),
221 many => {
222 let mut rendered = String::from("[");
223
224 for (index, value) in many.iter().enumerate() {
225 if index > 0 {
226 rendered.push(',');
227 }
228
229 rendered.push_str(utf8(value, argument)?);
230 }
231
232 rendered.push(']');
233 rendered
234 }
235 };
236
237 self.set_text(path, &text)?;
238 }
239
240 Ok(())
241 }
242
243 #[must_use = "the return says whether anything was removed; ignore it \
245 deliberately with `let _ =` if you do not care"]
246 pub fn unset(&self, path: &str) -> bool {
247 self.lock().remove(path).is_some()
248 }
249
250 pub fn clear(&self) {
253 self.lock().clear();
254 }
255
256 #[must_use]
259 pub fn is_empty(&self) -> bool {
260 self.lock().is_empty()
261 }
262
263 fn lock(&self) -> std::sync::MutexGuard<'_, BTreeMap<String, Value>> {
271 self.entries
272 .lock()
273 .unwrap_or_else(std::sync::PoisonError::into_inner)
274 }
275
276 pub(crate) fn tree(&self) -> Tree {
278 let mut root = Tree::new();
279
280 for (path, value) in self.lock().iter() {
281 insert_path(&mut root, path, value.clone());
282 }
283
284 root
285 }
286}
287
288#[cfg(feature = "clap")]
291fn utf8<'a>(value: &'a std::ffi::OsStr, argument: &str) -> Result<&'a str, Error> {
292 value.to_str().ok_or_else(|| {
293 Error::new(
294 ErrorKind::Type,
295 format!("`--{argument}` is not valid UTF-8"),
296 )
297 })
298}
299
300pub(crate) fn check_path(path: &str) -> Result<(), Error> {
309 if path.is_empty() || path.split('.').any(str::is_empty) {
310 return Err(Error::new(
311 ErrorKind::Type,
312 format!("`{path}` is not a usable key path"),
313 ));
314 }
315
316 if path.contains(crate::aliases::SECTION) {
322 return Err(Error::new(
323 ErrorKind::Type,
324 format!(
325 "`{path}` names another section, and this path is relative to \
326 the section being loaded; `{}` is only meaningful in the old \
327 path of an alias",
328 crate::aliases::SECTION
329 ),
330 ));
331 }
332
333 Ok(())
334}
335
336pub(crate) fn insert_path(root: &mut Tree, path: &str, value: Value) {
337 let mut segments = path.split('.').peekable();
338 let mut current = root;
339
340 while let Some(segment) = segments.next() {
341 if segments.peek().is_none() {
342 current.insert(segment.to_owned(), value);
343 return;
344 }
345
346 let entry = current
347 .entry(segment.to_owned())
348 .or_insert_with(|| Value::Table(Tree::new()));
349
350 if !matches!(entry, Value::Table(_)) {
351 *entry = Value::Table(Tree::new());
352 }
353
354 let Value::Table(nested) = entry else {
355 unreachable!("just replaced with a table")
356 };
357
358 current = nested;
359 }
360}
361
362impl std::fmt::Debug for Layer {
366 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367 let entries = self.lock();
368
369 f.debug_struct("Layer")
370 .field("keys", &entries.keys().collect::<Vec<_>>())
371 .field("len", &entries.len())
372 .finish_non_exhaustive()
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379
380 #[test]
381 fn a_fresh_layer_contributes_nothing() {
382 assert!(Layer::new().is_empty());
383 }
384
385 #[test]
386 fn setting_the_same_path_replaces_it() {
387 let layer = Layer::new();
388 layer.set("port", 1u16).unwrap();
389 layer.set("port", 2u16).unwrap();
390
391 let dict = layer.tree();
392 assert_eq!(dict.get("port"), Some(&Value::from(2u16)));
393 }
394
395 #[test]
396 fn a_dotted_path_becomes_a_nested_table() {
397 let layer = Layer::new();
398 layer.set("pool.max_size", 32u16).unwrap();
399
400 let dict = layer.tree();
401 let Some(Value::Table(pool)) = dict.get("pool") else {
402 panic!("expected a nested dict, got {dict:?}");
403 };
404
405 assert_eq!(pool.get("max_size"), Some(&Value::from(32u16)));
406 }
407
408 #[test]
409 fn siblings_under_one_parent_do_not_clobber_each_other() {
410 let layer = Layer::new();
411 layer.set("pool.max_size", 32u16).unwrap();
412 layer.set("pool.min_size", 4u16).unwrap();
413
414 let dict = layer.tree();
415 let Some(Value::Table(pool)) = dict.get("pool") else {
416 panic!("expected a nested dict");
417 };
418
419 assert_eq!(pool.len(), 2);
420 }
421
422 #[test]
423 fn a_scalar_standing_where_a_table_is_needed_is_replaced() {
424 let layer = Layer::new();
425 layer.set("pool", 1u16).unwrap();
426 layer.set("pool.max_size", 32u16).unwrap();
427
428 let dict = layer.tree();
429 assert!(matches!(dict.get("pool"), Some(Value::Table(_))));
430 }
431
432 #[test]
433 fn unset_and_clear_both_report_honestly() {
434 let layer = Layer::new();
435 layer.set("a", 1u16).unwrap();
436
437 assert!(layer.unset("a"));
438 assert!(!layer.unset("a"));
439 assert!(layer.is_empty());
440
441 layer.set("b", 1u16).unwrap();
442 layer.clear();
443 assert!(layer.is_empty());
444 }
445
446 #[test]
447 fn text_is_read_the_way_an_environment_variable_is() {
448 let layer = Layer::new();
449 layer.set_text("port", "8080").unwrap();
450 layer.set_text("enabled", "true").unwrap();
451 layer.set_text("host", "localhost").unwrap();
452
453 let dict = layer.tree();
454
455 let read = |key: &str| dict.get(key).cloned();
459
460 assert_eq!(read("port"), Some(Value::Integer(8080)));
461 assert_eq!(read("enabled"), Some(Value::Bool(true)));
462 assert_eq!(read("host"), Some(Value::String("localhost".to_owned())));
463 }
464
465 #[test]
472 fn a_quoted_escape_after_a_non_ascii_character_is_read_not_fatal() {
473 let layer = Layer::new();
474
475 layer
476 .set_text("greeting", "\"é\\nthere\"")
477 .expect("the value is read");
478
479 assert_eq!(
480 layer.tree().get("greeting").cloned(),
481 Some(Value::String("é\nthere".to_owned()))
482 );
483 }
484
485 #[test]
486 fn assignments_are_split_on_the_first_equals() {
487 let layer = Layer::new();
488 layer
489 .set_assignments(["db.host=post=gres", "db.port=5432"])
490 .unwrap();
491
492 let dict = layer.tree();
493 let Some(Value::Table(db)) = dict.get("db") else {
494 panic!("expected a nested dict");
495 };
496
497 let read = |key: &str| db.get(key).cloned();
498
499 assert_eq!(read("host"), Some(Value::String("post=gres".to_owned())));
500 assert_eq!(read("port"), Some(Value::Integer(5432)));
501 }
502
503 #[test]
504 fn an_assignment_without_an_equals_names_itself() {
505 let error = Layer::new().set_assignments(["nonsense"]).unwrap_err();
506
507 assert!(error.to_string().contains("`nonsense`"), "{error}");
508 }
509
510 #[test]
511 fn an_unusable_path_is_rejected_at_the_call_site() {
512 let layer = Layer::new();
513
514 assert!(layer.set("", 1u16).is_err());
515 assert!(layer.set("a..b", 1u16).is_err());
516 assert!(layer.set(".a", 1u16).is_err());
517 }
518
519 #[test]
520 fn structured_values_survive_the_round_trip() {
521 #[derive(serde::Serialize)]
522 struct Pool {
523 max_size: u16,
524 }
525
526 let layer = Layer::new();
527 layer.set("pool", Pool { max_size: 7 }).unwrap();
528
529 let dict = layer.tree();
530 let Some(Value::Table(pool)) = dict.get("pool") else {
531 panic!("expected a nested dict");
532 };
533
534 assert_eq!(pool.get("max_size"), Some(&Value::from(7u16)));
535 }
536}