use super::expand::asset_name_str;
pub fn merge_args(template: &serde_json::Value, patch: &serde_json::Value) -> serde_json::Value {
match (template, patch) {
(serde_json::Value::Object(t), serde_json::Value::Object(p)) => {
let mut out = t.clone();
for (key, patch_val) in p {
let merged = match out.get(key) {
Some(existing) => merge_args(existing, patch_val),
None => patch_val.clone(),
};
out.insert(key.clone(), merged);
}
serde_json::Value::Object(out)
}
_ => patch.clone(),
}
}
pub(crate) fn merge_into_authored(
assets: &mut [serde_json::Value],
name: &str,
template_args: &serde_json::Value,
) {
let Some(line) = assets.iter_mut().find(|v| asset_name_str(v) == name) else {
return;
};
let authored = line.get("args").cloned().unwrap_or(serde_json::json!({}));
line["args"] = merge_args(template_args, &authored);
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn patch_keys_win_and_omitted_keys_keep_template_values() {
let template = json!({"a": 1, "b": [1, 2], "c": "x"});
let patch = json!({"b": [9]});
assert_eq!(
merge_args(&template, &patch),
json!({"a": 1, "b": [9], "c": "x"})
);
}
#[test]
fn nested_objects_merge_recursively() {
let template = json!({"controller": {"move_speed": 4.0, "free_fly": false}, "far": 100});
let patch = json!({"controller": {"move_speed": 9.0}});
assert_eq!(
merge_args(&template, &patch),
json!({"controller": {"move_speed": 9.0, "free_fly": false}, "far": 100})
);
}
#[test]
fn an_explicit_null_overrides_a_template_object() {
let template = json!({"controller": {"move_speed": 4.0}});
let patch = json!({"controller": null});
assert_eq!(merge_args(&template, &patch), json!({"controller": null}));
}
#[test]
fn arrays_replace_wholesale() {
let template = json!({"waves": [{"amplitude": 1.0}, {"amplitude": 2.0}]});
let patch = json!({"waves": [{"amplitude": 5.0}]});
assert_eq!(
merge_args(&template, &patch),
json!({"waves": [{"amplitude": 5.0}]})
);
}
#[test]
fn patch_may_add_keys_the_template_lacks() {
let template = json!({"a": 1});
let patch = json!({"b": {"c": 2}});
assert_eq!(
merge_args(&template, &patch),
json!({"a": 1, "b": {"c": 2}})
);
}
#[test]
fn merging_a_merged_result_is_idempotent() {
let template = json!({"a": 1, "b": {"c": 2, "d": 3}});
let patch = json!({"b": {"c": 9}});
let once = merge_args(&template, &patch);
assert_eq!(merge_args(&template, &once), once);
}
#[test]
fn merge_into_authored_patches_the_named_line_only() {
let mut assets = vec![
json!({"name": "other", "type": "Prop", "args": {"scale": [2, 2, 2]}}),
json!({"name": "inst_table", "type": "Prop", "args": {"position": [5, 0, 0]}}),
];
let template = json!({"position": [1, 0, 0], "mesh": "box"});
merge_into_authored(&mut assets, "inst_table", &template);
assert_eq!(
assets[1]["args"],
json!({"position": [5, 0, 0], "mesh": "box"})
);
assert_eq!(assets[0]["args"], json!({"scale": [2, 2, 2]}));
}
#[test]
fn merge_into_authored_tolerates_a_line_without_args() {
let mut assets = vec![json!({"name": "x", "type": "Font"})];
merge_into_authored(&mut assets, "x", &json!({"size_px": 48}));
assert_eq!(assets[0]["args"], json!({"size_px": 48}));
}
}