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 System,
287 EnvBase,
289 User,
291 Repo,
293 Workspace,
295 EnvOverrides,
297 CommandArg,
299}
300
301impl Display for ConfigSource {
302 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303 use ConfigSource::*;
304 let c = match self {
305 Default => "default",
306 System => "system",
307 User => "user",
308 Repo => "repo",
309 Workspace => "workspace",
310 CommandArg => "cli",
311 EnvBase | EnvOverrides => "env",
312 };
313 write!(f, "{c}")
314 }
315}
316
317#[derive(Clone, Debug)]
319pub struct ConfigLayer {
320 pub source: ConfigSource,
322 pub path: Option<PathBuf>,
324 pub data: DocumentMut,
326}
327
328impl ConfigLayer {
329 pub fn empty(source: ConfigSource) -> Self {
331 Self::with_data(source, DocumentMut::new())
332 }
333
334 pub fn with_data(source: ConfigSource, data: DocumentMut) -> Self {
336 Self {
337 source,
338 path: None,
339 data,
340 }
341 }
342
343 pub fn parse(source: ConfigSource, text: &str) -> Result<Self, ConfigLoadError> {
345 let data = Document::parse(text).map_err(|error| ConfigLoadError::Parse {
346 error: Box::new(error),
347 source_path: None,
348 })?;
349 Ok(Self::with_data(source, data.into_mut()))
350 }
351
352 pub fn load_from_file(source: ConfigSource, path: PathBuf) -> Result<Self, ConfigLoadError> {
354 let text = fs::read_to_string(&path)
355 .context(&path)
356 .map_err(ConfigLoadError::Read)?;
357 let data = Document::parse(text).map_err(|error| ConfigLoadError::Parse {
358 error: Box::new(error),
359 source_path: Some(path.clone()),
360 })?;
361 Ok(Self {
362 source,
363 path: Some(path),
364 data: data.into_mut(),
365 })
366 }
367
368 fn load_from_dir(source: ConfigSource, path: &Path) -> Result<Vec<Self>, ConfigLoadError> {
369 let mut file_paths: Vec<_> = path
371 .read_dir()
372 .and_then(|dir_entries| {
373 dir_entries
374 .map(|entry| Ok(entry?.path()))
375 .filter_ok(|path| path.is_file() && path.extension() == Some("toml".as_ref()))
376 .try_collect()
377 })
378 .context(path)
379 .map_err(ConfigLoadError::Read)?;
380 file_paths.sort_unstable();
381 file_paths
382 .into_iter()
383 .map(|path| Self::load_from_file(source, path))
384 .try_collect()
385 }
386
387 pub fn is_empty(&self) -> bool {
389 self.data.is_empty()
390 }
391
392 pub fn look_up_table(
398 &self,
399 name: impl ToConfigNamePath,
400 ) -> Result<Option<&ConfigTableLike<'_>>, &ConfigItem> {
401 match self.look_up_item(name) {
402 Ok(Some(item)) => match item.as_table_like() {
403 Some(table) => Ok(Some(table)),
404 None => Err(item),
405 },
406 Ok(None) => Ok(None),
407 Err(item) => Err(item),
408 }
409 }
410
411 pub fn look_up_item(
414 &self,
415 name: impl ToConfigNamePath,
416 ) -> Result<Option<&ConfigItem>, &ConfigItem> {
417 look_up_item(self.data.as_item(), name.into_name_path().borrow())
418 }
419
420 pub fn set_value(
426 &mut self,
427 name: impl ToConfigNamePath,
428 new_value: impl Into<ConfigValue>,
429 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
430 let would_overwrite_table = |name| ConfigUpdateError::WouldOverwriteValue { name };
431 let name = name.into_name_path();
432 let name = name.borrow();
433 let (leaf_key, table_keys) = name
434 .0
435 .split_last()
436 .ok_or_else(|| would_overwrite_table(name.to_string()))?;
437 let parent_table = ensure_table(self.data.as_table_mut(), table_keys)
438 .map_err(|keys| would_overwrite_table(keys.join(".")))?;
439 match parent_table.entry_format(leaf_key) {
440 toml_edit::Entry::Occupied(mut entry) => {
441 if !entry.get().is_value() {
442 return Err(ConfigUpdateError::WouldOverwriteTable {
443 name: name.to_string(),
444 });
445 }
446 let old_item = entry.insert(toml_edit::value(new_value));
447 Ok(Some(old_item.into_value().unwrap()))
448 }
449 toml_edit::Entry::Vacant(entry) => {
450 entry.insert(toml_edit::value(new_value));
451 let mut new_key = parent_table.key_mut(leaf_key).unwrap();
453 new_key.leaf_decor_mut().clear();
454 Ok(None)
455 }
456 }
457 }
458
459 pub fn delete_value(
465 &mut self,
466 name: impl ToConfigNamePath,
467 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
468 let would_delete_table = |name| ConfigUpdateError::WouldDeleteTable { name };
469 let name = name.into_name_path();
470 let name = name.borrow();
471 let mut keys = name.components();
472 let leaf_key = keys
473 .next_back()
474 .ok_or_else(|| would_delete_table(name.to_string()))?;
475 let Some(parent_table) = keys.try_fold(
476 self.data.as_table_mut() as &mut ConfigTableLike,
477 |table, key| table.get_mut(key)?.as_table_like_mut(),
478 ) else {
479 return Ok(None);
480 };
481 match parent_table.entry(leaf_key) {
482 toml_edit::Entry::Occupied(entry) => {
483 if !entry.get().is_value() {
484 return Err(would_delete_table(name.to_string()));
485 }
486 let old_item = entry.remove();
487 Ok(Some(old_item.into_value().unwrap()))
488 }
489 toml_edit::Entry::Vacant(_) => Ok(None),
490 }
491 }
492
493 pub fn ensure_table(
499 &mut self,
500 name: impl ToConfigNamePath,
501 ) -> Result<&mut ConfigTableLike<'_>, ConfigUpdateError> {
502 let would_overwrite_table = |name| ConfigUpdateError::WouldOverwriteValue { name };
503 let name = name.into_name_path();
504 let name = name.borrow();
505 ensure_table(self.data.as_table_mut(), &name.0)
506 .map_err(|keys| would_overwrite_table(keys.join(".")))
507 }
508}
509
510fn look_up_item<'a>(
513 root_item: &'a ConfigItem,
514 name: &ConfigNamePathBuf,
515) -> Result<Option<&'a ConfigItem>, &'a ConfigItem> {
516 let mut cur_item = root_item;
517 for key in name.components() {
518 let Some(table) = cur_item.as_table_like() else {
519 return Err(cur_item);
520 };
521 cur_item = match table.get(key) {
522 Some(item) => item,
523 None => return Ok(None),
524 };
525 }
526 Ok(Some(cur_item))
527}
528
529fn ensure_table<'a, 'b>(
532 root_table: &'a mut ConfigTableLike<'a>,
533 keys: &'b [toml_edit::Key],
534) -> Result<&'a mut ConfigTableLike<'a>, &'b [toml_edit::Key]> {
535 keys.iter()
536 .enumerate()
537 .try_fold(root_table, |table, (i, key)| {
538 let sub_item = table.entry_format(key).or_insert_with(new_implicit_table);
539 sub_item.as_table_like_mut().ok_or(&keys[..=i])
540 })
541}
542
543fn new_implicit_table() -> ConfigItem {
544 let mut table = ConfigTable::new();
545 table.set_implicit(true);
546 ConfigItem::Table(table)
547}
548
549#[derive(Clone, Debug)]
552pub struct ConfigFile {
553 layer: Arc<ConfigLayer>,
554}
555
556impl ConfigFile {
557 pub fn load_or_empty(
560 source: ConfigSource,
561 path: impl Into<PathBuf>,
562 ) -> Result<Self, ConfigLoadError> {
563 let layer = match ConfigLayer::load_from_file(source, path.into()) {
564 Ok(layer) => Arc::new(layer),
565 Err(ConfigLoadError::Read(PathError {
566 path,
567 source: error,
568 })) if error.kind() == io::ErrorKind::NotFound => {
569 let mut data = DocumentMut::new();
570 data.decor_mut()
571 .set_prefix("#:schema https://docs.jj-vcs.dev/latest/config-schema.json\n\n");
572 let layer = ConfigLayer {
573 source,
574 path: Some(path),
575 data,
576 };
577 Arc::new(layer)
578 }
579 Err(err) => return Err(err),
580 };
581 Ok(Self { layer })
582 }
583
584 pub fn from_layer(layer: Arc<ConfigLayer>) -> Result<Self, Arc<ConfigLayer>> {
587 if layer.path.is_some() {
588 Ok(Self { layer })
589 } else {
590 Err(layer)
591 }
592 }
593
594 pub fn save(&self) -> Result<(), ConfigFileSaveError> {
596 fs::write(self.path(), self.layer.data.to_string())
597 .context(self.path())
598 .map_err(ConfigFileSaveError)
599 }
600
601 pub fn path(&self) -> &Path {
603 self.layer.path.as_ref().expect("path must be known")
604 }
605
606 pub fn layer(&self) -> &Arc<ConfigLayer> {
608 &self.layer
609 }
610
611 pub fn data_mut(&mut self) -> &mut DocumentMut {
615 &mut Arc::make_mut(&mut self.layer).data
616 }
617
618 pub fn set_value(
620 &mut self,
621 name: impl ToConfigNamePath,
622 new_value: impl Into<ConfigValue>,
623 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
624 Arc::make_mut(&mut self.layer).set_value(name, new_value)
625 }
626
627 pub fn delete_value(
629 &mut self,
630 name: impl ToConfigNamePath,
631 ) -> Result<Option<ConfigValue>, ConfigUpdateError> {
632 Arc::make_mut(&mut self.layer).delete_value(name)
633 }
634}
635
636#[derive(Clone, Debug)]
650pub struct StackedConfig {
651 layers: Vec<Arc<ConfigLayer>>,
653}
654
655impl StackedConfig {
656 pub fn empty() -> Self {
658 Self { layers: vec![] }
659 }
660
661 pub fn with_defaults() -> Self {
664 Self {
665 layers: DEFAULT_CONFIG_LAYERS.to_vec(),
666 }
667 }
668
669 pub fn load_file(
672 &mut self,
673 source: ConfigSource,
674 path: impl Into<PathBuf>,
675 ) -> Result<(), ConfigLoadError> {
676 let layer = ConfigLayer::load_from_file(source, path.into())?;
677 self.add_layer(layer);
678 Ok(())
679 }
680
681 pub fn load_dir(
684 &mut self,
685 source: ConfigSource,
686 path: impl AsRef<Path>,
687 ) -> Result<(), ConfigLoadError> {
688 let layers = ConfigLayer::load_from_dir(source, path.as_ref())?;
689 self.extend_layers(layers);
690 Ok(())
691 }
692
693 pub fn add_layer(&mut self, layer: impl Into<Arc<ConfigLayer>>) {
695 let layer = layer.into();
696 let index = self.insert_point(layer.source);
697 self.layers.insert(index, layer);
698 }
699
700 pub fn extend_layers<I>(&mut self, layers: I)
702 where
703 I: IntoIterator,
704 I::Item: Into<Arc<ConfigLayer>>,
705 {
706 let layers = layers.into_iter().map(Into::into);
707 for (source, chunk) in &layers.chunk_by(|layer| layer.source) {
708 let index = self.insert_point(source);
709 self.layers.splice(index..index, chunk);
710 }
711 }
712
713 pub fn remove_layers(&mut self, source: ConfigSource) {
715 self.layers.drain(self.layer_range(source));
716 }
717
718 fn layer_range(&self, source: ConfigSource) -> Range<usize> {
719 let start = self
721 .layers
722 .iter()
723 .take_while(|layer| layer.source < source)
724 .count();
725 let count = self.layers[start..]
726 .iter()
727 .take_while(|layer| layer.source == source)
728 .count();
729 start..(start + count)
730 }
731
732 fn insert_point(&self, source: ConfigSource) -> usize {
733 let skip = self
736 .layers
737 .iter()
738 .rev()
739 .take_while(|layer| layer.source > source)
740 .count();
741 self.layers.len() - skip
742 }
743
744 pub fn layers(&self) -> &[Arc<ConfigLayer>] {
746 &self.layers
747 }
748
749 pub fn layers_mut(&mut self) -> &mut [Arc<ConfigLayer>] {
751 &mut self.layers
752 }
753
754 pub fn layers_for(&self, source: ConfigSource) -> &[Arc<ConfigLayer>] {
756 &self.layers[self.layer_range(source)]
757 }
758
759 pub fn get<'de, T: Deserialize<'de>>(
762 &self,
763 name: impl ToConfigNamePath,
764 ) -> Result<T, ConfigGetError> {
765 self.get_value_with(name, |value| T::deserialize(value.into_deserializer()))
766 }
767
768 pub fn get_value(&self, name: impl ToConfigNamePath) -> Result<ConfigValue, ConfigGetError> {
770 self.get_value_with::<_, Infallible>(name, Ok)
771 }
772
773 pub fn get_value_with<T, E: Into<Box<dyn std::error::Error + Send + Sync>>>(
776 &self,
777 name: impl ToConfigNamePath,
778 convert: impl FnOnce(ConfigValue) -> Result<T, E>,
779 ) -> Result<T, ConfigGetError> {
780 self.get_item_with(name, |item| {
781 let value = item
785 .into_value()
786 .expect("Item::None should not exist in loaded tables");
787 convert(value)
788 })
789 }
790
791 pub fn get_table(&self, name: impl ToConfigNamePath) -> Result<ConfigTable, ConfigGetError> {
796 self.get_item_with(name, |item| {
797 item.into_table()
798 .map_err(|item| format!("Expected a table, but is {}", item.type_name()))
799 })
800 }
801
802 fn get_item_with<T, E: Into<Box<dyn std::error::Error + Send + Sync>>>(
803 &self,
804 name: impl ToConfigNamePath,
805 convert: impl FnOnce(ConfigItem) -> Result<T, E>,
806 ) -> Result<T, ConfigGetError> {
807 let name = name.into_name_path();
808 let name = name.borrow();
809 let (item, layer_index) =
810 get_merged_item(&self.layers, name).ok_or_else(|| ConfigGetError::NotFound {
811 name: name.to_string(),
812 })?;
813 convert(item).map_err(|err| ConfigGetError::Type {
819 name: name.to_string(),
820 error: err.into(),
821 source_path: self.layers[layer_index].path.clone(),
822 })
823 }
824
825 pub fn table_keys(&self, name: impl ToConfigNamePath) -> impl Iterator<Item = &str> {
828 let name = name.into_name_path();
829 let name = name.borrow();
830 let to_merge = get_tables_to_merge(&self.layers, name);
831 to_merge
832 .into_iter()
833 .rev()
834 .flat_map(|table| table.iter().map(|(k, _)| k))
835 .unique()
836 }
837}
838
839fn get_merged_item(
842 layers: &[Arc<ConfigLayer>],
843 name: &ConfigNamePathBuf,
844) -> Option<(ConfigItem, usize)> {
845 let mut to_merge = Vec::new();
846 for (index, layer) in layers.iter().enumerate().rev() {
847 let item = match layer.look_up_item(name) {
848 Ok(Some(item)) => item,
849 Ok(None) => continue, Err(_) => break, };
852 if item.is_table_like() {
853 to_merge.push((item, index));
854 } else if to_merge.is_empty() {
855 return Some((item.clone(), index)); } else {
857 break; }
859 }
860
861 let (item, mut top_index) = to_merge.pop()?;
865 let mut merged = item.clone();
866 for (item, index) in to_merge.into_iter().rev() {
867 merge_items(&mut merged, item);
868 top_index = index;
869 }
870 Some((merged, top_index))
871}
872
873fn get_tables_to_merge<'a>(
875 layers: &'a [Arc<ConfigLayer>],
876 name: &ConfigNamePathBuf,
877) -> Vec<&'a ConfigTableLike<'a>> {
878 let mut to_merge = Vec::new();
879 for layer in layers.iter().rev() {
880 match layer.look_up_table(name) {
881 Ok(Some(table)) => to_merge.push(table),
882 Ok(None) => {} Err(_) => break, }
885 }
886 to_merge
887}
888
889fn merge_items(lower_item: &mut ConfigItem, upper_item: &ConfigItem) {
891 let (Some(lower_table), Some(upper_table)) =
892 (lower_item.as_table_like_mut(), upper_item.as_table_like())
893 else {
894 *lower_item = upper_item.clone();
896 return;
897 };
898 for (key, upper) in upper_table.iter() {
899 match lower_table.entry(key) {
900 toml_edit::Entry::Occupied(entry) => {
901 merge_items(entry.into_mut(), upper);
902 }
903 toml_edit::Entry::Vacant(entry) => {
904 entry.insert(upper.clone());
905 }
906 }
907 }
908}
909
910static DEFAULT_CONFIG_LAYERS: LazyLock<[Arc<ConfigLayer>; 1]> = LazyLock::new(|| {
911 let parse = |text: &str| Arc::new(ConfigLayer::parse(ConfigSource::Default, text).unwrap());
912 [parse(include_str!("config/misc.toml"))]
913});
914
915#[cfg(test)]
916mod tests {
917 use std::assert_matches;
918
919 use indoc::indoc;
920 use pretty_assertions::assert_eq;
921
922 use super::*;
923 use crate::tests::TestResult;
924
925 #[test]
926 fn test_config_layer_set_value() -> TestResult {
927 let mut layer = ConfigLayer::empty(ConfigSource::User);
928 assert_matches!(
930 layer.set_value(ConfigNamePathBuf::root(), 0),
931 Err(ConfigUpdateError::WouldOverwriteValue { name }) if name.is_empty()
932 );
933
934 layer.set_value("foo", 1)?;
936 layer.set_value("bar.baz.blah", "2")?;
937 layer.set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))?;
938 layer.set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))?;
939 insta::assert_snapshot!(layer.data, @r#"
940 foo = 1
941
942 [bar]
943 qux = { inline = "table" }
944 to-update = { some = true }
945
946 [bar.baz]
947 blah = "2"
948 "#);
949
950 layer.set_value("foo", ConfigValue::from_iter(["new", "foo"]))?;
952 layer.set_value("bar.qux", "new bar.qux")?;
954 layer.set_value(
956 "bar.to-update.new",
957 ConfigValue::from_iter([("table", "value")]),
958 )?;
959 assert_matches!(
961 layer.set_value("bar", 0),
962 Err(ConfigUpdateError::WouldOverwriteTable { name }) if name == "bar"
963 );
964 assert_matches!(
966 layer.set_value("bar.baz.blah.blah", 0),
967 Err(ConfigUpdateError::WouldOverwriteValue { name }) if name == "bar.baz.blah"
968 );
969 insta::assert_snapshot!(layer.data, @r#"
970 foo = ["new", "foo"]
971
972 [bar]
973 qux = "new bar.qux"
974 to-update = { some = true, new = { table = "value" } }
975
976 [bar.baz]
977 blah = "2"
978 "#);
979 Ok(())
980 }
981
982 #[test]
983 fn test_config_layer_set_value_formatting() -> TestResult {
984 let mut layer = ConfigLayer::empty(ConfigSource::User);
985 layer.set_value("'foo' . bar . 'baz'", ConfigValue::from_str("'value'")?)?;
987 insta::assert_snapshot!(layer.data, @"
988 ['foo' . bar]
989 'baz' = 'value'
990 ");
991
992 layer.set_value("foo.bar.baz", "new value")?;
994 layer.set_value("foo.'bar'.blah", 0)?;
995 insta::assert_snapshot!(layer.data, @r#"
996 ['foo' . bar]
997 'baz' = "new value"
998 blah = 0
999 "#);
1000 Ok(())
1001 }
1002
1003 #[test]
1004 fn test_config_layer_set_value_inline_table() -> TestResult {
1005 let mut layer = ConfigLayer::empty(ConfigSource::User);
1006 layer.set_value("a", ConfigValue::from_iter([("b", "a.b")]))?;
1007 insta::assert_snapshot!(layer.data, @r#"a = { b = "a.b" }"#);
1008
1009 layer.set_value("a.c.d", "a.c.d")?;
1011 insta::assert_snapshot!(layer.data, @r#"a = { b = "a.b", c.d = "a.c.d" }"#);
1012 Ok(())
1013 }
1014
1015 #[test]
1016 fn test_config_layer_delete_value() -> TestResult {
1017 let mut layer = ConfigLayer::empty(ConfigSource::User);
1018 assert_matches!(
1020 layer.delete_value(ConfigNamePathBuf::root()),
1021 Err(ConfigUpdateError::WouldDeleteTable { name }) if name.is_empty()
1022 );
1023
1024 layer.set_value("foo", 1)?;
1026 layer.set_value("bar.baz.blah", "2")?;
1027 layer.set_value("bar.qux", ConfigValue::from_iter([("inline", "table")]))?;
1028 layer.set_value("bar.to-update", ConfigValue::from_iter([("some", true)]))?;
1029 insta::assert_snapshot!(layer.data, @r#"
1030 foo = 1
1031
1032 [bar]
1033 qux = { inline = "table" }
1034 to-update = { some = true }
1035
1036 [bar.baz]
1037 blah = "2"
1038 "#);
1039
1040 let old_value = layer.delete_value("foo")?;
1042 assert_eq!(old_value.and_then(|v| v.as_integer()), Some(1));
1043 let old_value = layer.delete_value("bar.qux")?;
1045 assert!(old_value.is_some_and(|v| v.is_inline_table()));
1046 let old_value = layer.delete_value("bar.to-update.some")?;
1048 assert_eq!(old_value.and_then(|v| v.as_bool()), Some(true));
1049 assert_matches!(
1051 layer.delete_value("bar"),
1052 Err(ConfigUpdateError::WouldDeleteTable { name }) if name == "bar"
1053 );
1054 assert_matches!(layer.delete_value("bar.baz.blah.blah"), Ok(None));
1057 insta::assert_snapshot!(layer.data, @r#"
1058 [bar]
1059 to-update = {}
1060
1061 [bar.baz]
1062 blah = "2"
1063 "#);
1064 Ok(())
1065 }
1066
1067 #[test]
1068 fn test_stacked_config_layer_order() {
1069 let empty_data = || DocumentMut::new();
1070 let layer_sources = |config: &StackedConfig| {
1071 config
1072 .layers()
1073 .iter()
1074 .map(|layer| layer.source)
1075 .collect_vec()
1076 };
1077
1078 let mut config = StackedConfig::empty();
1080 config.add_layer(ConfigLayer::with_data(ConfigSource::Repo, empty_data()));
1081 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1082 config.add_layer(ConfigLayer::with_data(ConfigSource::System, empty_data()));
1083 config.add_layer(ConfigLayer::with_data(ConfigSource::Default, empty_data()));
1084 assert_eq!(
1085 layer_sources(&config),
1086 vec![
1087 ConfigSource::Default,
1088 ConfigSource::System,
1089 ConfigSource::User,
1090 ConfigSource::Repo,
1091 ]
1092 );
1093
1094 config.add_layer(ConfigLayer::with_data(
1096 ConfigSource::CommandArg,
1097 empty_data(),
1098 ));
1099 config.add_layer(ConfigLayer::with_data(ConfigSource::EnvBase, empty_data()));
1100 config.add_layer(ConfigLayer::with_data(ConfigSource::User, empty_data()));
1101 assert_eq!(
1102 layer_sources(&config),
1103 vec![
1104 ConfigSource::Default,
1105 ConfigSource::System,
1106 ConfigSource::EnvBase,
1107 ConfigSource::User,
1108 ConfigSource::User,
1109 ConfigSource::Repo,
1110 ConfigSource::CommandArg,
1111 ]
1112 );
1113
1114 config.remove_layers(ConfigSource::CommandArg);
1116 config.remove_layers(ConfigSource::Default);
1117 config.remove_layers(ConfigSource::User);
1118 assert_eq!(
1119 layer_sources(&config),
1120 vec![
1121 ConfigSource::System,
1122 ConfigSource::EnvBase,
1123 ConfigSource::Repo,
1124 ]
1125 );
1126
1127 config.remove_layers(ConfigSource::Default);
1129 config.remove_layers(ConfigSource::EnvOverrides);
1130 assert_eq!(
1131 layer_sources(&config),
1132 vec![
1133 ConfigSource::System,
1134 ConfigSource::EnvBase,
1135 ConfigSource::Repo,
1136 ]
1137 );
1138
1139 config.extend_layers([
1141 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1142 ConfigLayer::with_data(ConfigSource::Repo, empty_data()),
1143 ConfigLayer::with_data(ConfigSource::User, empty_data()),
1144 ]);
1145 assert_eq!(
1146 layer_sources(&config),
1147 vec![
1148 ConfigSource::System,
1149 ConfigSource::EnvBase,
1150 ConfigSource::User,
1151 ConfigSource::Repo,
1152 ConfigSource::Repo,
1153 ConfigSource::Repo,
1154 ]
1155 );
1156
1157 config.remove_layers(ConfigSource::EnvBase);
1159 config.remove_layers(ConfigSource::System);
1160 config.remove_layers(ConfigSource::User);
1161 config.remove_layers(ConfigSource::Repo);
1162 assert_eq!(layer_sources(&config), vec![]);
1163 }
1164
1165 fn new_user_layer(text: &str) -> ConfigLayer {
1166 ConfigLayer::parse(ConfigSource::User, text).unwrap()
1167 }
1168
1169 #[test]
1170 fn test_stacked_config_get_simple_value() -> TestResult {
1171 let mut config = StackedConfig::empty();
1172 config.add_layer(new_user_layer(indoc! {"
1173 a.b.c = 'a.b.c #0'
1174 "}));
1175 config.add_layer(new_user_layer(indoc! {"
1176 a.d = ['a.d #1']
1177 "}));
1178
1179 assert_eq!(config.get::<String>("a.b.c")?, "a.b.c #0");
1180
1181 assert_eq!(config.get::<Vec<String>>("a.d")?, vec!["a.d #1".to_owned()]);
1182
1183 assert_matches!(
1185 config.get::<String>("a.b.missing"),
1186 Err(ConfigGetError::NotFound { name }) if name == "a.b.missing"
1187 );
1188
1189 assert_matches!(
1191 config.get::<String>("a.b.c.d"),
1192 Err(ConfigGetError::NotFound { name }) if name == "a.b.c.d"
1193 );
1194
1195 assert_matches!(
1197 config.get::<String>("a.b"),
1198 Err(ConfigGetError::Type { name, .. }) if name == "a.b"
1199 );
1200 Ok(())
1201 }
1202
1203 #[test]
1204 fn test_stacked_config_get_table_as_value() -> TestResult {
1205 let mut config = StackedConfig::empty();
1206 config.add_layer(new_user_layer(indoc! {"
1207 a.b = { c = 'a.b.c #0' }
1208 "}));
1209 config.add_layer(new_user_layer(indoc! {"
1210 a.d = ['a.d #1']
1211 "}));
1212
1213 insta::assert_snapshot!(
1216 config.get_value("a")?,
1217 @"{ b = { c = 'a.b.c #0' }, d = ['a.d #1'] }");
1218 Ok(())
1219 }
1220
1221 #[test]
1222 fn test_stacked_config_get_inline_table() -> TestResult {
1223 let mut config = StackedConfig::empty();
1224 config.add_layer(new_user_layer(indoc! {"
1225 a.b = { c = 'a.b.c #0' }
1226 "}));
1227 config.add_layer(new_user_layer(indoc! {"
1228 a.b = { d = 'a.b.d #1' }
1229 "}));
1230
1231 insta::assert_snapshot!(
1233 config.get_value("a.b")?,
1234 @" { c = 'a.b.c #0' , d = 'a.b.d #1' }");
1235 Ok(())
1236 }
1237
1238 #[test]
1239 fn test_stacked_config_get_inline_non_inline_table() -> TestResult {
1240 let mut config = StackedConfig::empty();
1241 config.add_layer(new_user_layer(indoc! {"
1242 a.b = { c = 'a.b.c #0' }
1243 "}));
1244 config.add_layer(new_user_layer(indoc! {"
1245 a.b.d = 'a.b.d #1'
1246 "}));
1247
1248 insta::assert_snapshot!(
1249 config.get_value("a.b")?,
1250 @" { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1251 insta::assert_snapshot!(
1252 config.get_table("a")?,
1253 @"b = { c = 'a.b.c #0' , d = 'a.b.d #1'}");
1254 Ok(())
1255 }
1256
1257 #[test]
1258 fn test_stacked_config_get_value_shadowing_table() -> TestResult {
1259 let mut config = StackedConfig::empty();
1260 config.add_layer(new_user_layer(indoc! {"
1261 a.b.c = 'a.b.c #0'
1262 "}));
1263 config.add_layer(new_user_layer(indoc! {"
1265 a.b = 'a.b #1'
1266 "}));
1267
1268 assert_eq!(config.get::<String>("a.b")?, "a.b #1");
1269
1270 assert_matches!(
1271 config.get::<String>("a.b.c"),
1272 Err(ConfigGetError::NotFound { name }) if name == "a.b.c"
1273 );
1274 Ok(())
1275 }
1276
1277 #[test]
1278 fn test_stacked_config_get_table_shadowing_table() -> TestResult {
1279 let mut config = StackedConfig::empty();
1280 config.add_layer(new_user_layer(indoc! {"
1281 a.b = 'a.b #0'
1282 "}));
1283 config.add_layer(new_user_layer(indoc! {"
1285 a.b.c = 'a.b.c #1'
1286 "}));
1287 insta::assert_snapshot!(config.get_table("a.b")?, @"c = 'a.b.c #1'");
1288 Ok(())
1289 }
1290
1291 #[test]
1292 fn test_stacked_config_get_merged_table() -> TestResult {
1293 let mut config = StackedConfig::empty();
1294 config.add_layer(new_user_layer(indoc! {"
1295 a.a.a = 'a.a.a #0'
1296 a.a.b = 'a.a.b #0'
1297 a.b = 'a.b #0'
1298 "}));
1299 config.add_layer(new_user_layer(indoc! {"
1300 a.a.b = 'a.a.b #1'
1301 a.a.c = 'a.a.c #1'
1302 a.c = 'a.c #1'
1303 "}));
1304 insta::assert_snapshot!(config.get_table("a")?, @"
1305 a.a = 'a.a.a #0'
1306 a.b = 'a.a.b #1'
1307 a.c = 'a.a.c #1'
1308 b = 'a.b #0'
1309 c = 'a.c #1'
1310 ");
1311 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b", "c"]);
1312 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["a", "b", "c"]);
1313 assert_eq!(config.table_keys("a.b").collect_vec(), vec![""; 0]);
1314 assert_eq!(config.table_keys("a.missing").collect_vec(), vec![""; 0]);
1315 Ok(())
1316 }
1317
1318 #[test]
1319 fn test_stacked_config_get_merged_table_shadowed_top() -> TestResult {
1320 let mut config = StackedConfig::empty();
1321 config.add_layer(new_user_layer(indoc! {"
1322 a.a.a = 'a.a.a #0'
1323 a.b = 'a.b #0'
1324 "}));
1325 config.add_layer(new_user_layer(indoc! {"
1327 a = 'a #1'
1328 "}));
1329 config.add_layer(new_user_layer(indoc! {"
1331 a.a.b = 'a.a.b #2'
1332 "}));
1333 insta::assert_snapshot!(config.get_table("a")?, @"a.b = 'a.a.b #2'");
1334 assert_eq!(config.table_keys("a").collect_vec(), vec!["a"]);
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_child() -> TestResult {
1341 let mut config = StackedConfig::empty();
1342 config.add_layer(new_user_layer(indoc! {"
1343 a.a.a = 'a.a.a #0'
1344 a.b = 'a.b #0'
1345 "}));
1346 config.add_layer(new_user_layer(indoc! {"
1348 a.a = 'a.a #1'
1349 "}));
1350 config.add_layer(new_user_layer(indoc! {"
1352 a.a.b = 'a.a.b #2'
1353 "}));
1354 insta::assert_snapshot!(config.get_table("a")?, @"
1355 a.b = 'a.a.b #2'
1356 b = 'a.b #0'
1357 ");
1358 assert_eq!(config.table_keys("a").collect_vec(), vec!["a", "b"]);
1359 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1360 Ok(())
1361 }
1362
1363 #[test]
1364 fn test_stacked_config_get_merged_table_shadowed_parent() -> TestResult {
1365 let mut config = StackedConfig::empty();
1366 config.add_layer(new_user_layer(indoc! {"
1367 a.a.a = 'a.a.a #0'
1368 "}));
1369 config.add_layer(new_user_layer(indoc! {"
1371 a = 'a #1'
1372 "}));
1373 config.add_layer(new_user_layer(indoc! {"
1375 a.a.b = 'a.a.b #2'
1376 "}));
1377 insta::assert_snapshot!(config.get_table("a.a")?, @"b = 'a.a.b #2'");
1379 assert_eq!(config.table_keys("a.a").collect_vec(), vec!["b"]);
1380 Ok(())
1381 }
1382}