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