diffo 0.2.0

Semantic diffing for Rust structs via serde
Documentation
use super::Formatter;
use crate::{Change, Diff, FormatError};
use serde_json::json;

/// JSON Patch (RFC 6902) formatter.
#[derive(Debug)]
pub struct JsonPatchFormatter;

impl JsonPatchFormatter {
    /// Create a new JSON Patch formatter.
    pub fn new() -> Self {
        Self
    }
}

impl Default for JsonPatchFormatter {
    fn default() -> Self {
        Self::new()
    }
}

impl Formatter for JsonPatchFormatter {
    fn format(&self, diff: &Diff) -> Result<String, FormatError> {
        let mut patches = vec![];

        for (path, change) in diff.changes() {
            let json_path = path.to_json_pointer();

            match change {
                Change::Added(val) => {
                    patches.push(json!({
                        "op": "add",
                        "path": json_path,
                        "value": val,
                    }));
                }
                Change::Removed(_) => {
                    patches.push(json!({
                        "op": "remove",
                        "path": json_path,
                    }));
                }
                Change::Modified { to, .. } => {
                    patches.push(json!({
                        "op": "replace",
                        "path": json_path,
                        "value": to,
                    }));
                }
                Change::Elided { .. } => {
                    // Skip elided changes in JSON Patch
                }
            }
        }

        serde_json::to_string_pretty(&patches).map_err(FormatError::Json)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Path;
    use serde_value::Value;

    #[test]
    fn test_json_patch_add() {
        let mut diff = Diff::new();
        diff.insert(Path::root().field("x"), Change::Added(Value::I64(42)));

        let formatter = JsonPatchFormatter::new();
        let output = formatter.format(&diff).unwrap();
        assert!(output.contains("\"op\": \"add\""));
        assert!(output.contains("\"/x\""));
    }
}