1use std::borrow::Borrow;
18use std::convert::Infallible;
19use std::fmt;
20use std::fmt::Display;
21use std::fs;
22use std::io;
23use std::ops::Range;
24use std::path::Path;
25use std::path::PathBuf;
26use std::slice;
27use std::str::FromStr;
28use std::sync::Arc;
29use std::sync::LazyLock;
30
31use itertools::Itertools as _;
32use serde::Deserialize;
33use serde::de::IntoDeserializer as _;
34use thiserror::Error;
35use toml_edit::Document;
36use toml_edit::DocumentMut;
37
38pub use crate::config_resolver::ConfigMigrateError;
39pub use crate::config_resolver::ConfigMigrateLayerError;
40pub use crate::config_resolver::ConfigMigrationRule;
41pub use crate::config_resolver::ConfigResolutionContext;
42pub use crate::config_resolver::migrate;
43pub use crate::config_resolver::resolve;
44use crate::file_util::IoResultExt as _;
45use crate::file_util::PathError;
46
47pub type ConfigItem = toml_edit::Item;
49pub type ConfigTable = toml_edit::Table;
51pub type ConfigTableLike<'a> = dyn toml_edit::TableLike + 'a;
53pub type ConfigValue = toml_edit::Value;
55
56#[derive(Debug, Error)]
58pub enum ConfigLoadError {
59 #[error("Failed to read configuration file")]
61 Read(#[source] PathError),
62 #[error("Configuration cannot be parsed as TOML document")]
64 Parse {
65 #[source]
67 error: Box<toml_edit::TomlError>,
68 source_path: Option<PathBuf>,
70 },
71}
72
73#[derive(Debug, Error)]
75#[error("Failed to write configuration file")]
76pub struct ConfigFileSaveError(#[source] pub PathError);
77
78#[derive(Debug, Error)]
80pub enum ConfigGetError {
81 #[error("Value not found for {name}")]
83 NotFound {
84 name: String,
86 },
87 #[error("Invalid type or value for {name}")]
89 Type {
90 name: String,
92 #[source]
94 error: Box<dyn std::error::Error + Send + Sync>,
95 source_path: Option<PathBuf>,
97 },
98}
99
100#[derive(Debug, Error)]
102pub enum ConfigUpdateError {
103 #[error("Would overwrite non-table value with parent table {name}")]
105 WouldOverwriteValue {
106 name: String,
108 },
109 #[error("Would overwrite entire table {name}")]
112 WouldOverwriteTable {
113 name: String,
115 },
116 #[error("Would delete entire table {name}")]
118 WouldDeleteTable {
119 name: String,
121 },
122}
123
124pub trait ConfigGetResultExt<T> {
126 fn optional(self) -> Result<Option<T>, ConfigGetError>;
128}
129
130impl<T> ConfigGetResultExt<T> for Result<T, ConfigGetError> {
131 fn optional(self) -> Result<Option<T>, ConfigGetError> {
132 match self {
133 Ok(value) => Ok(Some(value)),
134 Err(ConfigGetError::NotFound { .. }) => Ok(None),
135 Err(err) => Err(err),
136 }
137 }
138}
139
140#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
142pub struct ConfigNamePathBuf(Vec<toml_edit::Key>);
143
144impl ConfigNamePathBuf {
145 pub fn root() -> Self {
149 Self(vec![])
150 }
151
152 pub fn is_root(&self) -> bool {
154 self.0.is_empty()
155 }
156
157 pub fn starts_with(&self, base: impl AsRef<[toml_edit::Key]>) -> bool {
159 self.0.starts_with(base.as_ref())
160 }
161
162 pub fn components(&self) -> slice::Iter<'_, toml_edit::Key> {
164 self.0.iter()
165 }
166
167 pub fn push(&mut self, key: impl Into<toml_edit::Key>) {
169 self.0.push(key.into());
170 }
171}
172
173impl From<&Self> for ConfigNamePathBuf {
176 fn from(value: &Self) -> Self {
177 value.clone()
178 }
179}
180
181impl<K: Into<toml_edit::Key>> FromIterator<K> for ConfigNamePathBuf {
182 fn from_iter<I: IntoIterator<Item = K>>(iter: I) -> Self {
183 let keys = iter.into_iter().map(|k| k.into()).collect();
184 Self(keys)
185 }
186}
187
188impl FromStr for ConfigNamePathBuf {
189 type Err = toml_edit::TomlError;
190
191 fn from_str(s: &str) -> Result<Self, Self::Err> {
192 toml_edit::Key::parse(s).map(ConfigNamePathBuf)
194 }
195}
196
197impl AsRef<[toml_edit::Key]> for ConfigNamePathBuf {
198 fn as_ref(&self) -> &[toml_edit::Key] {
199 &self.0
200 }
201}
202
203impl Display for ConfigNamePathBuf {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 let mut components = self.0.iter().fuse();
206 if let Some(key) = components.next() {
207 write!(f, "{key}")?;
208 }
209 components.try_for_each(|key| write!(f, ".{key}"))
210 }
211}
212
213pub trait ToConfigNamePath: Sized {
219 type Output: Borrow<ConfigNamePathBuf> + Into<ConfigNamePathBuf>;
221
222 fn into_name_path(self) -> Self::Output;
224}
225
226impl ToConfigNamePath for ConfigNamePathBuf {
227 type Output = Self;
228
229 fn into_name_path(self) -> Self::Output {
230 self
231 }
232}
233
234impl ToConfigNamePath for &ConfigNamePathBuf {
235 type Output = Self;
236
237 fn into_name_path(self) -> Self::Output {
238 self
239 }
240}
241
242impl ToConfigNamePath for &'static str {
243 type Output = ConfigNamePathBuf;
245
246 fn into_name_path(self) -> Self::Output {
251 self.parse()
252 .expect("valid TOML dotted key must be provided")
253 }
254}
255
256impl<const N: usize> ToConfigNamePath for [&str; N] {
257 type Output = ConfigNamePathBuf;
258
259 fn into_name_path(self) -> Self::Output {
260 self.into_iter().collect()
261 }
262}
263
264impl<const N: usize> ToConfigNamePath for &[&str; N] {
265 type Output = ConfigNamePathBuf;
266
267 fn into_name_path(self) -> Self::Output {
268 self.as_slice().into_name_path()
269 }
270}
271
272impl ToConfigNamePath for &[&str] {
273 type Output = ConfigNamePathBuf;
274
275 fn into_name_path(self) -> Self::Output {
276 self.iter().copied().collect()
277 }
278}
279
280#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
282pub enum ConfigSource {
283 Default,
285 EnvBase,
287 User,
289 Repo,
291 Workspace,
293 EnvOverrides,
295 CommandArg,
297}
298
299impl Display for ConfigSource {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 use ConfigSource::*;
302 let c = match self {
303 Default => "default",
304 User => "user",
305 Repo => "repo",
306 Workspace => "workspace",
307 CommandArg => "cli",
308 EnvBase | EnvOverrides => "env",
309 };
310 write!(f, "{c}")
311 }
312}
313
314#[derive(Clone, Debug)]
316pub struct ConfigLayer {
317 pub source: ConfigSource,
319 pub path: Option<PathBuf>,
321 pub data: DocumentMut,
323}
324
325impl ConfigLayer {
326 pub fn empty(source: ConfigSource) -> Self {
328 Self::with_data(source, DocumentMut::new())
329 }
330
331 pub fn with_data(source: ConfigSource, data: DocumentMut) -> Self {
333 Self {
334 source,
335 path: None,
336 data,
337 }
338 }
339
340 pub fn parse(source: ConfigSource, text: &str) -> Result<Self, ConfigLoadError> {
342 let data = Document::parse(text).map_err(|error| ConfigLoadError::Parse {
343 error: Box::new(error),
344 source_path: None,
345 })?;
346 Ok(Self::with_data(source, data.into_mut()))
347 }
348
349 pub fn load_from_file(source: ConfigSource, path: PathBuf) -> Result<Self, ConfigLoadError> {
351 let text = fs::read_to_string(&path)
352 .context(&path)
353 .map_err(ConfigLoadError::Read)?;
354 let data = Document::parse(text).map_err(|error| ConfigLoadError::Parse {
355 error: Box::new(error),
356 source_path: Some(path.clone()),
357 })?;
358 Ok(Self {
359 source,
360 path: Some(path),
361 data: data.into_mut(),
362 })
363 }
364
365 fn load_from_dir(source: ConfigSource, path: &Path) -> Result<Vec<Self>, ConfigLoadError> {
366 let mut file_paths: Vec<_> = path
368 .read_dir()
369 .and_then(|dir_entries| {
370 dir_entries
371 .map(|entry| Ok(entry?.path()))
372 .filter_ok(|path| path.is_file() && path.extension() == Some("toml".as_ref()))
373 .try_collect()
374 })
375 .context(path)
376 .map_err(ConfigLoadError::Read)?;
377 file_paths.sort_unstable();
378 file_paths
379 .into_iter()
380 .map(|path| Self::load_from_file(source, path))
381 .try_collect()
382 }
383
384 pub fn is_empty(&self) -> bool {
386 self.data.is_empty()
387 }
388
389 pub fn look_up_table(
395 &self,
396 name: impl ToConfigNamePath,
397 ) -> Result<Option<&ConfigTableLike<'_>>, &ConfigItem> {
398 match self.look_up_item(name) {
399 Ok(Some(item)) => match item.as_table_like() {
400 Some(table) => Ok(Some(table)),
401 None => Err(item),
402 },
403 Ok(None) => Ok(None),
404 Err(item) => Err(item),
405 }
406 }
407
408 pub fn look_up_item(
411 &self,
412 name: impl ToConfigNamePath,
413 ) -> Result<Option<&ConfigItem>, &ConfigItem> {
414 look_up_item(self.data.as_item(), name.into_name_path().borrow())
415 }
416
417 pub fn set_value(
423 &mut self,
424 name: impl ToConfigNamePath,
425 new_value: impl Into<ConfigValue>,
426 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
427 let would_overwrite_table = |name| ConfigUpdateError::WouldOverwriteValue { name };
428 let name = name.into_name_path();
429 let name = name.borrow();
430 let (leaf_key, table_keys) = name
431 .0
432 .split_last()
433 .ok_or_else(|| would_overwrite_table(name.to_string()))?;
434 let parent_table = ensure_table(self.data.as_table_mut(), table_keys)
435 .map_err(|keys| would_overwrite_table(keys.join(".")))?;
436 match parent_table.entry_format(leaf_key) {
437 toml_edit::Entry::Occupied(mut entry) => {
438 if !entry.get().is_value() {
439 return Err(ConfigUpdateError::WouldOverwriteTable {
440 name: name.to_string(),
441 });
442 }
443 let old_item = entry.insert(toml_edit::value(new_value));
444 Ok(Some(old_item.into_value().unwrap()))
445 }
446 toml_edit::Entry::Vacant(entry) => {
447 entry.insert(toml_edit::value(new_value));
448 let mut new_key = parent_table.key_mut(leaf_key).unwrap();
450 new_key.leaf_decor_mut().clear();
451 Ok(None)
452 }
453 }
454 }
455
456 pub fn delete_value(
462 &mut self,
463 name: impl ToConfigNamePath,
464 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
465 let would_delete_table = |name| ConfigUpdateError::WouldDeleteTable { name };
466 let name = name.into_name_path();
467 let name = name.borrow();
468 let mut keys = name.components();
469 let leaf_key = keys
470 .next_back()
471 .ok_or_else(|| would_delete_table(name.to_string()))?;
472 let Some(parent_table) = keys.try_fold(
473 self.data.as_table_mut() as &mut ConfigTableLike,
474 |table, key| table.get_mut(key)?.as_table_like_mut(),
475 ) else {
476 return Ok(None);
477 };
478 match parent_table.entry(leaf_key) {
479 toml_edit::Entry::Occupied(entry) => {
480 if !entry.get().is_value() {
481 return Err(would_delete_table(name.to_string()));
482 }
483 let old_item = entry.remove();
484 Ok(Some(old_item.into_value().unwrap()))
485 }
486 toml_edit::Entry::Vacant(_) => Ok(None),
487 }
488 }
489
490 pub fn ensure_table(
496 &mut self,
497 name: impl ToConfigNamePath,
498 ) -> Result<&mut ConfigTableLike<'_>, ConfigUpdateError> {
499 let would_overwrite_table = |name| ConfigUpdateError::WouldOverwriteValue { name };
500 let name = name.into_name_path();
501 let name = name.borrow();
502 ensure_table(self.data.as_table_mut(), &name.0)
503 .map_err(|keys| would_overwrite_table(keys.join(".")))
504 }
505}
506
507fn look_up_item<'a>(
510 root_item: &'a ConfigItem,
511 name: &ConfigNamePathBuf,
512) -> Result<Option<&'a ConfigItem>, &'a ConfigItem> {
513 let mut cur_item = root_item;
514 for key in name.components() {
515 let Some(table) = cur_item.as_table_like() else {
516 return Err(cur_item);
517 };
518 cur_item = match table.get(key) {
519 Some(item) => item,
520 None => return Ok(None),
521 };
522 }
523 Ok(Some(cur_item))
524}
525
526fn ensure_table<'a, 'b>(
529 root_table: &'a mut ConfigTableLike<'a>,
530 keys: &'b [toml_edit::Key],
531) -> Result<&'a mut ConfigTableLike<'a>, &'b [toml_edit::Key]> {
532 keys.iter()
533 .enumerate()
534 .try_fold(root_table, |table, (i, key)| {
535 let sub_item = table.entry_format(key).or_insert_with(new_implicit_table);
536 sub_item.as_table_like_mut().ok_or(&keys[..=i])
537 })
538}
539
540fn new_implicit_table() -> ConfigItem {
541 let mut table = ConfigTable::new();
542 table.set_implicit(true);
543 ConfigItem::Table(table)
544}
545
546#[derive(Clone, Debug)]
549pub struct ConfigFile {
550 layer: Arc<ConfigLayer>,
551}
552
553impl ConfigFile {
554 pub fn load_or_empty(
557 source: ConfigSource,
558 path: impl Into<PathBuf>,
559 ) -> Result<Self, ConfigLoadError> {
560 let layer = match ConfigLayer::load_from_file(source, path.into()) {
561 Ok(layer) => Arc::new(layer),
562 Err(ConfigLoadError::Read(PathError {
563 path,
564 source: error,
565 })) if error.kind() == io::ErrorKind::NotFound => {
566 let mut data = DocumentMut::new();
567 data.decor_mut()
568 .set_prefix("#:schema https://docs.jj-vcs.dev/latest/config-schema.json\n\n");
569 let layer = ConfigLayer {
570 source,
571 path: Some(path),
572 data,
573 };
574 Arc::new(layer)
575 }
576 Err(err) => return Err(err),
577 };
578 Ok(Self { layer })
579 }
580
581 pub fn from_layer(layer: Arc<ConfigLayer>) -> Result<Self, Arc<ConfigLayer>> {
584 if layer.path.is_some() {
585 Ok(Self { layer })
586 } else {
587 Err(layer)
588 }
589 }
590
591 pub fn save(&self) -> Result<(), ConfigFileSaveError> {
593 fs::write(self.path(), self.layer.data.to_string())
594 .context(self.path())
595 .map_err(ConfigFileSaveError)
596 }
597
598 pub fn path(&self) -> &Path {
600 self.layer.path.as_ref().expect("path must be known")
601 }
602
603 pub fn layer(&self) -> &Arc<ConfigLayer> {
605 &self.layer
606 }
607
608 pub fn data_mut(&mut self) -> &mut DocumentMut {
612 &mut Arc::make_mut(&mut self.layer).data
613 }
614
615 pub fn set_value(
617 &mut self,
618 name: impl ToConfigNamePath,
619 new_value: impl Into<ConfigValue>,
620 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
621 Arc::make_mut(&mut self.layer).set_value(name, new_value)
622 }
623
624 pub fn delete_value(
626 &mut self,
627 name: impl ToConfigNamePath,
628 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
629 Arc::make_mut(&mut self.layer).delete_value(name)
630 }
631}
632
633#[derive(Clone, Debug)]
647pub struct StackedConfig {
648 layers: Vec<Arc<ConfigLayer>>,
650}
651
652impl StackedConfig {
653 pub fn empty() -> Self {
655 Self { layers: vec![] }
656 }
657
658 pub fn with_defaults() -> Self {
661 Self {
662 layers: DEFAULT_CONFIG_LAYERS.to_vec(),
663 }
664 }
665
666 pub fn load_file(
669 &mut self,
670 source: ConfigSource,
671 path: impl Into<PathBuf>,
672 ) -> Result<(), ConfigLoadError> {
673 let layer = ConfigLayer::load_from_file(source, path.into())?;
674 self.add_layer(layer);
675 Ok(())
676 }
677
678 pub fn load_dir(
681 &mut self,
682 source: ConfigSource,
683 path: impl AsRef<Path>,
684 ) -> Result<(), ConfigLoadError> {
685 let layers = ConfigLayer::load_from_dir(source, path.as_ref())?;
686 self.extend_layers(layers);
687 Ok(())
688 }
689
690 pub fn add_layer(&mut self, layer: impl Into<Arc<ConfigLayer>>) {
692 let layer = layer.into();
693 let index = self.insert_point(layer.source);
694 self.layers.insert(index, layer);
695 }
696
697 pub fn extend_layers<I>(&mut self, layers: I)
699 where
700 I: IntoIterator,
701 I::Item: Into<Arc<ConfigLayer>>,
702 {
703 let layers = layers.into_iter().map(Into::into);
704 for (source, chunk) in &layers.chunk_by(|layer| layer.source) {
705 let index = self.insert_point(source);
706 self.layers.splice(index..index, chunk);
707 }
708 }
709
710 pub fn remove_layers(&mut self, source: ConfigSource) {
712 self.layers.drain(self.layer_range(source));
713 }
714
715 fn layer_range(&self, source: ConfigSource) -> Range<usize> {
716 let start = self
718 .layers
719 .iter()
720 .take_while(|layer| layer.source < source)
721 .count();
722 let count = self.layers[start..]
723 .iter()
724 .take_while(|layer| layer.source == source)
725 .count();
726 start..(start + count)
727 }
728
729 fn insert_point(&self, source: ConfigSource) -> usize {
730 let skip = self
733 .layers
734 .iter()
735 .rev()
736 .take_while(|layer| layer.source > source)
737 .count();
738 self.layers.len() - skip
739 }
740
741 pub fn layers(&self) -> &[Arc<ConfigLayer>] {
743 &self.layers
744 }
745
746 pub fn layers_mut(&mut self) -> &mut [Arc<ConfigLayer>] {
748 &mut self.layers
749 }
750
751 pub fn layers_for(&self, source: ConfigSource) -> &[Arc<ConfigLayer>] {
753 &self.layers[self.layer_range(source)]
754 }
755
756 pub fn get<'de, T: Deserialize<'de>>(
759 &self,
760 name: impl ToConfigNamePath,
761 ) -> Result<T, ConfigGetError> {
762 self.get_value_with(name, |value| T::deserialize(value.into_deserializer()))
763 }
764
765 pub fn get_value(&self, name: impl ToConfigNamePath) -> Result<ConfigValue, ConfigGetError> {
767 self.get_value_with::<_, Infallible>(name, Ok)
768 }
769
770 pub fn get_value_with<T, E: Into<Box<dyn std::error::Error + Send + Sync>>>(
773 &self,
774 name: impl ToConfigNamePath,
775 convert: impl FnOnce(ConfigValue) -> Result<T, E>,
776 ) -> Result<T, ConfigGetError> {
777 self.get_item_with(name, |item| {
778 let value = item
782 .into_value()
783 .expect("Item::None should not exist in loaded tables");
784 convert(value)
785 })
786 }
787
788 pub fn get_table(&self, name: impl ToConfigNamePath) -> Result<ConfigTable, ConfigGetError> {
793 self.get_item_with(name, |item| {
794 item.into_table()
795 .map_err(|item| format!("Expected a table, but is {}", item.type_name()))
796 })
797 }
798
799 fn get_item_with<T, E: Into<Box<dyn std::error::Error + Send + Sync>>>(
800 &self,
801 name: impl ToConfigNamePath,
802 convert: impl FnOnce(ConfigItem) -> Result<T, E>,
803 ) -> Result<T, ConfigGetError> {
804 let name = name.into_name_path();
805 let name = name.borrow();
806 let (item, layer_index) =
807 get_merged_item(&self.layers, name).ok_or_else(|| ConfigGetError::NotFound {
808 name: name.to_string(),
809 })?;
810 convert(item).map_err(|err| ConfigGetError::Type {
816 name: name.to_string(),
817 error: err.into(),
818 source_path: self.layers[layer_index].path.clone(),
819 })
820 }
821
822 pub fn table_keys(&self, name: impl ToConfigNamePath) -> impl Iterator<Item = &str> {
825 let name = name.into_name_path();
826 let name = name.borrow();
827 let to_merge = get_tables_to_merge(&self.layers, name);
828 to_merge
829 .into_iter()
830 .rev()
831 .flat_map(|table| table.iter().map(|(k, _)| k))
832 .unique()
833 }
834}
835
836fn get_merged_item(
839 layers: &[Arc<ConfigLayer>],
840 name: &ConfigNamePathBuf,
841) -> Option<(ConfigItem, usize)> {
842 let mut to_merge = Vec::new();
843 for (index, layer) in layers.iter().enumerate().rev() {
844 let item = match layer.look_up_item(name) {
845 Ok(Some(item)) => item,
846 Ok(None) => continue, Err(_) => break, };
849 if item.is_table_like() {
850 to_merge.push((item, index));
851 } else if to_merge.is_empty() {
852 return Some((item.clone(), index)); } else {
854 break; }
856 }
857
858 let (item, mut top_index) = to_merge.pop()?;
862 let mut merged = item.clone();
863 for (item, index) in to_merge.into_iter().rev() {
864 merge_items(&mut merged, item);
865 top_index = index;
866 }
867 Some((merged, top_index))
868}
869
870fn get_tables_to_merge<'a>(
872 layers: &'a [Arc<ConfigLayer>],
873 name: &ConfigNamePathBuf,
874) -> Vec<&'a ConfigTableLike<'a>> {
875 let mut to_merge = Vec::new();
876 for layer in layers.iter().rev() {
877 match layer.look_up_table(name) {
878 Ok(Some(table)) => to_merge.push(table),
879 Ok(None) => {} Err(_) => break, }
882 }
883 to_merge
884}
885
886fn merge_items(lower_item: &mut ConfigItem, upper_item: &ConfigItem) {
888 let (Some(lower_table), Some(upper_table)) =
889 (lower_item.as_table_like_mut(), upper_item.as_table_like())
890 else {
891 *lower_item = upper_item.clone();
893 return;
894 };
895 for (key, upper) in upper_table.iter() {
896 match lower_table.entry(key) {
897 toml_edit::Entry::Occupied(entry) => {
898 merge_items(entry.into_mut(), upper);
899 }
900 toml_edit::Entry::Vacant(entry) => {
901 entry.insert(upper.clone());
902 }
903 }
904 }
905}
906
907static DEFAULT_CONFIG_LAYERS: LazyLock<[Arc<ConfigLayer>; 1]> = LazyLock::new(|| {
908 let parse = |text: &str| Arc::new(ConfigLayer::parse(ConfigSource::Default, text).unwrap());
909 [parse(include_str!("config/misc.toml"))]
910});
911
912#[cfg(test)]
913mod tests {
914 use assert_matches::assert_matches;
915 use indoc::indoc;
916 use pretty_assertions::assert_eq;
917
918 use super::*;
919 use crate::tests::TestResult;
920
921 #[test]
922 fn test_config_layer_set_value() -> TestResult {
923 let mut layer = ConfigLayer::empty(ConfigSource::User);
924 assert_matches!(
926 layer.set_value(ConfigNamePathBuf::root(), 0),
927 Err(ConfigUpdateError::WouldOverwriteValue { name }) if name.is_empty()
928 );
929
930 layer.set_value("foo", 1)?;
932 layer.set_value("bar.baz.blah", "2")?;
933 layer.set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))?;
934 layer.set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))?;
935 insta::assert_snapshot!(layer.data, @r#"
936 foo = 1
937
938 [bar]
939 qux = { inline = "table" }
940 to-update = { some = true }
941
942 [bar.baz]
943 blah = "2"
944 "#);
945
946 layer.set_value("foo", ConfigValue::from_iter(["new", "foo"]))?;
948 layer.set_value("bar.qux", "new bar.qux")?;
950 layer.set_value(
952 "bar.to-update.new",
953 ConfigValue::from_iter([("table", "value")]),
954 )?;
955 assert_matches!(
957 layer.set_value("bar", 0),
958 Err(ConfigUpdateError::WouldOverwriteTable { name }) if name == "bar"
959 );
960 assert_matches!(
962 layer.set_value("bar.baz.blah.blah", 0),
963 Err(ConfigUpdateError::WouldOverwriteValue { name }) if name == "bar.baz.blah"
964 );
965 insta::assert_snapshot!(layer.data, @r#"
966 foo = ["new", "foo"]
967
968 [bar]
969 qux = "new bar.qux"
970 to-update = { some = true, new = { table = "value" } }
971
972 [bar.baz]
973 blah = "2"
974 "#);
975 Ok(())
976 }
977
978 #[test]
979 fn test_config_layer_set_value_formatting() -> TestResult {
980 let mut layer = ConfigLayer::empty(ConfigSource::User);
981 layer.set_value("'foo' . bar . 'baz'", ConfigValue::from_str("'value'")?)?;
983 insta::assert_snapshot!(layer.data, @"
984 ['foo' . bar]
985 'baz' = 'value'
986 ");
987
988 layer.set_value("foo.bar.baz", "new value")?;
990 layer.set_value("foo.'bar'.blah", 0)?;
991 insta::assert_snapshot!(layer.data, @r#"
992 ['foo' . bar]
993 'baz' = "new value"
994 blah = 0
995 "#);
996 Ok(())
997 }
998
999 #[test]
1000 fn test_config_layer_set_value_inline_table() -> TestResult {
1001 let mut layer = ConfigLayer::empty(ConfigSource::User);
1002 layer.set_value("a", ConfigValue::from_iter([("b", "a.b")]))?;
1003 insta::assert_snapshot!(layer.data, @r#"a = { b = "a.b" }"#);
1004
1005 layer.set_value("a.c.d", "a.c.d")?;
1007 insta::assert_snapshot!(layer.data, @r#"a = { b = "a.b", c.d = "a.c.d" }"#);
1008 Ok(())
1009 }
1010
1011 #[test]
1012 fn test_config_layer_delete_value() -> TestResult {
1013 let mut layer = ConfigLayer::empty(ConfigSource::User);
1014 assert_matches!(
1016 layer.delete_value(ConfigNamePathBuf::root()),
1017 Err(ConfigUpdateError::WouldDeleteTable { name }) if name.is_empty()
1018 );
1019
1020 layer.set_value("foo", 1)?;
1022 layer.set_value("bar.baz.blah", "2")?;
1023 layer.set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))?;
1024 layer.set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))?;
1025 insta::assert_snapshot!(layer.data, @r#"
1026 foo = 1
1027
1028 [bar]
1029 qux = { inline = "table" }
1030 to-update = { some = true }
1031
1032 [bar.baz]
1033 blah = "2"
1034 "#);
1035
1036 let old_value = layer.delete_value("foo")?;
1038 assert_eq!(old_value.and_then(|v| v.as_integer()), Some(1));
1039 let old_value = layer.delete_value("bar.qux")?;
1041 assert!(old_value.is_some_and(|v| v.is_inline_table()));
1042 let old_value = layer.delete_value("bar.to-update.some")?;
1044 assert_eq!(old_value.and_then(|v| v.as_bool()), Some(true));
1045 assert_matches!(
1047 layer.delete_value("bar"),
1048 Err(ConfigUpdateError::WouldDeleteTable { name }) if name == "bar"
1049 );
1050 assert_matches!(layer.delete_value("bar.baz.blah.blah"), Ok(None));
1053 insta::assert_snapshot!(layer.data, @r#"
1054 [bar]
1055 to-update = {}
1056
1057 [bar.baz]
1058 blah = "2"
1059 "#);
1060 Ok(())
1061 }
1062
1063 #[test]
1064 fn test_stacked_config_layer_order() {
1065 let empty_data = || DocumentMut::new();
1066 let layer_sources = |config: &StackedConfig| {
1067 config
1068 .layers()
1069 .iter()
1070 .map(|layer| layer.source)
1071 .collect_vec()
1072 };
1073
1074 let mut config = StackedConfig::empty();
1076 config.add_layer(ConfigLayer::with_data(ConfigSource::Repo, empty_data()));
1077 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1078 config.add_layer(ConfigLayer::with_data(ConfigSource::Default, empty_data()));
1079 assert_eq!(
1080 layer_sources(&config),
1081 vec![
1082 ConfigSource::Default,
1083 ConfigSource::User,
1084 ConfigSource::Repo,
1085 ]
1086 );
1087
1088 config.add_layer(ConfigLayer::with_data(
1090 ConfigSource::CommandArg,
1091 empty_data(),
1092 ));
1093 config.add_layer(ConfigLayer::with_data(ConfigSource::EnvBase, empty_data()));
1094 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1095 assert_eq!(
1096 layer_sources(&config),
1097 vec![
1098 ConfigSource::Default,
1099 ConfigSource::EnvBase,
1100 ConfigSource::User,
1101 ConfigSource::User,
1102 ConfigSource::Repo,
1103 ConfigSource::CommandArg,
1104 ]
1105 );
1106
1107 config.remove_layers(ConfigSource::CommandArg);
1109 config.remove_layers(ConfigSource::Default);
1110 config.remove_layers(ConfigSource::User);
1111 assert_eq!(
1112 layer_sources(&config),
1113 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1114 );
1115
1116 config.remove_layers(ConfigSource::Default);
1118 config.remove_layers(ConfigSource::EnvOverrides);
1119 assert_eq!(
1120 layer_sources(&config),
1121 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1122 );
1123
1124 config.extend_layers([
1126 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1127 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1128 ConfigLayer::with_data(ConfigSource::User, empty_data()),
1129 ]);
1130 assert_eq!(
1131 layer_sources(&config),
1132 vec![
1133 ConfigSource::EnvBase,
1134 ConfigSource::User,
1135 ConfigSource::Repo,
1136 ConfigSource::Repo,
1137 ConfigSource::Repo,
1138 ]
1139 );
1140
1141 config.remove_layers(ConfigSource::EnvBase);
1143 config.remove_layers(ConfigSource::User);
1144 config.remove_layers(ConfigSource::Repo);
1145 assert_eq!(layer_sources(&config), vec![]);
1146 }
1147
1148 fn new_user_layer(text: &str) -> ConfigLayer {
1149 ConfigLayer::parse(ConfigSource::User, text).unwrap()
1150 }
1151
1152 #[test]
1153 fn test_stacked_config_get_simple_value() -> TestResult {
1154 let mut config = StackedConfig::empty();
1155 config.add_layer(new_user_layer(indoc! {"
1156 a.b.c = 'a.b.c #0'
1157 "}));
1158 config.add_layer(new_user_layer(indoc! {"
1159 a.d = ['a.d #1']
1160 "}));
1161
1162 assert_eq!(config.get::<String>("a.b.c")?, "a.b.c #0");
1163
1164 assert_eq!(config.get::<Vec<String>>("a.d")?, vec!["a.d #1".to_owned()]);
1165
1166 assert_matches!(
1168 config.get::<String>("a.b.missing"),
1169 Err(ConfigGetError::NotFound { name }) if name == "a.b.missing"
1170 );
1171
1172 assert_matches!(
1174 config.get::<String>("a.b.c.d"),
1175 Err(ConfigGetError::NotFound { name }) if name == "a.b.c.d"
1176 );
1177
1178 assert_matches!(
1180 config.get::<String>("a.b"),
1181 Err(ConfigGetError::Type { name, .. }) if name == "a.b"
1182 );
1183 Ok(())
1184 }
1185
1186 #[test]
1187 fn test_stacked_config_get_table_as_value() -> TestResult {
1188 let mut config = StackedConfig::empty();
1189 config.add_layer(new_user_layer(indoc! {"
1190 a.b = { c = 'a.b.c #0' }
1191 "}));
1192 config.add_layer(new_user_layer(indoc! {"
1193 a.d = ['a.d #1']
1194 "}));
1195
1196 insta::assert_snapshot!(
1199 config.get_value("a")?,
1200 @"{ b = { c = 'a.b.c #0' }, d = ['a.d #1'] }");
1201 Ok(())
1202 }
1203
1204 #[test]
1205 fn test_stacked_config_get_inline_table() -> TestResult {
1206 let mut config = StackedConfig::empty();
1207 config.add_layer(new_user_layer(indoc! {"
1208 a.b = { c = 'a.b.c #0' }
1209 "}));
1210 config.add_layer(new_user_layer(indoc! {"
1211 a.b = { d = 'a.b.d #1' }
1212 "}));
1213
1214 insta::assert_snapshot!(
1216 config.get_value("a.b")?,
1217 @" { c = 'a.b.c #0' , d = 'a.b.d #1' }");
1218 Ok(())
1219 }
1220
1221 #[test]
1222 fn test_stacked_config_get_inline_non_inline_table() -> TestResult {
1223 let mut config = StackedConfig::empty();
1224 config.add_layer(new_user_layer(indoc! {"
1225 a.b = { c = 'a.b.c #0' }
1226 "}));
1227 config.add_layer(new_user_layer(indoc! {"
1228 a.b.d = 'a.b.d #1'
1229 "}));
1230
1231 insta::assert_snapshot!(
1232 config.get_value("a.b")?,
1233 @" { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1234 insta::assert_snapshot!(
1235 config.get_table("a")?,
1236 @"b = { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1237 Ok(())
1238 }
1239
1240 #[test]
1241 fn test_stacked_config_get_value_shadowing_table() -> TestResult {
1242 let mut config = StackedConfig::empty();
1243 config.add_layer(new_user_layer(indoc! {"
1244 a.b.c = 'a.b.c #0'
1245 "}));
1246 config.add_layer(new_user_layer(indoc! {"
1248 a.b = 'a.b #1'
1249 "}));
1250
1251 assert_eq!(config.get::<String>("a.b")?, "a.b #1");
1252
1253 assert_matches!(
1254 config.get::<String>("a.b.c"),
1255 Err(ConfigGetError::NotFound { name }) if name == "a.b.c"
1256 );
1257 Ok(())
1258 }
1259
1260 #[test]
1261 fn test_stacked_config_get_table_shadowing_table() -> TestResult {
1262 let mut config = StackedConfig::empty();
1263 config.add_layer(new_user_layer(indoc! {"
1264 a.b = 'a.b #0'
1265 "}));
1266 config.add_layer(new_user_layer(indoc! {"
1268 a.b.c = 'a.b.c #1'
1269 "}));
1270 insta::assert_snapshot!(config.get_table("a.b")?, @"c = 'a.b.c #1'");
1271 Ok(())
1272 }
1273
1274 #[test]
1275 fn test_stacked_config_get_merged_table() -> TestResult {
1276 let mut config = StackedConfig::empty();
1277 config.add_layer(new_user_layer(indoc! {"
1278 a.a.a = 'a.a.a #0'
1279 a.a.b = 'a.a.b #0'
1280 a.b = 'a.b #0'
1281 "}));
1282 config.add_layer(new_user_layer(indoc! {"
1283 a.a.b = 'a.a.b #1'
1284 a.a.c = 'a.a.c #1'
1285 a.c = 'a.c #1'
1286 "}));
1287 insta::assert_snapshot!(config.get_table("a")?, @"
1288 a.a = 'a.a.a #0'
1289 a.b = 'a.a.b #1'
1290 a.c = 'a.a.c #1'
1291 b = 'a.b #0'
1292 c = 'a.c #1'
1293 ");
1294 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b", "c"]);
1295 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["a", "b", "c"]);
1296 assert_eq!(config.table_keys("a.b").collect_vec(), vec![""; 0]);
1297 assert_eq!(config.table_keys("a.missing").collect_vec(), vec![""; 0]);
1298 Ok(())
1299 }
1300
1301 #[test]
1302 fn test_stacked_config_get_merged_table_shadowed_top() -> TestResult {
1303 let mut config = StackedConfig::empty();
1304 config.add_layer(new_user_layer(indoc! {"
1305 a.a.a = 'a.a.a #0'
1306 a.b = 'a.b #0'
1307 "}));
1308 config.add_layer(new_user_layer(indoc! {"
1310 a = 'a #1'
1311 "}));
1312 config.add_layer(new_user_layer(indoc! {"
1314 a.a.b = 'a.a.b #2'
1315 "}));
1316 insta::assert_snapshot!(config.get_table("a")?, @"a.b = 'a.a.b #2'");
1317 assert_eq!(config.table_keys("a").collect_vec(), vec!["a"]);
1318 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1319 Ok(())
1320 }
1321
1322 #[test]
1323 fn test_stacked_config_get_merged_table_shadowed_child() -> TestResult {
1324 let mut config = StackedConfig::empty();
1325 config.add_layer(new_user_layer(indoc! {"
1326 a.a.a = 'a.a.a #0'
1327 a.b = 'a.b #0'
1328 "}));
1329 config.add_layer(new_user_layer(indoc! {"
1331 a.a = 'a.a #1'
1332 "}));
1333 config.add_layer(new_user_layer(indoc! {"
1335 a.a.b = 'a.a.b #2'
1336 "}));
1337 insta::assert_snapshot!(config.get_table("a")?, @"
1338 a.b = 'a.a.b #2'
1339 b = 'a.b #0'
1340 ");
1341 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b"]);
1342 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1343 Ok(())
1344 }
1345
1346 #[test]
1347 fn test_stacked_config_get_merged_table_shadowed_parent() -> TestResult {
1348 let mut config = StackedConfig::empty();
1349 config.add_layer(new_user_layer(indoc! {"
1350 a.a.a = 'a.a.a #0'
1351 "}));
1352 config.add_layer(new_user_layer(indoc! {"
1354 a = 'a #1'
1355 "}));
1356 config.add_layer(new_user_layer(indoc! {"
1358 a.a.b = 'a.a.b #2'
1359 "}));
1360 insta::assert_snapshot!(config.get_table("a.a")?, @"b = 'a.a.b #2'");
1362 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1363 Ok(())
1364 }
1365}