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 use crate::tests::TestResult;
913
914 #[test]
915 fn test_config_layer_set_value() -> TestResult {
916 let mut layer = ConfigLayer::empty(ConfigSource::User);
917 assert_matches!(
919 layer.set_value(ConfigNamePathBuf::root(), 0),
920 Err(ConfigUpdateError::WouldOverwriteValue { name }) if name.is_empty()
921 );
922
923 layer.set_value("foo", 1)?;
925 layer.set_value("bar.baz.blah", "2")?;
926 layer.set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))?;
927 layer.set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))?;
928 insta::assert_snapshot!(layer.data, @r#"
929 foo = 1
930
931 [bar]
932 qux = { inline = "table" }
933 to-update = { some = true }
934
935 [bar.baz]
936 blah = "2"
937 "#);
938
939 layer.set_value("foo", ConfigValue::from_iter(["new", "foo"]))?;
941 layer.set_value("bar.qux", "new bar.qux")?;
943 layer.set_value(
945 "bar.to-update.new",
946 ConfigValue::from_iter([("table", "value")]),
947 )?;
948 assert_matches!(
950 layer.set_value("bar", 0),
951 Err(ConfigUpdateError::WouldOverwriteTable { name }) if name == "bar"
952 );
953 assert_matches!(
955 layer.set_value("bar.baz.blah.blah", 0),
956 Err(ConfigUpdateError::WouldOverwriteValue { name }) if name == "bar.baz.blah"
957 );
958 insta::assert_snapshot!(layer.data, @r#"
959 foo = ["new", "foo"]
960
961 [bar]
962 qux = "new bar.qux"
963 to-update = { some = true, new = { table = "value" } }
964
965 [bar.baz]
966 blah = "2"
967 "#);
968 Ok(())
969 }
970
971 #[test]
972 fn test_config_layer_set_value_formatting() -> TestResult {
973 let mut layer = ConfigLayer::empty(ConfigSource::User);
974 layer.set_value("'foo' . bar . 'baz'", ConfigValue::from_str("'value'")?)?;
976 insta::assert_snapshot!(layer.data, @"
977 ['foo' . bar]
978 'baz' = 'value'
979 ");
980
981 layer.set_value("foo.bar.baz", "new value")?;
983 layer.set_value("foo.'bar'.blah", 0)?;
984 insta::assert_snapshot!(layer.data, @r#"
985 ['foo' . bar]
986 'baz' = "new value"
987 blah = 0
988 "#);
989 Ok(())
990 }
991
992 #[test]
993 fn test_config_layer_set_value_inline_table() -> TestResult {
994 let mut layer = ConfigLayer::empty(ConfigSource::User);
995 layer.set_value("a", ConfigValue::from_iter([("b", "a.b")]))?;
996 insta::assert_snapshot!(layer.data, @r#"a = { b = "a.b" }"#);
997
998 layer.set_value("a.c.d", "a.c.d")?;
1000 insta::assert_snapshot!(layer.data, @r#"a = { b = "a.b", c.d = "a.c.d" }"#);
1001 Ok(())
1002 }
1003
1004 #[test]
1005 fn test_config_layer_delete_value() -> TestResult {
1006 let mut layer = ConfigLayer::empty(ConfigSource::User);
1007 assert_matches!(
1009 layer.delete_value(ConfigNamePathBuf::root()),
1010 Err(ConfigUpdateError::WouldDeleteTable { name }) if name.is_empty()
1011 );
1012
1013 layer.set_value("foo", 1)?;
1015 layer.set_value("bar.baz.blah", "2")?;
1016 layer.set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))?;
1017 layer.set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))?;
1018 insta::assert_snapshot!(layer.data, @r#"
1019 foo = 1
1020
1021 [bar]
1022 qux = { inline = "table" }
1023 to-update = { some = true }
1024
1025 [bar.baz]
1026 blah = "2"
1027 "#);
1028
1029 let old_value = layer.delete_value("foo")?;
1031 assert_eq!(old_value.and_then(|v| v.as_integer()), Some(1));
1032 let old_value = layer.delete_value("bar.qux")?;
1034 assert!(old_value.is_some_and(|v| v.is_inline_table()));
1035 let old_value = layer.delete_value("bar.to-update.some")?;
1037 assert_eq!(old_value.and_then(|v| v.as_bool()), Some(true));
1038 assert_matches!(
1040 layer.delete_value("bar"),
1041 Err(ConfigUpdateError::WouldDeleteTable { name }) if name == "bar"
1042 );
1043 assert_matches!(layer.delete_value("bar.baz.blah.blah"), Ok(None));
1046 insta::assert_snapshot!(layer.data, @r#"
1047 [bar]
1048 to-update = {}
1049
1050 [bar.baz]
1051 blah = "2"
1052 "#);
1053 Ok(())
1054 }
1055
1056 #[test]
1057 fn test_stacked_config_layer_order() {
1058 let empty_data = || DocumentMut::new();
1059 let layer_sources = |config: &StackedConfig| {
1060 config
1061 .layers()
1062 .iter()
1063 .map(|layer| layer.source)
1064 .collect_vec()
1065 };
1066
1067 let mut config = StackedConfig::empty();
1069 config.add_layer(ConfigLayer::with_data(ConfigSource::Repo, empty_data()));
1070 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1071 config.add_layer(ConfigLayer::with_data(ConfigSource::Default, empty_data()));
1072 assert_eq!(
1073 layer_sources(&config),
1074 vec![
1075 ConfigSource::Default,
1076 ConfigSource::User,
1077 ConfigSource::Repo,
1078 ]
1079 );
1080
1081 config.add_layer(ConfigLayer::with_data(
1083 ConfigSource::CommandArg,
1084 empty_data(),
1085 ));
1086 config.add_layer(ConfigLayer::with_data(ConfigSource::EnvBase, empty_data()));
1087 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1088 assert_eq!(
1089 layer_sources(&config),
1090 vec![
1091 ConfigSource::Default,
1092 ConfigSource::EnvBase,
1093 ConfigSource::User,
1094 ConfigSource::User,
1095 ConfigSource::Repo,
1096 ConfigSource::CommandArg,
1097 ]
1098 );
1099
1100 config.remove_layers(ConfigSource::CommandArg);
1102 config.remove_layers(ConfigSource::Default);
1103 config.remove_layers(ConfigSource::User);
1104 assert_eq!(
1105 layer_sources(&config),
1106 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1107 );
1108
1109 config.remove_layers(ConfigSource::Default);
1111 config.remove_layers(ConfigSource::EnvOverrides);
1112 assert_eq!(
1113 layer_sources(&config),
1114 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1115 );
1116
1117 config.extend_layers([
1119 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1120 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1121 ConfigLayer::with_data(ConfigSource::User, empty_data()),
1122 ]);
1123 assert_eq!(
1124 layer_sources(&config),
1125 vec![
1126 ConfigSource::EnvBase,
1127 ConfigSource::User,
1128 ConfigSource::Repo,
1129 ConfigSource::Repo,
1130 ConfigSource::Repo,
1131 ]
1132 );
1133
1134 config.remove_layers(ConfigSource::EnvBase);
1136 config.remove_layers(ConfigSource::User);
1137 config.remove_layers(ConfigSource::Repo);
1138 assert_eq!(layer_sources(&config), vec![]);
1139 }
1140
1141 fn new_user_layer(text: &str) -> ConfigLayer {
1142 ConfigLayer::parse(ConfigSource::User, text).unwrap()
1143 }
1144
1145 #[test]
1146 fn test_stacked_config_get_simple_value() -> TestResult {
1147 let mut config = StackedConfig::empty();
1148 config.add_layer(new_user_layer(indoc! {"
1149 a.b.c = 'a.b.c #0'
1150 "}));
1151 config.add_layer(new_user_layer(indoc! {"
1152 a.d = ['a.d #1']
1153 "}));
1154
1155 assert_eq!(config.get::<String>("a.b.c")?, "a.b.c #0");
1156
1157 assert_eq!(config.get::<Vec<String>>("a.d")?, vec!["a.d #1".to_owned()]);
1158
1159 assert_matches!(
1161 config.get::<String>("a.b.missing"),
1162 Err(ConfigGetError::NotFound { name }) if name == "a.b.missing"
1163 );
1164
1165 assert_matches!(
1167 config.get::<String>("a.b.c.d"),
1168 Err(ConfigGetError::NotFound { name }) if name == "a.b.c.d"
1169 );
1170
1171 assert_matches!(
1173 config.get::<String>("a.b"),
1174 Err(ConfigGetError::Type { name, .. }) if name == "a.b"
1175 );
1176 Ok(())
1177 }
1178
1179 #[test]
1180 fn test_stacked_config_get_table_as_value() -> TestResult {
1181 let mut config = StackedConfig::empty();
1182 config.add_layer(new_user_layer(indoc! {"
1183 a.b = { c = 'a.b.c #0' }
1184 "}));
1185 config.add_layer(new_user_layer(indoc! {"
1186 a.d = ['a.d #1']
1187 "}));
1188
1189 insta::assert_snapshot!(
1192 config.get_value("a")?,
1193 @"{ b = { c = 'a.b.c #0' }, d = ['a.d #1'] }");
1194 Ok(())
1195 }
1196
1197 #[test]
1198 fn test_stacked_config_get_inline_table() -> TestResult {
1199 let mut config = StackedConfig::empty();
1200 config.add_layer(new_user_layer(indoc! {"
1201 a.b = { c = 'a.b.c #0' }
1202 "}));
1203 config.add_layer(new_user_layer(indoc! {"
1204 a.b = { d = 'a.b.d #1' }
1205 "}));
1206
1207 insta::assert_snapshot!(
1209 config.get_value("a.b")?,
1210 @" { c = 'a.b.c #0' , d = 'a.b.d #1' }");
1211 Ok(())
1212 }
1213
1214 #[test]
1215 fn test_stacked_config_get_inline_non_inline_table() -> TestResult {
1216 let mut config = StackedConfig::empty();
1217 config.add_layer(new_user_layer(indoc! {"
1218 a.b = { c = 'a.b.c #0' }
1219 "}));
1220 config.add_layer(new_user_layer(indoc! {"
1221 a.b.d = 'a.b.d #1'
1222 "}));
1223
1224 insta::assert_snapshot!(
1225 config.get_value("a.b")?,
1226 @" { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1227 insta::assert_snapshot!(
1228 config.get_table("a")?,
1229 @"b = { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1230 Ok(())
1231 }
1232
1233 #[test]
1234 fn test_stacked_config_get_value_shadowing_table() -> TestResult {
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")?, "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 Ok(())
1251 }
1252
1253 #[test]
1254 fn test_stacked_config_get_table_shadowing_table() -> TestResult {
1255 let mut config = StackedConfig::empty();
1256 config.add_layer(new_user_layer(indoc! {"
1257 a.b = 'a.b #0'
1258 "}));
1259 config.add_layer(new_user_layer(indoc! {"
1261 a.b.c = 'a.b.c #1'
1262 "}));
1263 insta::assert_snapshot!(config.get_table("a.b")?, @"c = 'a.b.c #1'");
1264 Ok(())
1265 }
1266
1267 #[test]
1268 fn test_stacked_config_get_merged_table() -> TestResult {
1269 let mut config = StackedConfig::empty();
1270 config.add_layer(new_user_layer(indoc! {"
1271 a.a.a = 'a.a.a #0'
1272 a.a.b = 'a.a.b #0'
1273 a.b = 'a.b #0'
1274 "}));
1275 config.add_layer(new_user_layer(indoc! {"
1276 a.a.b = 'a.a.b #1'
1277 a.a.c = 'a.a.c #1'
1278 a.c = 'a.c #1'
1279 "}));
1280 insta::assert_snapshot!(config.get_table("a")?, @"
1281 a.a = 'a.a.a #0'
1282 a.b = 'a.a.b #1'
1283 a.c = 'a.a.c #1'
1284 b = 'a.b #0'
1285 c = 'a.c #1'
1286 ");
1287 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b", "c"]);
1288 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["a", "b", "c"]);
1289 assert_eq!(config.table_keys("a.b").collect_vec(), vec![""; 0]);
1290 assert_eq!(config.table_keys("a.missing").collect_vec(), vec![""; 0]);
1291 Ok(())
1292 }
1293
1294 #[test]
1295 fn test_stacked_config_get_merged_table_shadowed_top() -> TestResult {
1296 let mut config = StackedConfig::empty();
1297 config.add_layer(new_user_layer(indoc! {"
1298 a.a.a = 'a.a.a #0'
1299 a.b = 'a.b #0'
1300 "}));
1301 config.add_layer(new_user_layer(indoc! {"
1303 a = 'a #1'
1304 "}));
1305 config.add_layer(new_user_layer(indoc! {"
1307 a.a.b = 'a.a.b #2'
1308 "}));
1309 insta::assert_snapshot!(config.get_table("a")?, @"a.b = 'a.a.b #2'");
1310 assert_eq!(config.table_keys("a").collect_vec(), vec!["a"]);
1311 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1312 Ok(())
1313 }
1314
1315 #[test]
1316 fn test_stacked_config_get_merged_table_shadowed_child() -> TestResult {
1317 let mut config = StackedConfig::empty();
1318 config.add_layer(new_user_layer(indoc! {"
1319 a.a.a = 'a.a.a #0'
1320 a.b = 'a.b #0'
1321 "}));
1322 config.add_layer(new_user_layer(indoc! {"
1324 a.a = 'a.a #1'
1325 "}));
1326 config.add_layer(new_user_layer(indoc! {"
1328 a.a.b = 'a.a.b #2'
1329 "}));
1330 insta::assert_snapshot!(config.get_table("a")?, @"
1331 a.b = 'a.a.b #2'
1332 b = 'a.b #0'
1333 ");
1334 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b"]);
1335 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1336 Ok(())
1337 }
1338
1339 #[test]
1340 fn test_stacked_config_get_merged_table_shadowed_parent() -> TestResult {
1341 let mut config = StackedConfig::empty();
1342 config.add_layer(new_user_layer(indoc! {"
1343 a.a.a = 'a.a.a #0'
1344 "}));
1345 config.add_layer(new_user_layer(indoc! {"
1347 a = 'a #1'
1348 "}));
1349 config.add_layer(new_user_layer(indoc! {"
1351 a.a.b = 'a.a.b #2'
1352 "}));
1353 insta::assert_snapshot!(config.get_table("a.a")?, @"b = 'a.a.b #2'");
1355 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1356 Ok(())
1357 }
1358}