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;
29
30use itertools::Itertools as _;
31use once_cell::sync::Lazy;
32use serde::de::IntoDeserializer as _;
33use serde::Deserialize;
34use thiserror::Error;
35use toml_edit::DocumentMut;
36use toml_edit::ImDocument;
37
38pub use crate::config_resolver::migrate;
39pub use crate::config_resolver::resolve;
40pub use crate::config_resolver::ConfigMigrateError;
41pub use crate::config_resolver::ConfigMigrateLayerError;
42pub use crate::config_resolver::ConfigMigrationRule;
43pub use crate::config_resolver::ConfigResolutionContext;
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 ConfigNamePathBuf(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<&ConfigNamePathBuf> for ConfigNamePathBuf {
176 fn from(value: &ConfigNamePathBuf) -> 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 ConfigNamePathBuf(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 EnvOverrides,
293 CommandArg,
295}
296
297impl Display for ConfigSource {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 use ConfigSource::*;
300 let c = match self {
301 Default => "default",
302 User => "user",
303 Repo => "repo",
304 CommandArg => "cli",
305 EnvBase | EnvOverrides => "env",
306 };
307 write!(f, "{c}")
308 }
309}
310
311#[derive(Clone, Debug)]
313pub struct ConfigLayer {
314 pub source: ConfigSource,
316 pub path: Option<PathBuf>,
318 pub data: DocumentMut,
320}
321
322impl ConfigLayer {
323 pub fn empty(source: ConfigSource) -> Self {
325 Self::with_data(source, DocumentMut::new())
326 }
327
328 pub fn with_data(source: ConfigSource, data: DocumentMut) -> Self {
330 ConfigLayer {
331 source,
332 path: None,
333 data,
334 }
335 }
336
337 pub fn parse(source: ConfigSource, text: &str) -> Result<Self, ConfigLoadError> {
339 let data = ImDocument::parse(text).map_err(|error| ConfigLoadError::Parse {
340 error: Box::new(error),
341 source_path: None,
342 })?;
343 Ok(Self::with_data(source, data.into_mut()))
344 }
345
346 pub fn load_from_file(source: ConfigSource, path: PathBuf) -> Result<Self, ConfigLoadError> {
348 let text = fs::read_to_string(&path)
349 .context(&path)
350 .map_err(ConfigLoadError::Read)?;
351 let data = ImDocument::parse(text).map_err(|error| ConfigLoadError::Parse {
352 error: Box::new(error),
353 source_path: Some(path.clone()),
354 })?;
355 Ok(ConfigLayer {
356 source,
357 path: Some(path),
358 data: data.into_mut(),
359 })
360 }
361
362 fn load_from_dir(source: ConfigSource, path: &Path) -> Result<Vec<Self>, ConfigLoadError> {
363 let mut file_paths: Vec<_> = path
365 .read_dir()
366 .and_then(|dir_entries| {
367 dir_entries
368 .map(|entry| Ok(entry?.path()))
369 .filter_ok(|path| path.is_file() && path.extension() == Some("toml".as_ref()))
370 .try_collect()
371 })
372 .context(path)
373 .map_err(ConfigLoadError::Read)?;
374 file_paths.sort_unstable();
375 file_paths
376 .into_iter()
377 .map(|path| Self::load_from_file(source, path))
378 .try_collect()
379 }
380
381 pub fn is_empty(&self) -> bool {
383 self.data.is_empty()
384 }
385
386 pub fn look_up_table(
392 &self,
393 name: impl ToConfigNamePath,
394 ) -> Result<Option<&ConfigTableLike<'_>>, &ConfigItem> {
395 match self.look_up_item(name) {
396 Ok(Some(item)) => match item.as_table_like() {
397 Some(table) => Ok(Some(table)),
398 None => Err(item),
399 },
400 Ok(None) => Ok(None),
401 Err(item) => Err(item),
402 }
403 }
404
405 pub fn look_up_item(
408 &self,
409 name: impl ToConfigNamePath,
410 ) -> Result<Option<&ConfigItem>, &ConfigItem> {
411 look_up_item(self.data.as_item(), name.into_name_path().borrow())
412 }
413
414 pub fn set_value(
420 &mut self,
421 name: impl ToConfigNamePath,
422 new_value: impl Into<ConfigValue>,
423 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
424 let would_overwrite_table = |name| ConfigUpdateError::WouldOverwriteValue { name };
425 let name = name.into_name_path();
426 let name = name.borrow();
427 let (leaf_key, table_keys) = name
428 .0
429 .split_last()
430 .ok_or_else(|| would_overwrite_table(name.to_string()))?;
431 let parent_table = ensure_table(self.data.as_table_mut(), table_keys)
432 .map_err(|keys| would_overwrite_table(keys.join(".")))?;
433 match parent_table.entry_format(leaf_key) {
434 toml_edit::Entry::Occupied(mut entry) => {
435 if !entry.get().is_value() {
436 return Err(ConfigUpdateError::WouldOverwriteTable {
437 name: name.to_string(),
438 });
439 }
440 let old_item = entry.insert(toml_edit::value(new_value));
441 Ok(Some(old_item.into_value().unwrap()))
442 }
443 toml_edit::Entry::Vacant(entry) => {
444 entry.insert(toml_edit::value(new_value));
445 let mut new_key = parent_table.key_mut(leaf_key).unwrap();
447 new_key.leaf_decor_mut().clear();
448 Ok(None)
449 }
450 }
451 }
452
453 pub fn delete_value(
459 &mut self,
460 name: impl ToConfigNamePath,
461 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
462 let would_delete_table = |name| ConfigUpdateError::WouldDeleteTable { name };
463 let name = name.into_name_path();
464 let name = name.borrow();
465 let mut keys = name.components();
466 let leaf_key = keys
467 .next_back()
468 .ok_or_else(|| would_delete_table(name.to_string()))?;
469 let Some(parent_table) = keys.try_fold(
470 self.data.as_table_mut() as &mut ConfigTableLike,
471 |table, key| table.get_mut(key)?.as_table_like_mut(),
472 ) else {
473 return Ok(None);
474 };
475 match parent_table.entry(leaf_key) {
476 toml_edit::Entry::Occupied(entry) => {
477 if !entry.get().is_value() {
478 return Err(would_delete_table(name.to_string()));
479 }
480 let old_item = entry.remove();
481 Ok(Some(old_item.into_value().unwrap()))
482 }
483 toml_edit::Entry::Vacant(_) => Ok(None),
484 }
485 }
486
487 pub fn ensure_table(
493 &mut self,
494 name: impl ToConfigNamePath,
495 ) -> Result<&mut ConfigTableLike<'_>, ConfigUpdateError> {
496 let would_overwrite_table = |name| ConfigUpdateError::WouldOverwriteValue { name };
497 let name = name.into_name_path();
498 let name = name.borrow();
499 ensure_table(self.data.as_table_mut(), &name.0)
500 .map_err(|keys| would_overwrite_table(keys.join(".")))
501 }
502}
503
504fn look_up_item<'a>(
507 root_item: &'a ConfigItem,
508 name: &ConfigNamePathBuf,
509) -> Result<Option<&'a ConfigItem>, &'a ConfigItem> {
510 let mut cur_item = root_item;
511 for key in name.components() {
512 let Some(table) = cur_item.as_table_like() else {
513 return Err(cur_item);
514 };
515 cur_item = match table.get(key) {
516 Some(item) => item,
517 None => return Ok(None),
518 };
519 }
520 Ok(Some(cur_item))
521}
522
523fn ensure_table<'a, 'b>(
526 root_table: &'a mut ConfigTableLike<'a>,
527 keys: &'b [toml_edit::Key],
528) -> Result<&'a mut ConfigTableLike<'a>, &'b [toml_edit::Key]> {
529 keys.iter()
530 .enumerate()
531 .try_fold(root_table, |table, (i, key)| {
532 let sub_item = table.entry_format(key).or_insert_with(new_implicit_table);
533 sub_item.as_table_like_mut().ok_or(&keys[..=i])
534 })
535}
536
537fn new_implicit_table() -> ConfigItem {
538 let mut table = ConfigTable::new();
539 table.set_implicit(true);
540 ConfigItem::Table(table)
541}
542
543#[derive(Clone, Debug)]
546pub struct ConfigFile {
547 layer: Arc<ConfigLayer>,
548}
549
550impl ConfigFile {
551 pub fn load_or_empty(
554 source: ConfigSource,
555 path: impl Into<PathBuf>,
556 ) -> Result<Self, ConfigLoadError> {
557 let layer = match ConfigLayer::load_from_file(source, path.into()) {
558 Ok(layer) => Arc::new(layer),
559 Err(ConfigLoadError::Read(PathError { path, error }))
560 if error.kind() == io::ErrorKind::NotFound =>
561 {
562 let mut data = DocumentMut::new();
563 data.insert(
564 "$schema",
565 toml_edit::Item::Value(
566 "https://jj-vcs.github.io/jj/latest/config-schema.json".into(),
567 ),
568 );
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(ConfigFile { layer })
579 }
580
581 pub fn from_layer(layer: Arc<ConfigLayer>) -> Result<Self, Arc<ConfigLayer>> {
584 if layer.path.is_some() {
585 Ok(ConfigFile { 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 StackedConfig { layers: vec![] }
649 }
650
651 pub fn with_defaults() -> Self {
654 StackedConfig {
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: Lazy<[Arc<ConfigLayer>; 1]> = Lazy::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_delete_value() {
1004 let mut layer = ConfigLayer::empty(ConfigSource::User);
1005 assert_matches!(
1007 layer.delete_value(ConfigNamePathBuf::root()),
1008 Err(ConfigUpdateError::WouldDeleteTable { name }) if name.is_empty()
1009 );
1010
1011 layer.set_value("foo", 1).unwrap();
1013 layer.set_value("bar.baz.blah", "2").unwrap();
1014 layer
1015 .set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))
1016 .unwrap();
1017 layer
1018 .set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))
1019 .unwrap();
1020 insta::assert_snapshot!(layer.data, @r#"
1021 foo = 1
1022
1023 [bar]
1024 qux = { inline = "table" }
1025 to-update = { some = true }
1026
1027 [bar.baz]
1028 blah = "2"
1029 "#);
1030
1031 let old_value = layer.delete_value("foo").unwrap();
1033 assert_eq!(old_value.and_then(|v| v.as_integer()), Some(1));
1034 let old_value = layer.delete_value("bar.qux").unwrap();
1036 assert!(old_value.is_some_and(|v| v.is_inline_table()));
1037 let old_value = layer.delete_value("bar.to-update.some").unwrap();
1039 assert_eq!(old_value.and_then(|v| v.as_bool()), Some(true));
1040 assert_matches!(
1042 layer.delete_value("bar"),
1043 Err(ConfigUpdateError::WouldDeleteTable { name }) if name == "bar"
1044 );
1045 assert_matches!(layer.delete_value("bar.baz.blah.blah"), Ok(None));
1048 insta::assert_snapshot!(layer.data, @r#"
1049 [bar]
1050 to-update = {}
1051
1052 [bar.baz]
1053 blah = "2"
1054 "#);
1055 }
1056
1057 #[test]
1058 fn test_stacked_config_layer_order() {
1059 let empty_data = || DocumentMut::new();
1060 let layer_sources = |config: &StackedConfig| {
1061 config
1062 .layers()
1063 .iter()
1064 .map(|layer| layer.source)
1065 .collect_vec()
1066 };
1067
1068 let mut config = StackedConfig::empty();
1070 config.add_layer(ConfigLayer::with_data(ConfigSource::Repo, empty_data()));
1071 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1072 config.add_layer(ConfigLayer::with_data(ConfigSource::Default, empty_data()));
1073 assert_eq!(
1074 layer_sources(&config),
1075 vec![
1076 ConfigSource::Default,
1077 ConfigSource::User,
1078 ConfigSource::Repo,
1079 ]
1080 );
1081
1082 config.add_layer(ConfigLayer::with_data(
1084 ConfigSource::CommandArg,
1085 empty_data(),
1086 ));
1087 config.add_layer(ConfigLayer::with_data(ConfigSource::EnvBase, empty_data()));
1088 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1089 assert_eq!(
1090 layer_sources(&config),
1091 vec![
1092 ConfigSource::Default,
1093 ConfigSource::EnvBase,
1094 ConfigSource::User,
1095 ConfigSource::User,
1096 ConfigSource::Repo,
1097 ConfigSource::CommandArg,
1098 ]
1099 );
1100
1101 config.remove_layers(ConfigSource::CommandArg);
1103 config.remove_layers(ConfigSource::Default);
1104 config.remove_layers(ConfigSource::User);
1105 assert_eq!(
1106 layer_sources(&config),
1107 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1108 );
1109
1110 config.remove_layers(ConfigSource::Default);
1112 config.remove_layers(ConfigSource::EnvOverrides);
1113 assert_eq!(
1114 layer_sources(&config),
1115 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1116 );
1117
1118 config.extend_layers([
1120 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1121 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1122 ConfigLayer::with_data(ConfigSource::User, empty_data()),
1123 ]);
1124 assert_eq!(
1125 layer_sources(&config),
1126 vec![
1127 ConfigSource::EnvBase,
1128 ConfigSource::User,
1129 ConfigSource::Repo,
1130 ConfigSource::Repo,
1131 ConfigSource::Repo,
1132 ]
1133 );
1134
1135 config.remove_layers(ConfigSource::EnvBase);
1137 config.remove_layers(ConfigSource::User);
1138 config.remove_layers(ConfigSource::Repo);
1139 assert_eq!(layer_sources(&config), vec![]);
1140 }
1141
1142 fn new_user_layer(text: &str) -> ConfigLayer {
1143 ConfigLayer::parse(ConfigSource::User, text).unwrap()
1144 }
1145
1146 #[test]
1147 fn test_stacked_config_get_simple_value() {
1148 let mut config = StackedConfig::empty();
1149 config.add_layer(new_user_layer(indoc! {"
1150 a.b.c = 'a.b.c #0'
1151 "}));
1152 config.add_layer(new_user_layer(indoc! {"
1153 a.d = ['a.d #1']
1154 "}));
1155
1156 assert_eq!(config.get::<String>("a.b.c").unwrap(), "a.b.c #0");
1157
1158 assert_eq!(
1159 config.get::<Vec<String>>("a.d").unwrap(),
1160 vec!["a.d #1".to_owned()]
1161 );
1162
1163 assert_matches!(
1165 config.get::<String>("a.b.missing"),
1166 Err(ConfigGetError::NotFound { name }) if name == "a.b.missing"
1167 );
1168
1169 assert_matches!(
1171 config.get::<String>("a.b.c.d"),
1172 Err(ConfigGetError::NotFound { name }) if name == "a.b.c.d"
1173 );
1174
1175 assert_matches!(
1177 config.get::<String>("a.b"),
1178 Err(ConfigGetError::Type { name, .. }) if name == "a.b"
1179 );
1180 }
1181
1182 #[test]
1183 fn test_stacked_config_get_table_as_value() {
1184 let mut config = StackedConfig::empty();
1185 config.add_layer(new_user_layer(indoc! {"
1186 a.b = { c = 'a.b.c #0' }
1187 "}));
1188 config.add_layer(new_user_layer(indoc! {"
1189 a.d = ['a.d #1']
1190 "}));
1191
1192 insta::assert_snapshot!(
1195 config.get_value("a").unwrap(),
1196 @"{ b = { c = 'a.b.c #0' }, d = ['a.d #1'] }");
1197 }
1198
1199 #[test]
1200 fn test_stacked_config_get_inline_table() {
1201 let mut config = StackedConfig::empty();
1202 config.add_layer(new_user_layer(indoc! {"
1203 a.b = { c = 'a.b.c #0' }
1204 "}));
1205 config.add_layer(new_user_layer(indoc! {"
1206 a.b = { d = 'a.b.d #1' }
1207 "}));
1208
1209 insta::assert_snapshot!(
1211 config.get_value("a.b").unwrap(),
1212 @" { c = 'a.b.c #0' , d = 'a.b.d #1' }");
1213 }
1214
1215 #[test]
1216 fn test_stacked_config_get_inline_non_inline_table() {
1217 let mut config = StackedConfig::empty();
1218 config.add_layer(new_user_layer(indoc! {"
1219 a.b = { c = 'a.b.c #0' }
1220 "}));
1221 config.add_layer(new_user_layer(indoc! {"
1222 a.b.d = 'a.b.d #1'
1223 "}));
1224
1225 insta::assert_snapshot!(
1226 config.get_value("a.b").unwrap(),
1227 @" { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1228 insta::assert_snapshot!(
1229 config.get_table("a").unwrap(),
1230 @"b = { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1231 }
1232
1233 #[test]
1234 fn test_stacked_config_get_value_shadowing_table() {
1235 let mut config = StackedConfig::empty();
1236 config.add_layer(new_user_layer(indoc! {"
1237 a.b.c = 'a.b.c #0'
1238 "}));
1239 config.add_layer(new_user_layer(indoc! {"
1241 a.b = 'a.b #1'
1242 "}));
1243
1244 assert_eq!(config.get::<String>("a.b").unwrap(), "a.b #1");
1245
1246 assert_matches!(
1247 config.get::<String>("a.b.c"),
1248 Err(ConfigGetError::NotFound { name }) if name == "a.b.c"
1249 );
1250 }
1251
1252 #[test]
1253 fn test_stacked_config_get_table_shadowing_table() {
1254 let mut config = StackedConfig::empty();
1255 config.add_layer(new_user_layer(indoc! {"
1256 a.b = 'a.b #0'
1257 "}));
1258 config.add_layer(new_user_layer(indoc! {"
1260 a.b.c = 'a.b.c #1'
1261 "}));
1262 insta::assert_snapshot!(config.get_table("a.b").unwrap(), @"c = 'a.b.c #1'");
1263 }
1264
1265 #[test]
1266 fn test_stacked_config_get_merged_table() {
1267 let mut config = StackedConfig::empty();
1268 config.add_layer(new_user_layer(indoc! {"
1269 a.a.a = 'a.a.a #0'
1270 a.a.b = 'a.a.b #0'
1271 a.b = 'a.b #0'
1272 "}));
1273 config.add_layer(new_user_layer(indoc! {"
1274 a.a.b = 'a.a.b #1'
1275 a.a.c = 'a.a.c #1'
1276 a.c = 'a.c #1'
1277 "}));
1278 insta::assert_snapshot!(config.get_table("a").unwrap(), @r"
1279 a.a = 'a.a.a #0'
1280 a.b = 'a.a.b #1'
1281 a.c = 'a.a.c #1'
1282 b = 'a.b #0'
1283 c = 'a.c #1'
1284 ");
1285 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b", "c"]);
1286 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["a", "b", "c"]);
1287 assert_eq!(config.table_keys("a.b").collect_vec(), vec![""; 0]);
1288 assert_eq!(config.table_keys("a.missing").collect_vec(), vec![""; 0]);
1289 }
1290
1291 #[test]
1292 fn test_stacked_config_get_merged_table_shadowed_top() {
1293 let mut config = StackedConfig::empty();
1294 config.add_layer(new_user_layer(indoc! {"
1295 a.a.a = 'a.a.a #0'
1296 a.b = 'a.b #0'
1297 "}));
1298 config.add_layer(new_user_layer(indoc! {"
1300 a = 'a #1'
1301 "}));
1302 config.add_layer(new_user_layer(indoc! {"
1304 a.a.b = 'a.a.b #2'
1305 "}));
1306 insta::assert_snapshot!(config.get_table("a").unwrap(), @"a.b = 'a.a.b #2'");
1307 assert_eq!(config.table_keys("a").collect_vec(), vec!["a"]);
1308 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1309 }
1310
1311 #[test]
1312 fn test_stacked_config_get_merged_table_shadowed_child() {
1313 let mut config = StackedConfig::empty();
1314 config.add_layer(new_user_layer(indoc! {"
1315 a.a.a = 'a.a.a #0'
1316 a.b = 'a.b #0'
1317 "}));
1318 config.add_layer(new_user_layer(indoc! {"
1320 a.a = '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").unwrap(), @r"
1327 a.b = 'a.a.b #2'
1328 b = 'a.b #0'
1329 ");
1330 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b"]);
1331 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1332 }
1333
1334 #[test]
1335 fn test_stacked_config_get_merged_table_shadowed_parent() {
1336 let mut config = StackedConfig::empty();
1337 config.add_layer(new_user_layer(indoc! {"
1338 a.a.a = 'a.a.a #0'
1339 "}));
1340 config.add_layer(new_user_layer(indoc! {"
1342 a = 'a #1'
1343 "}));
1344 config.add_layer(new_user_layer(indoc! {"
1346 a.a.b = 'a.a.b #2'
1347 "}));
1348 insta::assert_snapshot!(config.get_table("a.a").unwrap(), @"b = 'a.a.b #2'");
1350 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1351 }
1352}