///|
/// Merge independent object-field changes. Conflicts keep the original value.
/// Arrays are atomic values. No side silently wins a conflict.
pub fn merge(
base : Json,
ours : Json,
theirs : Json,
) -> MergeResult raise ConfigError {
validate_document(base)
validate_document(ours)
validate_document(theirs)
let conflicts : Array[Conflict] = []
let value = merge_at(Some(base), Some(ours), Some(theirs), "", conflicts)
let merged = value.unwrap()
validate_document(merged)
{ value: merged, conflicts, }
}
///|
fn merge_at(
base : Json?,
ours : Json?,
theirs : Json?,
path : String,
conflicts : Array[Conflict],
) -> Json? {
if same_slot(ours, theirs) {
return ours.map(clone_json)
}
if same_slot(ours, base) {
return theirs.map(clone_json)
}
if same_slot(theirs, base) {
return ours.map(clone_json)
}
let base_object : Map[String, Json]? = match base {
Some(Object(o)) => Some(o)
None => Some(Map([]))
_ => None
}
match (base_object, ours, theirs) {
(Some(b), Some(Object(o)), Some(Object(t))) => {
let keys = keys_union(b, o)
for k, _ in t {
if !keys.contains(k) {
keys.push(k)
}
}
keys.sort_by((a, b) => a.to_array().lexical_compare(b.to_array()))
let result : Map[String, Json] = Map([])
for k in keys {
match
merge_at(
b.get(k),
o.get(k),
t.get(k),
append_path(path, k),
conflicts,
) {
Some(v) => result[k] = v
None => ()
}
}
Some(Json::object(result))
}
_ => {
conflicts.push({
path,
base: base.map(clone_json),
ours: ours.map(clone_json),
theirs: theirs.map(clone_json),
})
base.map(clone_json)
}
}
}
///|
/// Stable report distinguishes deleted fields from explicit null values.
pub fn merge_json(result : MergeResult) -> Json {
fn slot(value : Json?) -> Json {
let o : Map[String, Json] = Map([])
o["present"] = Json::boolean(value is Some(_))
match value {
Some(v) => o["value"] = clone_json(v)
None => ()
}
Json::object(o)
}
let o : Map[String, Json] = Map([])
o["clean"] = Json::boolean(result.conflicts.is_empty())
o["value"] = clone_json(result.value)
o["conflicts"] = Json::array(
result.conflicts.map(fn(c) {
Json::object(
Map([
("path", Json::string(c.path)),
("base", slot(c.base)),
("ours", slot(c.ours)),
("theirs", slot(c.theirs)),
]),
)
}),
)
Json::object(o)
}