1use std::borrow::Borrow;
18use std::convert::Infallible;
19use std::fmt;
20use std::fs;
21use std::io;
22use std::ops::Range;
23use std::path::Path;
24use std::path::PathBuf;
25use std::slice;
26use std::str::FromStr;
27use std::sync::Arc;
28
29use itertools::Itertools as _;
30use once_cell::sync::Lazy;
31use serde::de::IntoDeserializer as _;
32use serde::Deserialize;
33use thiserror::Error;
34use toml_edit::DocumentMut;
35use toml_edit::ImDocument;
36
37pub use crate::config_resolver::migrate;
38pub use crate::config_resolver::resolve;
39pub use crate::config_resolver::ConfigMigrateError;
40pub use crate::config_resolver::ConfigMigrateLayerError;
41pub use crate::config_resolver::ConfigMigrationRule;
42pub use crate::config_resolver::ConfigResolutionContext;
43use crate::file_util::IoResultExt as _;
44use crate::file_util::PathError;
45
46pub type ConfigItem = toml_edit::Item;
48pub type ConfigTable = toml_edit::Table;
50pub type ConfigTableLike<'a> = dyn toml_edit::TableLike + 'a;
52pub type ConfigValue = toml_edit::Value;
54
55#[derive(Debug, Error)]
57pub enum ConfigLoadError {
58 #[error("Failed to read configuration file")]
60 Read(#[source] PathError),
61 #[error("Configuration cannot be parsed as TOML document")]
63 Parse {
64 #[source]
66 error: toml_edit::TomlError,
67 source_path: Option<PathBuf>,
69 },
70}
71
72#[derive(Debug, Error)]
74#[error("Failed to write configuration file")]
75pub struct ConfigFileSaveError(#[source] pub PathError);
76
77#[derive(Debug, Error)]
79pub enum ConfigGetError {
80 #[error("Value not found for {name}")]
82 NotFound {
83 name: String,
85 },
86 #[error("Invalid type or value for {name}")]
88 Type {
89 name: String,
91 #[source]
93 error: Box<dyn std::error::Error + Send + Sync>,
94 source_path: Option<PathBuf>,
96 },
97}
98
99#[derive(Debug, Error)]
101pub enum ConfigUpdateError {
102 #[error("Would overwrite non-table value with parent table {name}")]
104 WouldOverwriteValue {
105 name: String,
107 },
108 #[error("Would overwrite entire table {name}")]
111 WouldOverwriteTable {
112 name: String,
114 },
115 #[error("Would delete entire table {name}")]
117 WouldDeleteTable {
118 name: String,
120 },
121}
122
123pub trait ConfigGetResultExt<T> {
125 fn optional(self) -> Result<Option<T>, ConfigGetError>;
127}
128
129impl<T> ConfigGetResultExt<T> for Result<T, ConfigGetError> {
130 fn optional(self) -> Result<Option<T>, ConfigGetError> {
131 match self {
132 Ok(value) => Ok(Some(value)),
133 Err(ConfigGetError::NotFound { .. }) => Ok(None),
134 Err(err) => Err(err),
135 }
136 }
137}
138
139#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
141pub struct ConfigNamePathBuf(Vec<toml_edit::Key>);
142
143impl ConfigNamePathBuf {
144 pub fn root() -> Self {
148 ConfigNamePathBuf(vec![])
149 }
150
151 pub fn is_root(&self) -> bool {
153 self.0.is_empty()
154 }
155
156 pub fn starts_with(&self, base: impl AsRef<[toml_edit::Key]>) -> bool {
158 self.0.starts_with(base.as_ref())
159 }
160
161 pub fn components(&self) -> slice::Iter<'_, toml_edit::Key> {
163 self.0.iter()
164 }
165
166 pub fn push(&mut self, key: impl Into<toml_edit::Key>) {
168 self.0.push(key.into());
169 }
170}
171
172impl From<&ConfigNamePathBuf> for ConfigNamePathBuf {
175 fn from(value: &ConfigNamePathBuf) -> Self {
176 value.clone()
177 }
178}
179
180impl<K: Into<toml_edit::Key>> FromIterator<K> for ConfigNamePathBuf {
181 fn from_iter<I: IntoIterator<Item = K>>(iter: I) -> Self {
182 let keys = iter.into_iter().map(|k| k.into()).collect();
183 ConfigNamePathBuf(keys)
184 }
185}
186
187impl FromStr for ConfigNamePathBuf {
188 type Err = toml_edit::TomlError;
189
190 fn from_str(s: &str) -> Result<Self, Self::Err> {
191 toml_edit::Key::parse(s).map(ConfigNamePathBuf)
193 }
194}
195
196impl AsRef<[toml_edit::Key]> for ConfigNamePathBuf {
197 fn as_ref(&self) -> &[toml_edit::Key] {
198 &self.0
199 }
200}
201
202impl fmt::Display for ConfigNamePathBuf {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 let mut components = self.0.iter().fuse();
205 if let Some(key) = components.next() {
206 write!(f, "{key}")?;
207 }
208 components.try_for_each(|key| write!(f, ".{key}"))
209 }
210}
211
212pub trait ToConfigNamePath: Sized {
218 type Output: Borrow<ConfigNamePathBuf> + Into<ConfigNamePathBuf>;
220
221 fn into_name_path(self) -> Self::Output;
223}
224
225impl ToConfigNamePath for ConfigNamePathBuf {
226 type Output = Self;
227
228 fn into_name_path(self) -> Self::Output {
229 self
230 }
231}
232
233impl ToConfigNamePath for &ConfigNamePathBuf {
234 type Output = Self;
235
236 fn into_name_path(self) -> Self::Output {
237 self
238 }
239}
240
241impl ToConfigNamePath for &'static str {
242 type Output = ConfigNamePathBuf;
244
245 fn into_name_path(self) -> Self::Output {
250 self.parse()
251 .expect("valid TOML dotted key must be provided")
252 }
253}
254
255impl<const N: usize> ToConfigNamePath for [&str; N] {
256 type Output = ConfigNamePathBuf;
257
258 fn into_name_path(self) -> Self::Output {
259 self.into_iter().collect()
260 }
261}
262
263impl<const N: usize> ToConfigNamePath for &[&str; N] {
264 type Output = ConfigNamePathBuf;
265
266 fn into_name_path(self) -> Self::Output {
267 self.as_slice().into_name_path()
268 }
269}
270
271impl ToConfigNamePath for &[&str] {
272 type Output = ConfigNamePathBuf;
273
274 fn into_name_path(self) -> Self::Output {
275 self.iter().copied().collect()
276 }
277}
278
279#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
281pub enum ConfigSource {
282 Default,
284 EnvBase,
286 User,
288 Repo,
290 EnvOverrides,
292 CommandArg,
294}
295
296#[derive(Clone, Debug)]
298pub struct ConfigLayer {
299 pub source: ConfigSource,
301 pub path: Option<PathBuf>,
303 pub data: DocumentMut,
305}
306
307impl ConfigLayer {
308 pub fn empty(source: ConfigSource) -> Self {
310 Self::with_data(source, DocumentMut::new())
311 }
312
313 pub fn with_data(source: ConfigSource, data: DocumentMut) -> Self {
315 ConfigLayer {
316 source,
317 path: None,
318 data,
319 }
320 }
321
322 pub fn parse(source: ConfigSource, text: &str) -> Result<Self, ConfigLoadError> {
324 let data = ImDocument::parse(text).map_err(|error| ConfigLoadError::Parse {
325 error,
326 source_path: None,
327 })?;
328 Ok(Self::with_data(source, data.into_mut()))
329 }
330
331 pub fn load_from_file(source: ConfigSource, path: PathBuf) -> Result<Self, ConfigLoadError> {
333 let text = fs::read_to_string(&path)
334 .context(&path)
335 .map_err(ConfigLoadError::Read)?;
336 let data = ImDocument::parse(text).map_err(|error| ConfigLoadError::Parse {
337 error,
338 source_path: Some(path.clone()),
339 })?;
340 Ok(ConfigLayer {
341 source,
342 path: Some(path),
343 data: data.into_mut(),
344 })
345 }
346
347 fn load_from_dir(source: ConfigSource, path: &Path) -> Result<Vec<Self>, ConfigLoadError> {
348 let mut file_paths: Vec<_> = path
350 .read_dir()
351 .and_then(|dir_entries| {
352 dir_entries
353 .map(|entry| Ok(entry?.path()))
354 .filter_ok(|path| path.is_file())
356 .try_collect()
357 })
358 .context(path)
359 .map_err(ConfigLoadError::Read)?;
360 file_paths.sort_unstable();
361 file_paths
362 .into_iter()
363 .map(|path| Self::load_from_file(source, path))
364 .try_collect()
365 }
366
367 pub fn is_empty(&self) -> bool {
369 self.data.is_empty()
370 }
371
372 pub fn look_up_table(
378 &self,
379 name: impl ToConfigNamePath,
380 ) -> Result<Option<&ConfigTableLike>, &ConfigItem> {
381 match self.look_up_item(name) {
382 Ok(Some(item)) => match item.as_table_like() {
383 Some(table) => Ok(Some(table)),
384 None => Err(item),
385 },
386 Ok(None) => Ok(None),
387 Err(item) => Err(item),
388 }
389 }
390
391 pub fn look_up_item(
394 &self,
395 name: impl ToConfigNamePath,
396 ) -> Result<Option<&ConfigItem>, &ConfigItem> {
397 look_up_item(self.data.as_item(), name.into_name_path().borrow())
398 }
399
400 pub fn set_value(
406 &mut self,
407 name: impl ToConfigNamePath,
408 new_value: impl Into<ConfigValue>,
409 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
410 let would_overwrite_table = |name| ConfigUpdateError::WouldOverwriteValue { name };
411 let name = name.into_name_path();
412 let name = name.borrow();
413 let (leaf_key, table_keys) = name
414 .0
415 .split_last()
416 .ok_or_else(|| would_overwrite_table(name.to_string()))?;
417 let parent_table = ensure_table(self.data.as_table_mut(), table_keys)
418 .map_err(|keys| would_overwrite_table(keys.join(".")))?;
419 match parent_table.entry_format(leaf_key) {
420 toml_edit::Entry::Occupied(mut entry) => {
421 if !entry.get().is_value() {
422 return Err(ConfigUpdateError::WouldOverwriteTable {
423 name: name.to_string(),
424 });
425 }
426 let old_item = entry.insert(toml_edit::value(new_value));
427 Ok(Some(old_item.into_value().unwrap()))
428 }
429 toml_edit::Entry::Vacant(entry) => {
430 entry.insert(toml_edit::value(new_value));
431 let mut new_key = parent_table.key_mut(leaf_key).unwrap();
433 new_key.leaf_decor_mut().clear();
434 Ok(None)
435 }
436 }
437 }
438
439 pub fn delete_value(
445 &mut self,
446 name: impl ToConfigNamePath,
447 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
448 let would_delete_table = |name| ConfigUpdateError::WouldDeleteTable { name };
449 let name = name.into_name_path();
450 let name = name.borrow();
451 let mut keys = name.components();
452 let leaf_key = keys
453 .next_back()
454 .ok_or_else(|| would_delete_table(name.to_string()))?;
455 let Some(parent_table) = keys.try_fold(
456 self.data.as_table_mut() as &mut ConfigTableLike,
457 |table, key| table.get_mut(key)?.as_table_like_mut(),
458 ) else {
459 return Ok(None);
460 };
461 match parent_table.entry(leaf_key) {
462 toml_edit::Entry::Occupied(entry) => {
463 if !entry.get().is_value() {
464 return Err(would_delete_table(name.to_string()));
465 }
466 let old_item = entry.remove();
467 Ok(Some(old_item.into_value().unwrap()))
468 }
469 toml_edit::Entry::Vacant(_) => Ok(None),
470 }
471 }
472
473 pub fn ensure_table(
479 &mut self,
480 name: impl ToConfigNamePath,
481 ) -> Result<&mut ConfigTableLike, ConfigUpdateError> {
482 let would_overwrite_table = |name| ConfigUpdateError::WouldOverwriteValue { name };
483 let name = name.into_name_path();
484 let name = name.borrow();
485 ensure_table(self.data.as_table_mut(), &name.0)
486 .map_err(|keys| would_overwrite_table(keys.join(".")))
487 }
488}
489
490fn look_up_item<'a>(
493 root_item: &'a ConfigItem,
494 name: &ConfigNamePathBuf,
495) -> Result<Option<&'a ConfigItem>, &'a ConfigItem> {
496 let mut cur_item = root_item;
497 for key in name.components() {
498 let Some(table) = cur_item.as_table_like() else {
499 return Err(cur_item);
500 };
501 cur_item = match table.get(key) {
502 Some(item) => item,
503 None => return Ok(None),
504 };
505 }
506 Ok(Some(cur_item))
507}
508
509fn ensure_table<'a, 'b>(
512 root_table: &'a mut ConfigTableLike<'a>,
513 keys: &'b [toml_edit::Key],
514) -> Result<&'a mut ConfigTableLike<'a>, &'b [toml_edit::Key]> {
515 keys.iter()
516 .enumerate()
517 .try_fold(root_table, |table, (i, key)| {
518 let sub_item = table.entry_format(key).or_insert_with(new_implicit_table);
519 sub_item.as_table_like_mut().ok_or(&keys[..=i])
520 })
521}
522
523fn new_implicit_table() -> ConfigItem {
524 let mut table = ConfigTable::new();
525 table.set_implicit(true);
526 ConfigItem::Table(table)
527}
528
529#[derive(Debug)]
532pub struct ConfigFile {
533 layer: Arc<ConfigLayer>,
534}
535
536impl ConfigFile {
537 pub fn load_or_empty(
540 source: ConfigSource,
541 path: impl Into<PathBuf>,
542 ) -> Result<Self, ConfigLoadError> {
543 let layer = match ConfigLayer::load_from_file(source, path.into()) {
544 Ok(layer) => Arc::new(layer),
545 Err(ConfigLoadError::Read(PathError { path, error }))
546 if error.kind() == io::ErrorKind::NotFound =>
547 {
548 Arc::new(ConfigLayer {
549 source,
550 path: Some(path),
551 data: DocumentMut::new(),
552 })
553 }
554 Err(err) => return Err(err),
555 };
556 Ok(ConfigFile { layer })
557 }
558
559 pub fn from_layer(layer: Arc<ConfigLayer>) -> Result<Self, Arc<ConfigLayer>> {
562 if layer.path.is_some() {
563 Ok(ConfigFile { layer })
564 } else {
565 Err(layer)
566 }
567 }
568
569 pub fn save(&self) -> Result<(), ConfigFileSaveError> {
571 fs::write(self.path(), self.layer.data.to_string())
572 .context(self.path())
573 .map_err(ConfigFileSaveError)
574 }
575
576 pub fn path(&self) -> &Path {
578 self.layer.path.as_ref().expect("path must be known")
579 }
580
581 pub fn layer(&self) -> &Arc<ConfigLayer> {
583 &self.layer
584 }
585
586 pub fn set_value(
588 &mut self,
589 name: impl ToConfigNamePath,
590 new_value: impl Into<ConfigValue>,
591 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
592 Arc::make_mut(&mut self.layer).set_value(name, new_value)
593 }
594
595 pub fn delete_value(
597 &mut self,
598 name: impl ToConfigNamePath,
599 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
600 Arc::make_mut(&mut self.layer).delete_value(name)
601 }
602}
603
604#[derive(Clone, Debug)]
618pub struct StackedConfig {
619 layers: Vec<Arc<ConfigLayer>>,
621}
622
623impl StackedConfig {
624 pub fn empty() -> Self {
626 StackedConfig { layers: vec![] }
627 }
628
629 pub fn with_defaults() -> Self {
632 StackedConfig {
633 layers: DEFAULT_CONFIG_LAYERS.to_vec(),
634 }
635 }
636
637 pub fn load_file(
640 &mut self,
641 source: ConfigSource,
642 path: impl Into<PathBuf>,
643 ) -> Result<(), ConfigLoadError> {
644 let layer = ConfigLayer::load_from_file(source, path.into())?;
645 self.add_layer(layer);
646 Ok(())
647 }
648
649 pub fn load_dir(
652 &mut self,
653 source: ConfigSource,
654 path: impl AsRef<Path>,
655 ) -> Result<(), ConfigLoadError> {
656 let layers = ConfigLayer::load_from_dir(source, path.as_ref())?;
657 self.extend_layers(layers);
658 Ok(())
659 }
660
661 pub fn add_layer(&mut self, layer: impl Into<Arc<ConfigLayer>>) {
663 let layer = layer.into();
664 let index = self.insert_point(layer.source);
665 self.layers.insert(index, layer);
666 }
667
668 pub fn extend_layers<I>(&mut self, layers: I)
670 where
671 I: IntoIterator,
672 I::Item: Into<Arc<ConfigLayer>>,
673 {
674 let layers = layers.into_iter().map(Into::into);
675 for (source, chunk) in &layers.chunk_by(|layer| layer.source) {
676 let index = self.insert_point(source);
677 self.layers.splice(index..index, chunk);
678 }
679 }
680
681 pub fn remove_layers(&mut self, source: ConfigSource) {
683 self.layers.drain(self.layer_range(source));
684 }
685
686 fn layer_range(&self, source: ConfigSource) -> Range<usize> {
687 let start = self
689 .layers
690 .iter()
691 .take_while(|layer| layer.source < source)
692 .count();
693 let count = self.layers[start..]
694 .iter()
695 .take_while(|layer| layer.source == source)
696 .count();
697 start..(start + count)
698 }
699
700 fn insert_point(&self, source: ConfigSource) -> usize {
701 let skip = self
704 .layers
705 .iter()
706 .rev()
707 .take_while(|layer| layer.source > source)
708 .count();
709 self.layers.len() - skip
710 }
711
712 pub fn layers(&self) -> &[Arc<ConfigLayer>] {
714 &self.layers
715 }
716
717 pub fn layers_mut(&mut self) -> &mut [Arc<ConfigLayer>] {
719 &mut self.layers
720 }
721
722 pub fn layers_for(&self, source: ConfigSource) -> &[Arc<ConfigLayer>] {
724 &self.layers[self.layer_range(source)]
725 }
726
727 pub fn get<'de, T: Deserialize<'de>>(
730 &self,
731 name: impl ToConfigNamePath,
732 ) -> Result<T, ConfigGetError> {
733 self.get_value_with(name, |value| T::deserialize(value.into_deserializer()))
734 }
735
736 pub fn get_value(&self, name: impl ToConfigNamePath) -> Result<ConfigValue, ConfigGetError> {
738 self.get_value_with::<_, Infallible>(name, Ok)
739 }
740
741 pub fn get_value_with<T, E: Into<Box<dyn std::error::Error + Send + Sync>>>(
744 &self,
745 name: impl ToConfigNamePath,
746 convert: impl FnOnce(ConfigValue) -> Result<T, E>,
747 ) -> Result<T, ConfigGetError> {
748 self.get_item_with(name, |item| {
749 let value = item
753 .into_value()
754 .expect("Item::None should not exist in loaded tables");
755 convert(value)
756 })
757 }
758
759 pub fn get_table(&self, name: impl ToConfigNamePath) -> Result<ConfigTable, ConfigGetError> {
764 self.get_item_with(name, |item| {
765 item.into_table()
766 .map_err(|item| format!("Expected a table, but is {}", item.type_name()))
767 })
768 }
769
770 fn get_item_with<T, E: Into<Box<dyn std::error::Error + Send + Sync>>>(
771 &self,
772 name: impl ToConfigNamePath,
773 convert: impl FnOnce(ConfigItem) -> Result<T, E>,
774 ) -> Result<T, ConfigGetError> {
775 let name = name.into_name_path();
776 let name = name.borrow();
777 let (item, layer_index) =
778 get_merged_item(&self.layers, name).ok_or_else(|| ConfigGetError::NotFound {
779 name: name.to_string(),
780 })?;
781 convert(item).map_err(|err| ConfigGetError::Type {
787 name: name.to_string(),
788 error: err.into(),
789 source_path: self.layers[layer_index].path.clone(),
790 })
791 }
792
793 pub fn table_keys(&self, name: impl ToConfigNamePath) -> impl Iterator<Item = &str> {
796 let name = name.into_name_path();
797 let name = name.borrow();
798 let to_merge = get_tables_to_merge(&self.layers, name);
799 to_merge
800 .into_iter()
801 .rev()
802 .flat_map(|table| table.iter().map(|(k, _)| k))
803 .unique()
804 }
805}
806
807fn get_merged_item(
810 layers: &[Arc<ConfigLayer>],
811 name: &ConfigNamePathBuf,
812) -> Option<(ConfigItem, usize)> {
813 let mut to_merge = Vec::new();
814 for (index, layer) in layers.iter().enumerate().rev() {
815 let item = match layer.look_up_item(name) {
816 Ok(Some(item)) => item,
817 Ok(None) => continue, Err(_) => break, };
820 if item.is_table_like() {
821 to_merge.push((item, index));
822 } else if to_merge.is_empty() {
823 return Some((item.clone(), index)); } else {
825 break; }
827 }
828
829 let (item, mut top_index) = to_merge.pop()?;
833 let mut merged = item.clone();
834 for (item, index) in to_merge.into_iter().rev() {
835 merge_items(&mut merged, item);
836 top_index = index;
837 }
838 Some((merged, top_index))
839}
840
841fn get_tables_to_merge<'a>(
843 layers: &'a [Arc<ConfigLayer>],
844 name: &ConfigNamePathBuf,
845) -> Vec<&'a ConfigTableLike<'a>> {
846 let mut to_merge = Vec::new();
847 for layer in layers.iter().rev() {
848 match layer.look_up_table(name) {
849 Ok(Some(table)) => to_merge.push(table),
850 Ok(None) => {} Err(_) => break, }
853 }
854 to_merge
855}
856
857fn merge_items(lower_item: &mut ConfigItem, upper_item: &ConfigItem) {
859 let (Some(lower_table), Some(upper_table)) =
860 (lower_item.as_table_like_mut(), upper_item.as_table_like())
861 else {
862 *lower_item = upper_item.clone();
864 return;
865 };
866 for (key, upper) in upper_table.iter() {
867 match lower_table.entry(key) {
868 toml_edit::Entry::Occupied(entry) => {
869 merge_items(entry.into_mut(), upper);
870 }
871 toml_edit::Entry::Vacant(entry) => {
872 entry.insert(upper.clone());
873 }
874 };
875 }
876}
877
878static DEFAULT_CONFIG_LAYERS: Lazy<[Arc<ConfigLayer>; 1]> = Lazy::new(|| {
879 let parse = |text: &str| Arc::new(ConfigLayer::parse(ConfigSource::Default, text).unwrap());
880 [parse(include_str!("config/misc.toml"))]
881});
882
883#[cfg(test)]
884mod tests {
885 use assert_matches::assert_matches;
886 use indoc::indoc;
887 use pretty_assertions::assert_eq;
888
889 use super::*;
890
891 #[test]
892 fn test_config_layer_set_value() {
893 let mut layer = ConfigLayer::empty(ConfigSource::User);
894 assert_matches!(
896 layer.set_value(ConfigNamePathBuf::root(), 0),
897 Err(ConfigUpdateError::WouldOverwriteValue { name }) if name.is_empty()
898 );
899
900 layer.set_value("foo", 1).unwrap();
902 layer.set_value("bar.baz.blah", "2").unwrap();
903 layer
904 .set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))
905 .unwrap();
906 layer
907 .set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))
908 .unwrap();
909 insta::assert_snapshot!(layer.data, @r#"
910 foo = 1
911
912 [bar]
913 qux = { inline = "table" }
914 to-update = { some = true }
915
916 [bar.baz]
917 blah = "2"
918 "#);
919
920 layer
922 .set_value("foo", ConfigValue::from_iter(["new", "foo"]))
923 .unwrap();
924 layer.set_value("bar.qux", "new bar.qux").unwrap();
926 layer
928 .set_value(
929 "bar.to-update.new",
930 ConfigValue::from_iter([("table", "value")]),
931 )
932 .unwrap();
933 assert_matches!(
935 layer.set_value("bar", 0),
936 Err(ConfigUpdateError::WouldOverwriteTable { name }) if name == "bar"
937 );
938 assert_matches!(
940 layer.set_value("bar.baz.blah.blah", 0),
941 Err(ConfigUpdateError::WouldOverwriteValue { name }) if name == "bar.baz.blah"
942 );
943 insta::assert_snapshot!(layer.data, @r#"
944 foo = ["new", "foo"]
945
946 [bar]
947 qux = "new bar.qux"
948 to-update = { some = true, new = { table = "value" } }
949
950 [bar.baz]
951 blah = "2"
952 "#);
953 }
954
955 #[test]
956 fn test_config_layer_set_value_formatting() {
957 let mut layer = ConfigLayer::empty(ConfigSource::User);
958 layer
960 .set_value(
961 "'foo' . bar . 'baz'",
962 ConfigValue::from_str("'value'").unwrap(),
963 )
964 .unwrap();
965 insta::assert_snapshot!(layer.data, @r"
966 ['foo' . bar]
967 'baz' = 'value'
968 ");
969
970 layer.set_value("foo.bar.baz", "new value").unwrap();
972 layer.set_value("foo.'bar'.blah", 0).unwrap();
973 insta::assert_snapshot!(layer.data, @r#"
974 ['foo' . bar]
975 'baz' = "new value"
976 blah = 0
977 "#);
978 }
979
980 #[test]
981 fn test_config_layer_delete_value() {
982 let mut layer = ConfigLayer::empty(ConfigSource::User);
983 assert_matches!(
985 layer.delete_value(ConfigNamePathBuf::root()),
986 Err(ConfigUpdateError::WouldDeleteTable { name }) if name.is_empty()
987 );
988
989 layer.set_value("foo", 1).unwrap();
991 layer.set_value("bar.baz.blah", "2").unwrap();
992 layer
993 .set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))
994 .unwrap();
995 layer
996 .set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))
997 .unwrap();
998 insta::assert_snapshot!(layer.data, @r#"
999 foo = 1
1000
1001 [bar]
1002 qux = { inline = "table" }
1003 to-update = { some = true }
1004
1005 [bar.baz]
1006 blah = "2"
1007 "#);
1008
1009 let old_value = layer.delete_value("foo").unwrap();
1011 assert_eq!(old_value.and_then(|v| v.as_integer()), Some(1));
1012 let old_value = layer.delete_value("bar.qux").unwrap();
1014 assert!(old_value.is_some_and(|v| v.is_inline_table()));
1015 let old_value = layer.delete_value("bar.to-update.some").unwrap();
1017 assert_eq!(old_value.and_then(|v| v.as_bool()), Some(true));
1018 assert_matches!(
1020 layer.delete_value("bar"),
1021 Err(ConfigUpdateError::WouldDeleteTable { name }) if name == "bar"
1022 );
1023 assert_matches!(layer.delete_value("bar.baz.blah.blah"), Ok(None));
1026 insta::assert_snapshot!(layer.data, @r#"
1027 [bar]
1028 to-update = {}
1029
1030 [bar.baz]
1031 blah = "2"
1032 "#);
1033 }
1034
1035 #[test]
1036 fn test_stacked_config_layer_order() {
1037 let empty_data = || DocumentMut::new();
1038 let layer_sources = |config: &StackedConfig| {
1039 config
1040 .layers()
1041 .iter()
1042 .map(|layer| layer.source)
1043 .collect_vec()
1044 };
1045
1046 let mut config = StackedConfig::empty();
1048 config.add_layer(ConfigLayer::with_data(ConfigSource::Repo, empty_data()));
1049 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1050 config.add_layer(ConfigLayer::with_data(ConfigSource::Default, empty_data()));
1051 assert_eq!(
1052 layer_sources(&config),
1053 vec![
1054 ConfigSource::Default,
1055 ConfigSource::User,
1056 ConfigSource::Repo,
1057 ]
1058 );
1059
1060 config.add_layer(ConfigLayer::with_data(
1062 ConfigSource::CommandArg,
1063 empty_data(),
1064 ));
1065 config.add_layer(ConfigLayer::with_data(ConfigSource::EnvBase, empty_data()));
1066 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1067 assert_eq!(
1068 layer_sources(&config),
1069 vec![
1070 ConfigSource::Default,
1071 ConfigSource::EnvBase,
1072 ConfigSource::User,
1073 ConfigSource::User,
1074 ConfigSource::Repo,
1075 ConfigSource::CommandArg,
1076 ]
1077 );
1078
1079 config.remove_layers(ConfigSource::CommandArg);
1081 config.remove_layers(ConfigSource::Default);
1082 config.remove_layers(ConfigSource::User);
1083 assert_eq!(
1084 layer_sources(&config),
1085 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1086 );
1087
1088 config.remove_layers(ConfigSource::Default);
1090 config.remove_layers(ConfigSource::EnvOverrides);
1091 assert_eq!(
1092 layer_sources(&config),
1093 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1094 );
1095
1096 config.extend_layers([
1098 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1099 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1100 ConfigLayer::with_data(ConfigSource::User, empty_data()),
1101 ]);
1102 assert_eq!(
1103 layer_sources(&config),
1104 vec![
1105 ConfigSource::EnvBase,
1106 ConfigSource::User,
1107 ConfigSource::Repo,
1108 ConfigSource::Repo,
1109 ConfigSource::Repo,
1110 ]
1111 );
1112
1113 config.remove_layers(ConfigSource::EnvBase);
1115 config.remove_layers(ConfigSource::User);
1116 config.remove_layers(ConfigSource::Repo);
1117 assert_eq!(layer_sources(&config), vec![]);
1118 }
1119
1120 fn new_user_layer(text: &str) -> ConfigLayer {
1121 ConfigLayer::parse(ConfigSource::User, text).unwrap()
1122 }
1123
1124 #[test]
1125 fn test_stacked_config_get_simple_value() {
1126 let mut config = StackedConfig::empty();
1127 config.add_layer(new_user_layer(indoc! {"
1128 a.b.c = 'a.b.c #0'
1129 "}));
1130 config.add_layer(new_user_layer(indoc! {"
1131 a.d = ['a.d #1']
1132 "}));
1133
1134 assert_eq!(config.get::<String>("a.b.c").unwrap(), "a.b.c #0");
1135
1136 assert_eq!(
1137 config.get::<Vec<String>>("a.d").unwrap(),
1138 vec!["a.d #1".to_owned()]
1139 );
1140
1141 assert_matches!(
1143 config.get::<String>("a.b.missing"),
1144 Err(ConfigGetError::NotFound { name }) if name == "a.b.missing"
1145 );
1146
1147 assert_matches!(
1149 config.get::<String>("a.b.c.d"),
1150 Err(ConfigGetError::NotFound { name }) if name == "a.b.c.d"
1151 );
1152
1153 assert_matches!(
1155 config.get::<String>("a.b"),
1156 Err(ConfigGetError::Type { name, .. }) if name == "a.b"
1157 );
1158 }
1159
1160 #[test]
1161 fn test_stacked_config_get_table_as_value() {
1162 let mut config = StackedConfig::empty();
1163 config.add_layer(new_user_layer(indoc! {"
1164 a.b = { c = 'a.b.c #0' }
1165 "}));
1166 config.add_layer(new_user_layer(indoc! {"
1167 a.d = ['a.d #1']
1168 "}));
1169
1170 insta::assert_snapshot!(
1173 config.get_value("a").unwrap(),
1174 @"{ b = { c = 'a.b.c #0' }, d = ['a.d #1'] }");
1175 }
1176
1177 #[test]
1178 fn test_stacked_config_get_inline_table() {
1179 let mut config = StackedConfig::empty();
1180 config.add_layer(new_user_layer(indoc! {"
1181 a.b = { c = 'a.b.c #0' }
1182 "}));
1183 config.add_layer(new_user_layer(indoc! {"
1184 a.b = { d = 'a.b.d #1' }
1185 "}));
1186
1187 insta::assert_snapshot!(
1189 config.get_value("a.b").unwrap(),
1190 @" { c = 'a.b.c #0' , d = 'a.b.d #1' }");
1191 }
1192
1193 #[test]
1194 fn test_stacked_config_get_inline_non_inline_table() {
1195 let mut config = StackedConfig::empty();
1196 config.add_layer(new_user_layer(indoc! {"
1197 a.b = { c = 'a.b.c #0' }
1198 "}));
1199 config.add_layer(new_user_layer(indoc! {"
1200 a.b.d = 'a.b.d #1'
1201 "}));
1202
1203 insta::assert_snapshot!(
1204 config.get_value("a.b").unwrap(),
1205 @" { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1206 insta::assert_snapshot!(
1207 config.get_table("a").unwrap(),
1208 @"b = { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1209 }
1210
1211 #[test]
1212 fn test_stacked_config_get_value_shadowing_table() {
1213 let mut config = StackedConfig::empty();
1214 config.add_layer(new_user_layer(indoc! {"
1215 a.b.c = 'a.b.c #0'
1216 "}));
1217 config.add_layer(new_user_layer(indoc! {"
1219 a.b = 'a.b #1'
1220 "}));
1221
1222 assert_eq!(config.get::<String>("a.b").unwrap(), "a.b #1");
1223
1224 assert_matches!(
1225 config.get::<String>("a.b.c"),
1226 Err(ConfigGetError::NotFound { name }) if name == "a.b.c"
1227 );
1228 }
1229
1230 #[test]
1231 fn test_stacked_config_get_table_shadowing_table() {
1232 let mut config = StackedConfig::empty();
1233 config.add_layer(new_user_layer(indoc! {"
1234 a.b = 'a.b #0'
1235 "}));
1236 config.add_layer(new_user_layer(indoc! {"
1238 a.b.c = 'a.b.c #1'
1239 "}));
1240 insta::assert_snapshot!(config.get_table("a.b").unwrap(), @"c = 'a.b.c #1'");
1241 }
1242
1243 #[test]
1244 fn test_stacked_config_get_merged_table() {
1245 let mut config = StackedConfig::empty();
1246 config.add_layer(new_user_layer(indoc! {"
1247 a.a.a = 'a.a.a #0'
1248 a.a.b = 'a.a.b #0'
1249 a.b = 'a.b #0'
1250 "}));
1251 config.add_layer(new_user_layer(indoc! {"
1252 a.a.b = 'a.a.b #1'
1253 a.a.c = 'a.a.c #1'
1254 a.c = 'a.c #1'
1255 "}));
1256 insta::assert_snapshot!(config.get_table("a").unwrap(), @r"
1257 a.a = 'a.a.a #0'
1258 a.b = 'a.a.b #1'
1259 a.c = 'a.a.c #1'
1260 b = 'a.b #0'
1261 c = 'a.c #1'
1262 ");
1263 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b", "c"]);
1264 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["a", "b", "c"]);
1265 assert_eq!(config.table_keys("a.b").collect_vec(), vec![""; 0]);
1266 assert_eq!(config.table_keys("a.missing").collect_vec(), vec![""; 0]);
1267 }
1268
1269 #[test]
1270 fn test_stacked_config_get_merged_table_shadowed_top() {
1271 let mut config = StackedConfig::empty();
1272 config.add_layer(new_user_layer(indoc! {"
1273 a.a.a = 'a.a.a #0'
1274 a.b = 'a.b #0'
1275 "}));
1276 config.add_layer(new_user_layer(indoc! {"
1278 a = 'a #1'
1279 "}));
1280 config.add_layer(new_user_layer(indoc! {"
1282 a.a.b = 'a.a.b #2'
1283 "}));
1284 insta::assert_snapshot!(config.get_table("a").unwrap(), @"a.b = 'a.a.b #2'");
1285 assert_eq!(config.table_keys("a").collect_vec(), vec!["a"]);
1286 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1287 }
1288
1289 #[test]
1290 fn test_stacked_config_get_merged_table_shadowed_child() {
1291 let mut config = StackedConfig::empty();
1292 config.add_layer(new_user_layer(indoc! {"
1293 a.a.a = 'a.a.a #0'
1294 a.b = 'a.b #0'
1295 "}));
1296 config.add_layer(new_user_layer(indoc! {"
1298 a.a = 'a.a #1'
1299 "}));
1300 config.add_layer(new_user_layer(indoc! {"
1302 a.a.b = 'a.a.b #2'
1303 "}));
1304 insta::assert_snapshot!(config.get_table("a").unwrap(), @r"
1305 a.b = 'a.a.b #2'
1306 b = 'a.b #0'
1307 ");
1308 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b"]);
1309 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1310 }
1311
1312 #[test]
1313 fn test_stacked_config_get_merged_table_shadowed_parent() {
1314 let mut config = StackedConfig::empty();
1315 config.add_layer(new_user_layer(indoc! {"
1316 a.a.a = 'a.a.a #0'
1317 "}));
1318 config.add_layer(new_user_layer(indoc! {"
1320 a = 'a #1'
1321 "}));
1322 config.add_layer(new_user_layer(indoc! {"
1324 a.a.b = 'a.a.b #2'
1325 "}));
1326 insta::assert_snapshot!(config.get_table("a.a").unwrap(), @"b = 'a.a.b #2'");
1328 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1329 }
1330}