diffo 0.2.0

Semantic diffing for Rust structs via serde
Documentation
use diffo::*;
use serde::{Deserialize, Serialize};

#[test]
fn test_apply_simple_field_modification() {
    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct User {
        name: String,
        age: u32,
    }

    let old = User {
        name: "Alice".into(),
        age: 30,
    };

    let new = User {
        name: "Alice".into(),
        age: 31,
    };

    let d = diff(&old, &new).unwrap();
    let result: User = apply(&old, &d).unwrap();

    assert_eq!(result, new);
    assert_eq!(result.age, 31);
}

#[test]
fn test_apply_multiple_field_changes() {
    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct User {
        name: String,
        email: String,
        age: u32,
    }

    let old = User {
        name: "Alice".into(),
        email: "alice@old.com".into(),
        age: 30,
    };

    let new = User {
        name: "Alicia".into(),
        email: "alicia@new.com".into(),
        age: 31,
    };

    let d = diff(&old, &new).unwrap();
    let result: User = apply(&old, &d).unwrap();

    assert_eq!(result, new);
}

#[test]
fn test_apply_nested_struct() {
    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct Address {
        city: String,
        zip: String,
    }

    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct User {
        name: String,
        address: Address,
    }

    let old = User {
        name: "Alice".into(),
        address: Address {
            city: "NYC".into(),
            zip: "10001".into(),
        },
    };

    let new = User {
        name: "Alice".into(),
        address: Address {
            city: "Boston".into(),
            zip: "02101".into(),
        },
    };

    let d = diff(&old, &new).unwrap();
    let result: User = apply(&old, &d).unwrap();

    assert_eq!(result, new);
    assert_eq!(result.address.city, "Boston");
}

#[test]
fn test_apply_array_modification() {
    let old = vec![1, 2, 3];
    let new = vec![1, 2, 4];

    let d = diff(&old, &new).unwrap();
    let result: Vec<i32> = apply(&old, &d).unwrap();

    assert_eq!(result, new);
}

#[test]
fn test_apply_array_addition() {
    let old = vec![1, 2, 3];
    let new = vec![1, 2, 3, 4];

    let d = diff(&old, &new).unwrap();
    let result: Vec<i32> = apply(&old, &d).unwrap();

    assert_eq!(result, new);
}

#[test]
fn test_apply_array_removal() {
    let old = vec![1, 2, 3, 4];
    let new = vec![1, 2, 3];

    let d = diff(&old, &new).unwrap();
    let result: Vec<i32> = apply(&old, &d).unwrap();

    assert_eq!(result, new);
}

#[test]
fn test_apply_struct_with_array() {
    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct Team {
        name: String,
        members: Vec<String>,
    }

    let old = Team {
        name: "Dev Team".into(),
        members: vec!["Alice".into(), "Bob".into()],
    };

    let new = Team {
        name: "Dev Team".into(),
        members: vec!["Alice".into(), "Bob".into(), "Charlie".into()],
    };

    let d = diff(&old, &new).unwrap();
    let result: Team = apply(&old, &d).unwrap();

    assert_eq!(result, new);
    assert_eq!(result.members.len(), 3);
}

#[test]
fn test_apply_roundtrip() {
    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct Data {
        x: i32,
        y: String,
        z: Vec<f64>,
    }

    let old = Data {
        x: 10,
        y: "hello".into(),
        z: vec![1.0, 2.0],
    };

    let new = Data {
        x: 20,
        y: "world".into(),
        z: vec![1.0, 2.0, 3.0],
    };

    let d = diff(&old, &new).unwrap();
    let result: Data = apply(&old, &d).unwrap();

    // Roundtrip: apply(old, diff(old, new)) == new
    assert_eq!(result, new);
}

#[test]
fn test_apply_no_changes() {
    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct User {
        name: String,
    }

    let old = User {
        name: "Alice".into(),
    };

    let new = User {
        name: "Alice".into(),
    };

    let d = diff(&old, &new).unwrap();
    assert!(d.is_empty());

    let result: User = apply(&old, &d).unwrap();
    assert_eq!(result, old);
}

#[test]
fn test_apply_with_option_some_to_none() {
    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct Data {
        value: Option<String>,
    }

    let old = Data {
        value: Some("hello".into()),
    };

    let new = Data { value: None };

    let d = diff(&old, &new).unwrap();
    let result: Data = apply(&old, &d).unwrap();

    assert_eq!(result, new);
    assert!(result.value.is_none());
}

#[test]
fn test_apply_with_option_none_to_some() {
    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct Data {
        value: Option<String>,
    }

    let old = Data { value: None };

    let new = Data {
        value: Some("hello".into()),
    };

    let d = diff(&old, &new).unwrap();
    let result: Data = apply(&old, &d).unwrap();

    assert_eq!(result, new);
    assert_eq!(result.value, Some("hello".into()));
}

#[test]
fn test_apply_deeply_nested() {
    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct Inner {
        value: i32,
    }

    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct Middle {
        inner: Inner,
    }

    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct Outer {
        middle: Middle,
    }

    let old = Outer {
        middle: Middle {
            inner: Inner { value: 10 },
        },
    };

    let new = Outer {
        middle: Middle {
            inner: Inner { value: 20 },
        },
    };

    let d = diff(&old, &new).unwrap();
    let result: Outer = apply(&old, &d).unwrap();

    assert_eq!(result, new);
    assert_eq!(result.middle.inner.value, 20);
}

#[test]
fn test_apply_array_of_structs() {
    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct Item {
        id: u64,
        name: String,
    }

    let old = vec![
        Item {
            id: 1,
            name: "Alice".into(),
        },
        Item {
            id: 2,
            name: "Bob".into(),
        },
    ];

    let new = vec![
        Item {
            id: 1,
            name: "Alicia".into(), // Name changed
        },
        Item {
            id: 2,
            name: "Bob".into(),
        },
    ];

    let d = diff(&old, &new).unwrap();
    let result: Vec<Item> = apply(&old, &d).unwrap();

    assert_eq!(result, new);
    assert_eq!(result[0].name, "Alicia");
}

#[test]
fn test_apply_empty_to_non_empty() {
    let old: Vec<i32> = vec![];
    let new: Vec<i32> = vec![1, 2, 3];

    let d = diff(&old, &new).unwrap();
    let result: Vec<i32> = apply(&old, &d).unwrap();

    assert_eq!(result, new);
}

#[test]
fn test_apply_non_empty_to_empty() {
    let old: Vec<i32> = vec![1, 2, 3];
    let new: Vec<i32> = vec![];

    let d = diff(&old, &new).unwrap();
    let result: Vec<i32> = apply(&old, &d).unwrap();

    assert_eq!(result, new);
}