1use crate::glob::KeyGlobPattern;
4use crate::path::render_keys;
5use crate::{ConfigObject, ConfigValue};
6
7#[derive(Debug, Clone, Default, PartialEq, Eq)]
9pub struct MergeOptions {
10 pub strict: bool,
12 pub shallow: Option<KeyGlobPattern>,
15}
16
17impl MergeOptions {
18 pub const LAST_WINS: Self = Self {
20 strict: false,
21 shallow: None,
22 };
23 pub const STRICT: Self = Self {
25 strict: true,
26 shallow: None,
27 };
28
29 pub fn shallow_root() -> Self {
31 Self {
32 strict: false,
33 shallow: Some("*".parse().expect("valid root selector")),
34 }
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
39pub enum MergeError {
40 #[error(
42 "type conflict at `{}`: {expected} would be replaced by {found}",
43 render_keys(path)
44 )]
45 TypeConflict {
46 path: Vec<String>,
47 expected: &'static str,
48 found: &'static str,
49 },
50}
51
52impl MergeError {
53 pub fn path(&self) -> &[String] {
55 match self {
56 Self::TypeConflict { path, .. } => path,
57 }
58 }
59}
60
61pub fn merge_into<V: ConfigValue>(
66 base: &mut V,
67 over: V,
68 opts: &MergeOptions,
69) -> Result<(), MergeError> {
70 let mut path = Vec::new();
71 merge_at(base, over, opts, &mut path)
72}
73
74pub fn merge<V: ConfigValue>(
84 layers: impl IntoIterator<Item = V>,
85 opts: &MergeOptions,
86) -> Result<V, MergeError> {
87 let mut acc = V::object(V::Object::new());
88 let mut path = Vec::new();
89 for layer in layers {
90 merge_at(&mut acc, layer, opts, &mut path)?;
91 debug_assert!(path.is_empty(), "breadcrumb leaked between layers");
92 }
93 Ok(acc)
94}
95
96fn merge_at<V: ConfigValue>(
99 base: &mut V,
100 over: V,
101 opts: &MergeOptions,
102 path: &mut Vec<String>,
103) -> Result<(), MergeError> {
104 if let Some(base_map) = base.as_object_mut() {
105 match over.into_object() {
106 Ok(over_map) => {
107 for (k, v) in over_map {
108 if let Some(slot) = base_map.get_mut(&k) {
109 path.push(k);
110 if opts
111 .shallow
112 .as_ref()
113 .is_some_and(|glob| glob.matches_keys(path))
114 {
115 replace(slot, v, opts, path)?;
116 } else {
117 merge_at(slot, v, opts, path)?;
118 }
119 path.pop();
120 } else {
121 base_map.insert(k, v);
122 }
123 }
124 return Ok(());
125 }
126 Err(over) => return replace(base, over, opts, path),
127 }
128 }
129 replace(base, over, opts, path)
130}
131
132fn replace<V: ConfigValue>(
133 base: &mut V,
134 over: V,
135 opts: &MergeOptions,
136 path: &[String],
137) -> Result<(), MergeError> {
138 if opts.strict {
139 check_kind(base.kind(), over.kind(), path)?;
140 }
141 *base = over;
142 Ok(())
143}
144
145fn check_kind(
147 expected: &'static str,
148 found: &'static str,
149 path: &[String],
150) -> Result<(), MergeError> {
151 if expected == found {
152 return Ok(());
153 }
154 Err(MergeError::TypeConflict {
155 path: path.to_vec(),
156 expected,
157 found,
158 })
159}