use crate::{Change, Diff, Error, Path};
use serde::{Deserialize, Serialize};
use serde_value::Value;
use std::collections::BTreeMap;
pub fn apply<T>(base: &T, diff: &Diff) -> Result<T, Error>
where
T: Serialize + for<'de> Deserialize<'de>,
{
let mut value = serde_value::to_value(base)?;
let mut changes: Vec<_> = diff.changes().iter().collect();
changes.sort_by(|(path_a, change_a), (path_b, change_b)| {
use std::cmp::Ordering;
match (change_a, change_b) {
(Change::Removed(_), Change::Removed(_)) => {
match (path_a.last_index(), path_b.last_index()) {
(Some(idx_a), Some(idx_b)) => idx_b.cmp(&idx_a),
_ => path_b.as_str().cmp(path_a.as_str()),
}
}
(Change::Removed(_), _) => Ordering::Greater,
(_, Change::Removed(_)) => Ordering::Less,
_ => path_a.as_str().cmp(path_b.as_str()),
}
});
for (path, change) in changes {
apply_change(&mut value, path, change)?;
}
Ok(T::deserialize(value)?)
}
fn apply_change(root: &mut Value, path: &Path, change: &Change) -> Result<(), Error> {
match change {
Change::Added(new_value) => {
set_at_path(root, path, new_value.clone())?;
}
Change::Removed(_) => {
remove_at_path(root, path)?;
}
Change::Modified { to, .. } => {
set_at_path(root, path, to.clone())?;
}
Change::Elided { .. } => {
return Err(Error::Config(format!(
"Cannot apply elided change at path: {}",
path.as_str()
)));
}
}
Ok(())
}
fn set_at_path(root: &mut Value, path: &Path, new_value: Value) -> Result<(), Error> {
let path_str = path.as_str();
if path_str.is_empty() {
*root = new_value;
return Ok(());
}
let segments = parse_path(path_str);
let (parent_segments, last_segment) = segments.split_at(segments.len() - 1);
let parent = navigate_to_mut(root, parent_segments)?;
match last_segment[0] {
PathSegment::Field(ref field_name) => {
if let Value::Map(ref mut map) = parent {
let key = Value::String(field_name.clone());
map.insert(key, new_value);
Ok(())
} else {
Err(Error::InvalidPath(format!(
"Cannot set field '{}' on non-map value",
field_name
)))
}
}
PathSegment::Index(idx) => {
if let Value::Seq(ref mut seq) = parent {
if idx < seq.len() {
seq[idx] = new_value;
Ok(())
} else if idx == seq.len() {
seq.push(new_value);
Ok(())
} else {
Err(Error::InvalidPath(format!(
"Index {} out of bounds for sequence of length {}",
idx,
seq.len()
)))
}
} else {
Err(Error::InvalidPath(format!(
"Cannot index non-sequence value at index {}",
idx
)))
}
}
}
}
fn remove_at_path(root: &mut Value, path: &Path) -> Result<(), Error> {
let path_str = path.as_str();
if path_str.is_empty() {
return Err(Error::InvalidPath("Cannot remove root value".to_string()));
}
let segments = parse_path(path_str);
let (parent_segments, last_segment) = segments.split_at(segments.len() - 1);
let parent = navigate_to_mut(root, parent_segments)?;
match last_segment[0] {
PathSegment::Field(ref field_name) => {
if let Value::Map(ref mut map) = parent {
let key = Value::String(field_name.clone());
map.remove(&key);
Ok(())
} else {
Err(Error::InvalidPath(format!(
"Cannot remove field '{}' from non-map value",
field_name
)))
}
}
PathSegment::Index(idx) => {
if let Value::Seq(ref mut seq) = parent {
if idx < seq.len() {
seq.remove(idx);
Ok(())
} else {
Err(Error::InvalidPath(format!(
"Index {} out of bounds for sequence of length {}",
idx,
seq.len()
)))
}
} else {
Err(Error::InvalidPath(format!(
"Cannot remove index {} from non-sequence value",
idx
)))
}
}
}
}
fn navigate_to_mut<'a>(
mut current: &'a mut Value,
segments: &[PathSegment],
) -> Result<&'a mut Value, Error> {
for segment in segments {
current = match segment {
PathSegment::Field(field_name) => {
if let Value::Map(ref mut map) = current {
let key = Value::String(field_name.clone());
map.entry(key).or_insert(Value::Map(BTreeMap::new()))
} else {
return Err(Error::InvalidPath(format!(
"Cannot navigate through field '{}' on non-map value",
field_name
)));
}
}
PathSegment::Index(idx) => {
if let Value::Seq(ref mut seq) = current {
if *idx < seq.len() {
&mut seq[*idx]
} else {
return Err(Error::InvalidPath(format!(
"Index {} out of bounds for sequence of length {}",
idx,
seq.len()
)));
}
} else {
return Err(Error::InvalidPath(format!(
"Cannot navigate through index {} on non-sequence value",
idx
)));
}
}
};
}
Ok(current)
}
#[derive(Debug, Clone, PartialEq)]
enum PathSegment {
Field(String),
Index(usize),
}
fn parse_path(path: &str) -> Vec<PathSegment> {
let mut segments = Vec::new();
let mut current = String::new();
let mut chars = path.chars().peekable();
while let Some(ch) = chars.next() {
match ch {
'.' => {
if !current.is_empty() {
segments.push(PathSegment::Field(current.clone()));
current.clear();
}
}
'[' => {
if !current.is_empty() {
segments.push(PathSegment::Field(current.clone()));
current.clear();
}
let mut index_str = String::new();
while let Some(&next_ch) = chars.peek() {
if next_ch == ']' {
chars.next();
break;
}
index_str.push(chars.next().unwrap());
}
if let Ok(idx) = index_str.parse::<usize>() {
segments.push(PathSegment::Index(idx));
}
}
_ => {
current.push(ch);
}
}
}
if !current.is_empty() {
segments.push(PathSegment::Field(current));
}
segments
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_path_simple_field() {
let segments = parse_path("name");
assert_eq!(segments, vec![PathSegment::Field("name".into())]);
}
#[test]
fn test_parse_path_nested_field() {
let segments = parse_path("user.name");
assert_eq!(
segments,
vec![
PathSegment::Field("user".into()),
PathSegment::Field("name".into())
]
);
}
#[test]
fn test_parse_path_array_index() {
let segments = parse_path("[0]");
assert_eq!(segments, vec![PathSegment::Index(0)]);
}
#[test]
fn test_parse_path_field_and_index() {
let segments = parse_path("users[0].name");
assert_eq!(
segments,
vec![
PathSegment::Field("users".into()),
PathSegment::Index(0),
PathSegment::Field("name".into())
]
);
}
#[test]
fn test_parse_path_multiple_indices() {
let segments = parse_path("data[0][1]");
assert_eq!(
segments,
vec![
PathSegment::Field("data".into()),
PathSegment::Index(0),
PathSegment::Index(1)
]
);
}
}