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 set_value(
610 &mut self,
611 name: impl ToConfigNamePath,
612 new_value: impl Into<ConfigValue>,
613 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
614 Arc::make_mut(&mut self.layer).set_value(name, new_value)
615 }
616
617 pub fn delete_value(
619 &mut self,
620 name: impl ToConfigNamePath,
621 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
622 Arc::make_mut(&mut self.layer).delete_value(name)
623 }
624}
625
626#[derive(Clone, Debug)]
640pub struct StackedConfig {
641 layers: Vec<Arc<ConfigLayer>>,
643}
644
645impl StackedConfig {
646 pub fn empty() -> Self {
648 Self { layers: vec![] }
649 }
650
651 pub fn with_defaults() -> Self {
654 Self {
655 layers: DEFAULT_CONFIG_LAYERS.to_vec(),
656 }
657 }
658
659 pub fn load_file(
662 &mut self,
663 source: ConfigSource,
664 path: impl Into<PathBuf>,
665 ) -> Result<(), ConfigLoadError> {
666 let layer = ConfigLayer::load_from_file(source, path.into())?;
667 self.add_layer(layer);
668 Ok(())
669 }
670
671 pub fn load_dir(
674 &mut self,
675 source: ConfigSource,
676 path: impl AsRef<Path>,
677 ) -> Result<(), ConfigLoadError> {
678 let layers = ConfigLayer::load_from_dir(source, path.as_ref())?;
679 self.extend_layers(layers);
680 Ok(())
681 }
682
683 pub fn add_layer(&mut self, layer: impl Into<Arc<ConfigLayer>>) {
685 let layer = layer.into();
686 let index = self.insert_point(layer.source);
687 self.layers.insert(index, layer);
688 }
689
690 pub fn extend_layers<I>(&mut self, layers: I)
692 where
693 I: IntoIterator,
694 I::Item: Into<Arc<ConfigLayer>>,
695 {
696 let layers = layers.into_iter().map(Into::into);
697 for (source, chunk) in &layers.chunk_by(|layer| layer.source) {
698 let index = self.insert_point(source);
699 self.layers.splice(index..index, chunk);
700 }
701 }
702
703 pub fn remove_layers(&mut self, source: ConfigSource) {
705 self.layers.drain(self.layer_range(source));
706 }
707
708 fn layer_range(&self, source: ConfigSource) -> Range<usize> {
709 let start = self
711 .layers
712 .iter()
713 .take_while(|layer| layer.source < source)
714 .count();
715 let count = self.layers[start..]
716 .iter()
717 .take_while(|layer| layer.source == source)
718 .count();
719 start..(start + count)
720 }
721
722 fn insert_point(&self, source: ConfigSource) -> usize {
723 let skip = self
726 .layers
727 .iter()
728 .rev()
729 .take_while(|layer| layer.source > source)
730 .count();
731 self.layers.len() - skip
732 }
733
734 pub fn layers(&self) -> &[Arc<ConfigLayer>] {
736 &self.layers
737 }
738
739 pub fn layers_mut(&mut self) -> &mut [Arc<ConfigLayer>] {
741 &mut self.layers
742 }
743
744 pub fn layers_for(&self, source: ConfigSource) -> &[Arc<ConfigLayer>] {
746 &self.layers[self.layer_range(source)]
747 }
748
749 pub fn get<'de, T: Deserialize<'de>>(
752 &self,
753 name: impl ToConfigNamePath,
754 ) -> Result<T, ConfigGetError> {
755 self.get_value_with(name, |value| T::deserialize(value.into_deserializer()))
756 }
757
758 pub fn get_value(&self, name: impl ToConfigNamePath) -> Result<ConfigValue, ConfigGetError> {
760 self.get_value_with::<_, Infallible>(name, Ok)
761 }
762
763 pub fn get_value_with<T, E: Into<Box<dyn std::error::Error + Send + Sync>>>(
766 &self,
767 name: impl ToConfigNamePath,
768 convert: impl FnOnce(ConfigValue) -> Result<T, E>,
769 ) -> Result<T, ConfigGetError> {
770 self.get_item_with(name, |item| {
771 let value = item
775 .into_value()
776 .expect("Item::None should not exist in loaded tables");
777 convert(value)
778 })
779 }
780
781 pub fn get_table(&self, name: impl ToConfigNamePath) -> Result<ConfigTable, ConfigGetError> {
786 self.get_item_with(name, |item| {
787 item.into_table()
788 .map_err(|item| format!("Expected a table, but is {}", item.type_name()))
789 })
790 }
791
792 fn get_item_with<T, E: Into<Box<dyn std::error::Error + Send + Sync>>>(
793 &self,
794 name: impl ToConfigNamePath,
795 convert: impl FnOnce(ConfigItem) -> Result<T, E>,
796 ) -> Result<T, ConfigGetError> {
797 let name = name.into_name_path();
798 let name = name.borrow();
799 let (item, layer_index) =
800 get_merged_item(&self.layers, name).ok_or_else(|| ConfigGetError::NotFound {
801 name: name.to_string(),
802 })?;
803 convert(item).map_err(|err| ConfigGetError::Type {
809 name: name.to_string(),
810 error: err.into(),
811 source_path: self.layers[layer_index].path.clone(),
812 })
813 }
814
815 pub fn table_keys(&self, name: impl ToConfigNamePath) -> impl Iterator<Item = &str> {
818 let name = name.into_name_path();
819 let name = name.borrow();
820 let to_merge = get_tables_to_merge(&self.layers, name);
821 to_merge
822 .into_iter()
823 .rev()
824 .flat_map(|table| table.iter().map(|(k, _)| k))
825 .unique()
826 }
827}
828
829fn get_merged_item(
832 layers: &[Arc<ConfigLayer>],
833 name: &ConfigNamePathBuf,
834) -> Option<(ConfigItem, usize)> {
835 let mut to_merge = Vec::new();
836 for (index, layer) in layers.iter().enumerate().rev() {
837 let item = match layer.look_up_item(name) {
838 Ok(Some(item)) => item,
839 Ok(None) => continue, Err(_) => break, };
842 if item.is_table_like() {
843 to_merge.push((item, index));
844 } else if to_merge.is_empty() {
845 return Some((item.clone(), index)); } else {
847 break; }
849 }
850
851 let (item, mut top_index) = to_merge.pop()?;
855 let mut merged = item.clone();
856 for (item, index) in to_merge.into_iter().rev() {
857 merge_items(&mut merged, item);
858 top_index = index;
859 }
860 Some((merged, top_index))
861}
862
863fn get_tables_to_merge<'a>(
865 layers: &'a [Arc<ConfigLayer>],
866 name: &ConfigNamePathBuf,
867) -> Vec<&'a ConfigTableLike<'a>> {
868 let mut to_merge = Vec::new();
869 for layer in layers.iter().rev() {
870 match layer.look_up_table(name) {
871 Ok(Some(table)) => to_merge.push(table),
872 Ok(None) => {} Err(_) => break, }
875 }
876 to_merge
877}
878
879fn merge_items(lower_item: &mut ConfigItem, upper_item: &ConfigItem) {
881 let (Some(lower_table), Some(upper_table)) =
882 (lower_item.as_table_like_mut(), upper_item.as_table_like())
883 else {
884 *lower_item = upper_item.clone();
886 return;
887 };
888 for (key, upper) in upper_table.iter() {
889 match lower_table.entry(key) {
890 toml_edit::Entry::Occupied(entry) => {
891 merge_items(entry.into_mut(), upper);
892 }
893 toml_edit::Entry::Vacant(entry) => {
894 entry.insert(upper.clone());
895 }
896 };
897 }
898}
899
900static DEFAULT_CONFIG_LAYERS: LazyLock<[Arc<ConfigLayer>; 1]> = LazyLock::new(|| {
901 let parse = |text: &str| Arc::new(ConfigLayer::parse(ConfigSource::Default, text).unwrap());
902 [parse(include_str!("config/misc.toml"))]
903});
904
905#[cfg(test)]
906mod tests {
907 use assert_matches::assert_matches;
908 use indoc::indoc;
909 use pretty_assertions::assert_eq;
910
911 use super::*;
912
913 #[test]
914 fn test_config_layer_set_value() {
915 let mut layer = ConfigLayer::empty(ConfigSource::User);
916 assert_matches!(
918 layer.set_value(ConfigNamePathBuf::root(), 0),
919 Err(ConfigUpdateError::WouldOverwriteValue { name }) if name.is_empty()
920 );
921
922 layer.set_value("foo", 1).unwrap();
924 layer.set_value("bar.baz.blah", "2").unwrap();
925 layer
926 .set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))
927 .unwrap();
928 layer
929 .set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))
930 .unwrap();
931 insta::assert_snapshot!(layer.data, @r#"
932 foo = 1
933
934 [bar]
935 qux = { inline = "table" }
936 to-update = { some = true }
937
938 [bar.baz]
939 blah = "2"
940 "#);
941
942 layer
944 .set_value("foo", ConfigValue::from_iter(["new", "foo"]))
945 .unwrap();
946 layer.set_value("bar.qux", "new bar.qux").unwrap();
948 layer
950 .set_value(
951 "bar.to-update.new",
952 ConfigValue::from_iter([("table", "value")]),
953 )
954 .unwrap();
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 }
976
977 #[test]
978 fn test_config_layer_set_value_formatting() {
979 let mut layer = ConfigLayer::empty(ConfigSource::User);
980 layer
982 .set_value(
983 "'foo' . bar . 'baz'",
984 ConfigValue::from_str("'value'").unwrap(),
985 )
986 .unwrap();
987 insta::assert_snapshot!(layer.data, @r"
988 ['foo' . bar]
989 'baz' = 'value'
990 ");
991
992 layer.set_value("foo.bar.baz", "new value").unwrap();
994 layer.set_value("foo.'bar'.blah", 0).unwrap();
995 insta::assert_snapshot!(layer.data, @r#"
996 ['foo' . bar]
997 'baz' = "new value"
998 blah = 0
999 "#);
1000 }
1001
1002 #[test]
1003 fn test_config_layer_set_value_inline_table() {
1004 let mut layer = ConfigLayer::empty(ConfigSource::User);
1005 layer
1006 .set_value("a", ConfigValue::from_iter([("b", "a.b")]))
1007 .unwrap();
1008 insta::assert_snapshot!(layer.data, @r#"a = { b = "a.b" }"#);
1009
1010 layer.set_value("a.c.d", "a.c.d").unwrap();
1012 insta::assert_snapshot!(layer.data, @r#"a = { b = "a.b", c.d = "a.c.d" }"#);
1013 }
1014
1015 #[test]
1016 fn test_config_layer_delete_value() {
1017 let mut layer = ConfigLayer::empty(ConfigSource::User);
1018 assert_matches!(
1020 layer.delete_value(ConfigNamePathBuf::root()),
1021 Err(ConfigUpdateError::WouldDeleteTable { name }) if name.is_empty()
1022 );
1023
1024 layer.set_value("foo", 1).unwrap();
1026 layer.set_value("bar.baz.blah", "2").unwrap();
1027 layer
1028 .set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))
1029 .unwrap();
1030 layer
1031 .set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))
1032 .unwrap();
1033 insta::assert_snapshot!(layer.data, @r#"
1034 foo = 1
1035
1036 [bar]
1037 qux = { inline = "table" }
1038 to-update = { some = true }
1039
1040 [bar.baz]
1041 blah = "2"
1042 "#);
1043
1044 let old_value = layer.delete_value("foo").unwrap();
1046 assert_eq!(old_value.and_then(|v| v.as_integer()), Some(1));
1047 let old_value = layer.delete_value("bar.qux").unwrap();
1049 assert!(old_value.is_some_and(|v| v.is_inline_table()));
1050 let old_value = layer.delete_value("bar.to-update.some").unwrap();
1052 assert_eq!(old_value.and_then(|v| v.as_bool()), Some(true));
1053 assert_matches!(
1055 layer.delete_value("bar"),
1056 Err(ConfigUpdateError::WouldDeleteTable { name }) if name == "bar"
1057 );
1058 assert_matches!(layer.delete_value("bar.baz.blah.blah"), Ok(None));
1061 insta::assert_snapshot!(layer.data, @r#"
1062 [bar]
1063 to-update = {}
1064
1065 [bar.baz]
1066 blah = "2"
1067 "#);
1068 }
1069
1070 #[test]
1071 fn test_stacked_config_layer_order() {
1072 let empty_data = || DocumentMut::new();
1073 let layer_sources = |config: &StackedConfig| {
1074 config
1075 .layers()
1076 .iter()
1077 .map(|layer| layer.source)
1078 .collect_vec()
1079 };
1080
1081 let mut config = StackedConfig::empty();
1083 config.add_layer(ConfigLayer::with_data(ConfigSource::Repo, empty_data()));
1084 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1085 config.add_layer(ConfigLayer::with_data(ConfigSource::Default, empty_data()));
1086 assert_eq!(
1087 layer_sources(&config),
1088 vec![
1089 ConfigSource::Default,
1090 ConfigSource::User,
1091 ConfigSource::Repo,
1092 ]
1093 );
1094
1095 config.add_layer(ConfigLayer::with_data(
1097 ConfigSource::CommandArg,
1098 empty_data(),
1099 ));
1100 config.add_layer(ConfigLayer::with_data(ConfigSource::EnvBase, empty_data()));
1101 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1102 assert_eq!(
1103 layer_sources(&config),
1104 vec![
1105 ConfigSource::Default,
1106 ConfigSource::EnvBase,
1107 ConfigSource::User,
1108 ConfigSource::User,
1109 ConfigSource::Repo,
1110 ConfigSource::CommandArg,
1111 ]
1112 );
1113
1114 config.remove_layers(ConfigSource::CommandArg);
1116 config.remove_layers(ConfigSource::Default);
1117 config.remove_layers(ConfigSource::User);
1118 assert_eq!(
1119 layer_sources(&config),
1120 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1121 );
1122
1123 config.remove_layers(ConfigSource::Default);
1125 config.remove_layers(ConfigSource::EnvOverrides);
1126 assert_eq!(
1127 layer_sources(&config),
1128 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1129 );
1130
1131 config.extend_layers([
1133 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1134 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1135 ConfigLayer::with_data(ConfigSource::User, empty_data()),
1136 ]);
1137 assert_eq!(
1138 layer_sources(&config),
1139 vec![
1140 ConfigSource::EnvBase,
1141 ConfigSource::User,
1142 ConfigSource::Repo,
1143 ConfigSource::Repo,
1144 ConfigSource::Repo,
1145 ]
1146 );
1147
1148 config.remove_layers(ConfigSource::EnvBase);
1150 config.remove_layers(ConfigSource::User);
1151 config.remove_layers(ConfigSource::Repo);
1152 assert_eq!(layer_sources(&config), vec![]);
1153 }
1154
1155 fn new_user_layer(text: &str) -> ConfigLayer {
1156 ConfigLayer::parse(ConfigSource::User, text).unwrap()
1157 }
1158
1159 #[test]
1160 fn test_stacked_config_get_simple_value() {
1161 let mut config = StackedConfig::empty();
1162 config.add_layer(new_user_layer(indoc! {"
1163 a.b.c = 'a.b.c #0'
1164 "}));
1165 config.add_layer(new_user_layer(indoc! {"
1166 a.d = ['a.d #1']
1167 "}));
1168
1169 assert_eq!(config.get::<String>("a.b.c").unwrap(), "a.b.c #0");
1170
1171 assert_eq!(
1172 config.get::<Vec<String>>("a.d").unwrap(),
1173 vec!["a.d #1".to_owned()]
1174 );
1175
1176 assert_matches!(
1178 config.get::<String>("a.b.missing"),
1179 Err(ConfigGetError::NotFound { name }) if name == "a.b.missing"
1180 );
1181
1182 assert_matches!(
1184 config.get::<String>("a.b.c.d"),
1185 Err(ConfigGetError::NotFound { name }) if name == "a.b.c.d"
1186 );
1187
1188 assert_matches!(
1190 config.get::<String>("a.b"),
1191 Err(ConfigGetError::Type { name, .. }) if name == "a.b"
1192 );
1193 }
1194
1195 #[test]
1196 fn test_stacked_config_get_table_as_value() {
1197 let mut config = StackedConfig::empty();
1198 config.add_layer(new_user_layer(indoc! {"
1199 a.b = { c = 'a.b.c #0' }
1200 "}));
1201 config.add_layer(new_user_layer(indoc! {"
1202 a.d = ['a.d #1']
1203 "}));
1204
1205 insta::assert_snapshot!(
1208 config.get_value("a").unwrap(),
1209 @"{ b = { c = 'a.b.c #0' }, d = ['a.d #1'] }");
1210 }
1211
1212 #[test]
1213 fn test_stacked_config_get_inline_table() {
1214 let mut config = StackedConfig::empty();
1215 config.add_layer(new_user_layer(indoc! {"
1216 a.b = { c = 'a.b.c #0' }
1217 "}));
1218 config.add_layer(new_user_layer(indoc! {"
1219 a.b = { d = 'a.b.d #1' }
1220 "}));
1221
1222 insta::assert_snapshot!(
1224 config.get_value("a.b").unwrap(),
1225 @" { c = 'a.b.c #0' , d = 'a.b.d #1' }");
1226 }
1227
1228 #[test]
1229 fn test_stacked_config_get_inline_non_inline_table() {
1230 let mut config = StackedConfig::empty();
1231 config.add_layer(new_user_layer(indoc! {"
1232 a.b = { c = 'a.b.c #0' }
1233 "}));
1234 config.add_layer(new_user_layer(indoc! {"
1235 a.b.d = 'a.b.d #1'
1236 "}));
1237
1238 insta::assert_snapshot!(
1239 config.get_value("a.b").unwrap(),
1240 @" { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1241 insta::assert_snapshot!(
1242 config.get_table("a").unwrap(),
1243 @"b = { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1244 }
1245
1246 #[test]
1247 fn test_stacked_config_get_value_shadowing_table() {
1248 let mut config = StackedConfig::empty();
1249 config.add_layer(new_user_layer(indoc! {"
1250 a.b.c = 'a.b.c #0'
1251 "}));
1252 config.add_layer(new_user_layer(indoc! {"
1254 a.b = 'a.b #1'
1255 "}));
1256
1257 assert_eq!(config.get::<String>("a.b").unwrap(), "a.b #1");
1258
1259 assert_matches!(
1260 config.get::<String>("a.b.c"),
1261 Err(ConfigGetError::NotFound { name }) if name == "a.b.c"
1262 );
1263 }
1264
1265 #[test]
1266 fn test_stacked_config_get_table_shadowing_table() {
1267 let mut config = StackedConfig::empty();
1268 config.add_layer(new_user_layer(indoc! {"
1269 a.b = 'a.b #0'
1270 "}));
1271 config.add_layer(new_user_layer(indoc! {"
1273 a.b.c = 'a.b.c #1'
1274 "}));
1275 insta::assert_snapshot!(config.get_table("a.b").unwrap(), @"c = 'a.b.c #1'");
1276 }
1277
1278 #[test]
1279 fn test_stacked_config_get_merged_table() {
1280 let mut config = StackedConfig::empty();
1281 config.add_layer(new_user_layer(indoc! {"
1282 a.a.a = 'a.a.a #0'
1283 a.a.b = 'a.a.b #0'
1284 a.b = 'a.b #0'
1285 "}));
1286 config.add_layer(new_user_layer(indoc! {"
1287 a.a.b = 'a.a.b #1'
1288 a.a.c = 'a.a.c #1'
1289 a.c = 'a.c #1'
1290 "}));
1291 insta::assert_snapshot!(config.get_table("a").unwrap(), @r"
1292 a.a = 'a.a.a #0'
1293 a.b = 'a.a.b #1'
1294 a.c = 'a.a.c #1'
1295 b = 'a.b #0'
1296 c = 'a.c #1'
1297 ");
1298 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b", "c"]);
1299 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["a", "b", "c"]);
1300 assert_eq!(config.table_keys("a.b").collect_vec(), vec![""; 0]);
1301 assert_eq!(config.table_keys("a.missing").collect_vec(), vec![""; 0]);
1302 }
1303
1304 #[test]
1305 fn test_stacked_config_get_merged_table_shadowed_top() {
1306 let mut config = StackedConfig::empty();
1307 config.add_layer(new_user_layer(indoc! {"
1308 a.a.a = 'a.a.a #0'
1309 a.b = 'a.b #0'
1310 "}));
1311 config.add_layer(new_user_layer(indoc! {"
1313 a = 'a #1'
1314 "}));
1315 config.add_layer(new_user_layer(indoc! {"
1317 a.a.b = 'a.a.b #2'
1318 "}));
1319 insta::assert_snapshot!(config.get_table("a").unwrap(), @"a.b = 'a.a.b #2'");
1320 assert_eq!(config.table_keys("a").collect_vec(), vec!["a"]);
1321 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1322 }
1323
1324 #[test]
1325 fn test_stacked_config_get_merged_table_shadowed_child() {
1326 let mut config = StackedConfig::empty();
1327 config.add_layer(new_user_layer(indoc! {"
1328 a.a.a = 'a.a.a #0'
1329 a.b = 'a.b #0'
1330 "}));
1331 config.add_layer(new_user_layer(indoc! {"
1333 a.a = 'a.a #1'
1334 "}));
1335 config.add_layer(new_user_layer(indoc! {"
1337 a.a.b = 'a.a.b #2'
1338 "}));
1339 insta::assert_snapshot!(config.get_table("a").unwrap(), @r"
1340 a.b = 'a.a.b #2'
1341 b = 'a.b #0'
1342 ");
1343 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b"]);
1344 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1345 }
1346
1347 #[test]
1348 fn test_stacked_config_get_merged_table_shadowed_parent() {
1349 let mut config = StackedConfig::empty();
1350 config.add_layer(new_user_layer(indoc! {"
1351 a.a.a = 'a.a.a #0'
1352 "}));
1353 config.add_layer(new_user_layer(indoc! {"
1355 a = 'a #1'
1356 "}));
1357 config.add_layer(new_user_layer(indoc! {"
1359 a.a.b = 'a.a.b #2'
1360 "}));
1361 insta::assert_snapshot!(config.get_table("a.a").unwrap(), @"b = 'a.a.b #2'");
1363 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1364 }
1365}