mod strict;
mod value;
pub use value::{Map, Number, Value};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct MergeOptions {
pub strict: bool,
}
impl MergeOptions {
pub const LAST_WINS: Self = Self { strict: false };
pub const STRICT: Self = Self { strict: true };
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum MergeError {
#[error(
"type conflict at `{}`: {expected} would be replaced by {found}",
render_path(path)
)]
TypeConflict {
path: Vec<String>,
expected: &'static str,
found: &'static str,
},
}
impl MergeError {
pub fn path(&self) -> &[String] {
match self {
Self::TypeConflict { path, .. } => path,
}
}
}
fn render_path(path: &[String]) -> String {
if path.is_empty() {
"<root>".to_string()
} else {
path.join(".")
}
}
pub fn merge_into(base: &mut Value, over: Value, opts: &MergeOptions) -> Result<(), MergeError> {
let mut path = Vec::new();
merge_at(base, over, opts, &mut path)
}
pub fn merge(layers: impl IntoIterator<Item = Value>) -> Result<Value, MergeError> {
merge_with(layers, &MergeOptions::LAST_WINS)
}
pub fn merge_with(
layers: impl IntoIterator<Item = Value>,
opts: &MergeOptions,
) -> Result<Value, MergeError> {
let mut acc = Value::Object(Map::new());
let mut path = Vec::new();
for layer in layers {
merge_at(&mut acc, layer, opts, &mut path)?;
debug_assert!(path.is_empty(), "breadcrumb leaked between layers");
}
Ok(acc)
}
fn merge_at(
base: &mut Value,
over: Value,
opts: &MergeOptions,
path: &mut Vec<String>,
) -> Result<(), MergeError> {
match (base, over) {
(Value::Object(base_map), Value::Object(over_map)) => {
for (k, v) in over_map {
if let Some(slot) = base_map.get_mut(&k) {
path.push(k);
merge_at(slot, v, opts, path)?;
path.pop();
} else {
base_map.insert(k, v);
}
}
Ok(())
}
(base, over) => replace(base, over, opts, path),
}
}
fn replace(
base: &mut Value,
over: Value,
opts: &MergeOptions,
path: &[String],
) -> Result<(), MergeError> {
if opts.strict {
strict::check(base.kind(), over.kind(), path)?;
}
*base = over;
Ok(())
}