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().set_prefix(
568 "#:schema https://jj-vcs.github.io/jj/latest/config-schema.json\n\n",
569 );
570 let layer = ConfigLayer {
571 source,
572 path: Some(path),
573 data,
574 };
575 Arc::new(layer)
576 }
577 Err(err) => return Err(err),
578 };
579 Ok(Self { layer })
580 }
581
582 pub fn from_layer(layer: Arc<ConfigLayer>) -> Result<Self, Arc<ConfigLayer>> {
585 if layer.path.is_some() {
586 Ok(Self { layer })
587 } else {
588 Err(layer)
589 }
590 }
591
592 pub fn save(&self) -> Result<(), ConfigFileSaveError> {
594 fs::write(self.path(), self.layer.data.to_string())
595 .context(self.path())
596 .map_err(ConfigFileSaveError)
597 }
598
599 pub fn path(&self) -> &Path {
601 self.layer.path.as_ref().expect("path must be known")
602 }
603
604 pub fn layer(&self) -> &Arc<ConfigLayer> {
606 &self.layer
607 }
608
609 pub fn set_value(
611 &mut self,
612 name: impl ToConfigNamePath,
613 new_value: impl Into<ConfigValue>,
614 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
615 Arc::make_mut(&mut self.layer).set_value(name, new_value)
616 }
617
618 pub fn delete_value(
620 &mut self,
621 name: impl ToConfigNamePath,
622 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
623 Arc::make_mut(&mut self.layer).delete_value(name)
624 }
625}
626
627#[derive(Clone, Debug)]
641pub struct StackedConfig {
642 layers: Vec<Arc<ConfigLayer>>,
644}
645
646impl StackedConfig {
647 pub fn empty() -> Self {
649 Self { layers: vec![] }
650 }
651
652 pub fn with_defaults() -> Self {
655 Self {
656 layers: DEFAULT_CONFIG_LAYERS.to_vec(),
657 }
658 }
659
660 pub fn load_file(
663 &mut self,
664 source: ConfigSource,
665 path: impl Into<PathBuf>,
666 ) -> Result<(), ConfigLoadError> {
667 let layer = ConfigLayer::load_from_file(source, path.into())?;
668 self.add_layer(layer);
669 Ok(())
670 }
671
672 pub fn load_dir(
675 &mut self,
676 source: ConfigSource,
677 path: impl AsRef<Path>,
678 ) -> Result<(), ConfigLoadError> {
679 let layers = ConfigLayer::load_from_dir(source, path.as_ref())?;
680 self.extend_layers(layers);
681 Ok(())
682 }
683
684 pub fn add_layer(&mut self, layer: impl Into<Arc<ConfigLayer>>) {
686 let layer = layer.into();
687 let index = self.insert_point(layer.source);
688 self.layers.insert(index, layer);
689 }
690
691 pub fn extend_layers<I>(&mut self, layers: I)
693 where
694 I: IntoIterator,
695 I::Item: Into<Arc<ConfigLayer>>,
696 {
697 let layers = layers.into_iter().map(Into::into);
698 for (source, chunk) in &layers.chunk_by(|layer| layer.source) {
699 let index = self.insert_point(source);
700 self.layers.splice(index..index, chunk);
701 }
702 }
703
704 pub fn remove_layers(&mut self, source: ConfigSource) {
706 self.layers.drain(self.layer_range(source));
707 }
708
709 fn layer_range(&self, source: ConfigSource) -> Range<usize> {
710 let start = self
712 .layers
713 .iter()
714 .take_while(|layer| layer.source < source)
715 .count();
716 let count = self.layers[start..]
717 .iter()
718 .take_while(|layer| layer.source == source)
719 .count();
720 start..(start + count)
721 }
722
723 fn insert_point(&self, source: ConfigSource) -> usize {
724 let skip = self
727 .layers
728 .iter()
729 .rev()
730 .take_while(|layer| layer.source > source)
731 .count();
732 self.layers.len() - skip
733 }
734
735 pub fn layers(&self) -> &[Arc<ConfigLayer>] {
737 &self.layers
738 }
739
740 pub fn layers_mut(&mut self) -> &mut [Arc<ConfigLayer>] {
742 &mut self.layers
743 }
744
745 pub fn layers_for(&self, source: ConfigSource) -> &[Arc<ConfigLayer>] {
747 &self.layers[self.layer_range(source)]
748 }
749
750 pub fn get<'de, T: Deserialize<'de>>(
753 &self,
754 name: impl ToConfigNamePath,
755 ) -> Result<T, ConfigGetError> {
756 self.get_value_with(name, |value| T::deserialize(value.into_deserializer()))
757 }
758
759 pub fn get_value(&self, name: impl ToConfigNamePath) -> Result<ConfigValue, ConfigGetError> {
761 self.get_value_with::<_, Infallible>(name, Ok)
762 }
763
764 pub fn get_value_with<T, E: Into<Box<dyn std::error::Error + Send + Sync>>>(
767 &self,
768 name: impl ToConfigNamePath,
769 convert: impl FnOnce(ConfigValue) -> Result<T, E>,
770 ) -> Result<T, ConfigGetError> {
771 self.get_item_with(name, |item| {
772 let value = item
776 .into_value()
777 .expect("Item::None should not exist in loaded tables");
778 convert(value)
779 })
780 }
781
782 pub fn get_table(&self, name: impl ToConfigNamePath) -> Result<ConfigTable, ConfigGetError> {
787 self.get_item_with(name, |item| {
788 item.into_table()
789 .map_err(|item| format!("Expected a table, but is {}", item.type_name()))
790 })
791 }
792
793 fn get_item_with<T, E: Into<Box<dyn std::error::Error + Send + Sync>>>(
794 &self,
795 name: impl ToConfigNamePath,
796 convert: impl FnOnce(ConfigItem) -> Result<T, E>,
797 ) -> Result<T, ConfigGetError> {
798 let name = name.into_name_path();
799 let name = name.borrow();
800 let (item, layer_index) =
801 get_merged_item(&self.layers, name).ok_or_else(|| ConfigGetError::NotFound {
802 name: name.to_string(),
803 })?;
804 convert(item).map_err(|err| ConfigGetError::Type {
810 name: name.to_string(),
811 error: err.into(),
812 source_path: self.layers[layer_index].path.clone(),
813 })
814 }
815
816 pub fn table_keys(&self, name: impl ToConfigNamePath) -> impl Iterator<Item = &str> {
819 let name = name.into_name_path();
820 let name = name.borrow();
821 let to_merge = get_tables_to_merge(&self.layers, name);
822 to_merge
823 .into_iter()
824 .rev()
825 .flat_map(|table| table.iter().map(|(k, _)| k))
826 .unique()
827 }
828}
829
830fn get_merged_item(
833 layers: &[Arc<ConfigLayer>],
834 name: &ConfigNamePathBuf,
835) -> Option<(ConfigItem, usize)> {
836 let mut to_merge = Vec::new();
837 for (index, layer) in layers.iter().enumerate().rev() {
838 let item = match layer.look_up_item(name) {
839 Ok(Some(item)) => item,
840 Ok(None) => continue, Err(_) => break, };
843 if item.is_table_like() {
844 to_merge.push((item, index));
845 } else if to_merge.is_empty() {
846 return Some((item.clone(), index)); } else {
848 break; }
850 }
851
852 let (item, mut top_index) = to_merge.pop()?;
856 let mut merged = item.clone();
857 for (item, index) in to_merge.into_iter().rev() {
858 merge_items(&mut merged, item);
859 top_index = index;
860 }
861 Some((merged, top_index))
862}
863
864fn get_tables_to_merge<'a>(
866 layers: &'a [Arc<ConfigLayer>],
867 name: &ConfigNamePathBuf,
868) -> Vec<&'a ConfigTableLike<'a>> {
869 let mut to_merge = Vec::new();
870 for layer in layers.iter().rev() {
871 match layer.look_up_table(name) {
872 Ok(Some(table)) => to_merge.push(table),
873 Ok(None) => {} Err(_) => break, }
876 }
877 to_merge
878}
879
880fn merge_items(lower_item: &mut ConfigItem, upper_item: &ConfigItem) {
882 let (Some(lower_table), Some(upper_table)) =
883 (lower_item.as_table_like_mut(), upper_item.as_table_like())
884 else {
885 *lower_item = upper_item.clone();
887 return;
888 };
889 for (key, upper) in upper_table.iter() {
890 match lower_table.entry(key) {
891 toml_edit::Entry::Occupied(entry) => {
892 merge_items(entry.into_mut(), upper);
893 }
894 toml_edit::Entry::Vacant(entry) => {
895 entry.insert(upper.clone());
896 }
897 };
898 }
899}
900
901static DEFAULT_CONFIG_LAYERS: LazyLock<[Arc<ConfigLayer>; 1]> = LazyLock::new(|| {
902 let parse = |text: &str| Arc::new(ConfigLayer::parse(ConfigSource::Default, text).unwrap());
903 [parse(include_str!("config/misc.toml"))]
904});
905
906#[cfg(test)]
907mod tests {
908 use assert_matches::assert_matches;
909 use indoc::indoc;
910 use pretty_assertions::assert_eq;
911
912 use super::*;
913
914 #[test]
915 fn test_config_layer_set_value() {
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).unwrap();
925 layer.set_value("bar.baz.blah", "2").unwrap();
926 layer
927 .set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))
928 .unwrap();
929 layer
930 .set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))
931 .unwrap();
932 insta::assert_snapshot!(layer.data, @r#"
933 foo = 1
934
935 [bar]
936 qux = { inline = "table" }
937 to-update = { some = true }
938
939 [bar.baz]
940 blah = "2"
941 "#);
942
943 layer
945 .set_value("foo", ConfigValue::from_iter(["new", "foo"]))
946 .unwrap();
947 layer.set_value("bar.qux", "new bar.qux").unwrap();
949 layer
951 .set_value(
952 "bar.to-update.new",
953 ConfigValue::from_iter([("table", "value")]),
954 )
955 .unwrap();
956 assert_matches!(
958 layer.set_value("bar", 0),
959 Err(ConfigUpdateError::WouldOverwriteTable { name }) if name == "bar"
960 );
961 assert_matches!(
963 layer.set_value("bar.baz.blah.blah", 0),
964 Err(ConfigUpdateError::WouldOverwriteValue { name }) if name == "bar.baz.blah"
965 );
966 insta::assert_snapshot!(layer.data, @r#"
967 foo = ["new", "foo"]
968
969 [bar]
970 qux = "new bar.qux"
971 to-update = { some = true, new = { table = "value" } }
972
973 [bar.baz]
974 blah = "2"
975 "#);
976 }
977
978 #[test]
979 fn test_config_layer_set_value_formatting() {
980 let mut layer = ConfigLayer::empty(ConfigSource::User);
981 layer
983 .set_value(
984 "'foo' . bar . 'baz'",
985 ConfigValue::from_str("'value'").unwrap(),
986 )
987 .unwrap();
988 insta::assert_snapshot!(layer.data, @r"
989 ['foo' . bar]
990 'baz' = 'value'
991 ");
992
993 layer.set_value("foo.bar.baz", "new value").unwrap();
995 layer.set_value("foo.'bar'.blah", 0).unwrap();
996 insta::assert_snapshot!(layer.data, @r#"
997 ['foo' . bar]
998 'baz' = "new value"
999 blah = 0
1000 "#);
1001 }
1002
1003 #[test]
1004 fn test_config_layer_set_value_inline_table() {
1005 let mut layer = ConfigLayer::empty(ConfigSource::User);
1006 layer
1007 .set_value("a", ConfigValue::from_iter([("b", "a.b")]))
1008 .unwrap();
1009 insta::assert_snapshot!(layer.data, @r#"a = { b = "a.b" }"#);
1010
1011 layer.set_value("a.c.d", "a.c.d").unwrap();
1013 insta::assert_snapshot!(layer.data, @r#"a = { b = "a.b", c.d = "a.c.d" }"#);
1014 }
1015
1016 #[test]
1017 fn test_config_layer_delete_value() {
1018 let mut layer = ConfigLayer::empty(ConfigSource::User);
1019 assert_matches!(
1021 layer.delete_value(ConfigNamePathBuf::root()),
1022 Err(ConfigUpdateError::WouldDeleteTable { name }) if name.is_empty()
1023 );
1024
1025 layer.set_value("foo", 1).unwrap();
1027 layer.set_value("bar.baz.blah", "2").unwrap();
1028 layer
1029 .set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))
1030 .unwrap();
1031 layer
1032 .set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))
1033 .unwrap();
1034 insta::assert_snapshot!(layer.data, @r#"
1035 foo = 1
1036
1037 [bar]
1038 qux = { inline = "table" }
1039 to-update = { some = true }
1040
1041 [bar.baz]
1042 blah = "2"
1043 "#);
1044
1045 let old_value = layer.delete_value("foo").unwrap();
1047 assert_eq!(old_value.and_then(|v| v.as_integer()), Some(1));
1048 let old_value = layer.delete_value("bar.qux").unwrap();
1050 assert!(old_value.is_some_and(|v| v.is_inline_table()));
1051 let old_value = layer.delete_value("bar.to-update.some").unwrap();
1053 assert_eq!(old_value.and_then(|v| v.as_bool()), Some(true));
1054 assert_matches!(
1056 layer.delete_value("bar"),
1057 Err(ConfigUpdateError::WouldDeleteTable { name }) if name == "bar"
1058 );
1059 assert_matches!(layer.delete_value("bar.baz.blah.blah"), Ok(None));
1062 insta::assert_snapshot!(layer.data, @r#"
1063 [bar]
1064 to-update = {}
1065
1066 [bar.baz]
1067 blah = "2"
1068 "#);
1069 }
1070
1071 #[test]
1072 fn test_stacked_config_layer_order() {
1073 let empty_data = || DocumentMut::new();
1074 let layer_sources = |config: &StackedConfig| {
1075 config
1076 .layers()
1077 .iter()
1078 .map(|layer| layer.source)
1079 .collect_vec()
1080 };
1081
1082 let mut config = StackedConfig::empty();
1084 config.add_layer(ConfigLayer::with_data(ConfigSource::Repo, empty_data()));
1085 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1086 config.add_layer(ConfigLayer::with_data(ConfigSource::Default, empty_data()));
1087 assert_eq!(
1088 layer_sources(&config),
1089 vec![
1090 ConfigSource::Default,
1091 ConfigSource::User,
1092 ConfigSource::Repo,
1093 ]
1094 );
1095
1096 config.add_layer(ConfigLayer::with_data(
1098 ConfigSource::CommandArg,
1099 empty_data(),
1100 ));
1101 config.add_layer(ConfigLayer::with_data(ConfigSource::EnvBase, empty_data()));
1102 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1103 assert_eq!(
1104 layer_sources(&config),
1105 vec![
1106 ConfigSource::Default,
1107 ConfigSource::EnvBase,
1108 ConfigSource::User,
1109 ConfigSource::User,
1110 ConfigSource::Repo,
1111 ConfigSource::CommandArg,
1112 ]
1113 );
1114
1115 config.remove_layers(ConfigSource::CommandArg);
1117 config.remove_layers(ConfigSource::Default);
1118 config.remove_layers(ConfigSource::User);
1119 assert_eq!(
1120 layer_sources(&config),
1121 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1122 );
1123
1124 config.remove_layers(ConfigSource::Default);
1126 config.remove_layers(ConfigSource::EnvOverrides);
1127 assert_eq!(
1128 layer_sources(&config),
1129 vec![ConfigSource::EnvBase, ConfigSource::Repo]
1130 );
1131
1132 config.extend_layers([
1134 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1135 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1136 ConfigLayer::with_data(ConfigSource::User, empty_data()),
1137 ]);
1138 assert_eq!(
1139 layer_sources(&config),
1140 vec![
1141 ConfigSource::EnvBase,
1142 ConfigSource::User,
1143 ConfigSource::Repo,
1144 ConfigSource::Repo,
1145 ConfigSource::Repo,
1146 ]
1147 );
1148
1149 config.remove_layers(ConfigSource::EnvBase);
1151 config.remove_layers(ConfigSource::User);
1152 config.remove_layers(ConfigSource::Repo);
1153 assert_eq!(layer_sources(&config), vec![]);
1154 }
1155
1156 fn new_user_layer(text: &str) -> ConfigLayer {
1157 ConfigLayer::parse(ConfigSource::User, text).unwrap()
1158 }
1159
1160 #[test]
1161 fn test_stacked_config_get_simple_value() {
1162 let mut config = StackedConfig::empty();
1163 config.add_layer(new_user_layer(indoc! {"
1164 a.b.c = 'a.b.c #0'
1165 "}));
1166 config.add_layer(new_user_layer(indoc! {"
1167 a.d = ['a.d #1']
1168 "}));
1169
1170 assert_eq!(config.get::<String>("a.b.c").unwrap(), "a.b.c #0");
1171
1172 assert_eq!(
1173 config.get::<Vec<String>>("a.d").unwrap(),
1174 vec!["a.d #1".to_owned()]
1175 );
1176
1177 assert_matches!(
1179 config.get::<String>("a.b.missing"),
1180 Err(ConfigGetError::NotFound { name }) if name == "a.b.missing"
1181 );
1182
1183 assert_matches!(
1185 config.get::<String>("a.b.c.d"),
1186 Err(ConfigGetError::NotFound { name }) if name == "a.b.c.d"
1187 );
1188
1189 assert_matches!(
1191 config.get::<String>("a.b"),
1192 Err(ConfigGetError::Type { name, .. }) if name == "a.b"
1193 );
1194 }
1195
1196 #[test]
1197 fn test_stacked_config_get_table_as_value() {
1198 let mut config = StackedConfig::empty();
1199 config.add_layer(new_user_layer(indoc! {"
1200 a.b = { c = 'a.b.c #0' }
1201 "}));
1202 config.add_layer(new_user_layer(indoc! {"
1203 a.d = ['a.d #1']
1204 "}));
1205
1206 insta::assert_snapshot!(
1209 config.get_value("a").unwrap(),
1210 @"{ b = { c = 'a.b.c #0' }, d = ['a.d #1'] }");
1211 }
1212
1213 #[test]
1214 fn test_stacked_config_get_inline_table() {
1215 let mut config = StackedConfig::empty();
1216 config.add_layer(new_user_layer(indoc! {"
1217 a.b = { c = 'a.b.c #0' }
1218 "}));
1219 config.add_layer(new_user_layer(indoc! {"
1220 a.b = { d = 'a.b.d #1' }
1221 "}));
1222
1223 insta::assert_snapshot!(
1225 config.get_value("a.b").unwrap(),
1226 @" { c = 'a.b.c #0' , d = 'a.b.d #1' }");
1227 }
1228
1229 #[test]
1230 fn test_stacked_config_get_inline_non_inline_table() {
1231 let mut config = StackedConfig::empty();
1232 config.add_layer(new_user_layer(indoc! {"
1233 a.b = { c = 'a.b.c #0' }
1234 "}));
1235 config.add_layer(new_user_layer(indoc! {"
1236 a.b.d = 'a.b.d #1'
1237 "}));
1238
1239 insta::assert_snapshot!(
1240 config.get_value("a.b").unwrap(),
1241 @" { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1242 insta::assert_snapshot!(
1243 config.get_table("a").unwrap(),
1244 @"b = { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1245 }
1246
1247 #[test]
1248 fn test_stacked_config_get_value_shadowing_table() {
1249 let mut config = StackedConfig::empty();
1250 config.add_layer(new_user_layer(indoc! {"
1251 a.b.c = 'a.b.c #0'
1252 "}));
1253 config.add_layer(new_user_layer(indoc! {"
1255 a.b = 'a.b #1'
1256 "}));
1257
1258 assert_eq!(config.get::<String>("a.b").unwrap(), "a.b #1");
1259
1260 assert_matches!(
1261 config.get::<String>("a.b.c"),
1262 Err(ConfigGetError::NotFound { name }) if name == "a.b.c"
1263 );
1264 }
1265
1266 #[test]
1267 fn test_stacked_config_get_table_shadowing_table() {
1268 let mut config = StackedConfig::empty();
1269 config.add_layer(new_user_layer(indoc! {"
1270 a.b = 'a.b #0'
1271 "}));
1272 config.add_layer(new_user_layer(indoc! {"
1274 a.b.c = 'a.b.c #1'
1275 "}));
1276 insta::assert_snapshot!(config.get_table("a.b").unwrap(), @"c = 'a.b.c #1'");
1277 }
1278
1279 #[test]
1280 fn test_stacked_config_get_merged_table() {
1281 let mut config = StackedConfig::empty();
1282 config.add_layer(new_user_layer(indoc! {"
1283 a.a.a = 'a.a.a #0'
1284 a.a.b = 'a.a.b #0'
1285 a.b = 'a.b #0'
1286 "}));
1287 config.add_layer(new_user_layer(indoc! {"
1288 a.a.b = 'a.a.b #1'
1289 a.a.c = 'a.a.c #1'
1290 a.c = 'a.c #1'
1291 "}));
1292 insta::assert_snapshot!(config.get_table("a").unwrap(), @r"
1293 a.a = 'a.a.a #0'
1294 a.b = 'a.a.b #1'
1295 a.c = 'a.a.c #1'
1296 b = 'a.b #0'
1297 c = 'a.c #1'
1298 ");
1299 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b", "c"]);
1300 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["a", "b", "c"]);
1301 assert_eq!(config.table_keys("a.b").collect_vec(), vec![""; 0]);
1302 assert_eq!(config.table_keys("a.missing").collect_vec(), vec![""; 0]);
1303 }
1304
1305 #[test]
1306 fn test_stacked_config_get_merged_table_shadowed_top() {
1307 let mut config = StackedConfig::empty();
1308 config.add_layer(new_user_layer(indoc! {"
1309 a.a.a = 'a.a.a #0'
1310 a.b = 'a.b #0'
1311 "}));
1312 config.add_layer(new_user_layer(indoc! {"
1314 a = 'a #1'
1315 "}));
1316 config.add_layer(new_user_layer(indoc! {"
1318 a.a.b = 'a.a.b #2'
1319 "}));
1320 insta::assert_snapshot!(config.get_table("a").unwrap(), @"a.b = 'a.a.b #2'");
1321 assert_eq!(config.table_keys("a").collect_vec(), vec!["a"]);
1322 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1323 }
1324
1325 #[test]
1326 fn test_stacked_config_get_merged_table_shadowed_child() {
1327 let mut config = StackedConfig::empty();
1328 config.add_layer(new_user_layer(indoc! {"
1329 a.a.a = 'a.a.a #0'
1330 a.b = 'a.b #0'
1331 "}));
1332 config.add_layer(new_user_layer(indoc! {"
1334 a.a = 'a.a #1'
1335 "}));
1336 config.add_layer(new_user_layer(indoc! {"
1338 a.a.b = 'a.a.b #2'
1339 "}));
1340 insta::assert_snapshot!(config.get_table("a").unwrap(), @r"
1341 a.b = 'a.a.b #2'
1342 b = 'a.b #0'
1343 ");
1344 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b"]);
1345 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1346 }
1347
1348 #[test]
1349 fn test_stacked_config_get_merged_table_shadowed_parent() {
1350 let mut config = StackedConfig::empty();
1351 config.add_layer(new_user_layer(indoc! {"
1352 a.a.a = 'a.a.a #0'
1353 "}));
1354 config.add_layer(new_user_layer(indoc! {"
1356 a = 'a #1'
1357 "}));
1358 config.add_layer(new_user_layer(indoc! {"
1360 a.a.b = 'a.a.b #2'
1361 "}));
1362 insta::assert_snapshot!(config.get_table("a.a").unwrap(), @"b = 'a.a.b #2'");
1364 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1365 }
1366}