pub(crate) fn apply_yaml_mapping_diff(
mapping: &yaml_edit::Mapping,
old: &serde_json::Value,
new: &serde_json::Value,
) -> anyhow::Result<bool> {
if old == new {
return Ok(true);
}
let (Some(old_map), Some(new_map)) = (old.as_object(), new.as_object()) else {
return Ok(true);
};
let mut all_applied = true;
let removed: Vec<String> = old_map
.keys()
.filter(|k| !new_map.contains_key(k.as_str()))
.cloned()
.collect();
for k in &removed {
mapping.remove(k.as_str());
}
for (key, new_val) in new_map {
if let Some(old_val) = old_map.get(key) {
if old_val == new_val {
continue;
}
match (old_val, new_val) {
(serde_json::Value::Object(_), serde_json::Value::Object(_)) => {
if let Some(child) = mapping.get_mapping(key.as_str()) {
if !apply_yaml_mapping_diff(&child, old_val, new_val)? {
all_applied = false;
}
} else {
mapping.set(key.as_str(), json_to_yaml_mapping(new_val)?);
}
}
(serde_json::Value::Array(old_arr), serde_json::Value::Array(new_arr)) => {
if let Some(seq) = mapping.get_sequence(key.as_str()) {
if old_arr.len() == new_arr.len() {
if !apply_yaml_sequence_diff(&seq, old_arr, new_arr)? {
all_applied = false;
}
} else if !apply_yaml_sequence_resize(
&seq,
old_arr,
new_arr,
mapping,
key.as_str(),
new_val,
) {
all_applied = false;
}
} else {
mapping.set(key.as_str(), json_to_yaml_node(new_val)?);
}
}
_ => {
mapping.set(key.as_str(), json_to_yaml_node(new_val)?);
}
}
} else {
if new_val.is_object() {
let empty = yaml_edit::Mapping::new();
mapping.set(key.as_str(), &empty);
if let Some(nested) = mapping.get_mapping(key.as_str()) {
if let Some(obj) = new_val.as_object() {
for (k, v) in obj {
nested.set(k.as_str(), json_to_yaml_node(v)?);
}
}
} else {
mapping.set(key.as_str(), json_to_yaml_mapping(new_val)?);
}
} else {
mapping.set(key.as_str(), json_to_yaml_node(new_val)?);
}
}
}
Ok(all_applied)
}
pub(super) fn apply_yaml_sequence_diff(
seq: &yaml_edit::Sequence,
old_arr: &[serde_json::Value],
new_arr: &[serde_json::Value],
) -> anyhow::Result<bool> {
let mut all_applied = true;
for (i, (o, n)) in old_arr.iter().zip(new_arr.iter()).enumerate() {
if o == n {
continue;
}
match (o, n) {
(serde_json::Value::Object(_), serde_json::Value::Object(_)) => {
if let Some(node) = seq.get(i)
&& let Some(child_mapping) = node.as_mapping()
{
if !apply_yaml_mapping_diff(child_mapping, o, n)? {
all_applied = false;
}
continue;
}
seq.set(i, json_to_yaml_node(n)?);
}
_ => {
seq.set(i, json_to_yaml_node(n)?);
}
}
}
Ok(all_applied)
}
fn apply_yaml_sequence_resize(
seq: &yaml_edit::Sequence,
old_arr: &[serde_json::Value],
new_arr: &[serde_json::Value],
_mapping: &yaml_edit::Mapping,
_key: &str,
_new_val: &serde_json::Value,
) -> bool {
if new_arr.len() < old_arr.len() && try_remove_subsequence(seq, old_arr, new_arr) {
return true;
}
false
}
pub(super) fn try_remove_subsequence(
seq: &yaml_edit::Sequence,
old_arr: &[serde_json::Value],
new_arr: &[serde_json::Value],
) -> bool {
let new_len = new_arr.len();
let mut remove_indices = Vec::new();
let mut ni = 0;
for (oi, old_item) in old_arr.iter().enumerate() {
if ni < new_len && *old_item == new_arr[ni] {
ni += 1;
} else {
remove_indices.push(oi);
}
}
if ni != new_len {
return false;
}
for &idx in remove_indices.iter().rev() {
seq.remove(idx);
}
true
}
fn json_to_yaml_node(val: &serde_json::Value) -> anyhow::Result<yaml_edit::YamlNode> {
use std::str::FromStr;
let wrapper = serde_json::json!({ "__v__": val });
let yaml_text = serde_yaml_ng::to_string(&wrapper)
.map_err(|e| anyhow::anyhow!("YAML serialization failed: {e}"))?;
let doc = yaml_edit::Document::from_str(&yaml_text)
.map_err(|e| anyhow::anyhow!("YAML CST re-parse failed: {e}"))?;
doc.as_mapping()
.and_then(|m| m.get("__v__"))
.ok_or_else(|| anyhow::anyhow!("YAML CST wrapper key missing"))
}
fn json_to_yaml_mapping(val: &serde_json::Value) -> anyhow::Result<yaml_edit::Mapping> {
let mapping = yaml_edit::Mapping::new();
if let Some(obj) = val.as_object() {
for (k, v) in obj {
mapping.set(k.as_str(), json_to_yaml_node(v)?);
}
}
Ok(mapping)
}