├── .gitignore ├── Cargo.toml ├── LICENSE ├── src ├── utils.rs ├── lib.rs ├── tests.rs └── impls.rs ├── README.md ├── tests └── arbitrary.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "diff-struct" 3 | description = "A trait for diffing and applying diffs to types" 4 | version = "0.5.3" 5 | authors = ["BenHall-7 "] 6 | repository = "https://github.com/benhall-7/diff-struct" 7 | license = "MIT" 8 | edition = "2018" 9 | 10 | [lib] 11 | name = "diff" 12 | path = "src/lib.rs" 13 | 14 | [features] 15 | default = ["impl_num"] 16 | impl_num = ["num"] 17 | 18 | [dependencies] 19 | diff_derive = "0.2.3" 20 | serde = { version = "1", features = ["derive"] } 21 | num = { version = "0.4.0", optional = true } 22 | 23 | [dev-dependencies] 24 | quickcheck = "0.8" 25 | quickcheck_derive = "0.3" 26 | quickcheck_macros = "0.8" 27 | rand = "0.6" 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ben Hall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::{max, min}; 2 | 3 | /// Finds the closest-to-starting element present in both slices. 4 | /// Returns whether an equal element was found in each slice, the index of the element in a, the index in b. 5 | /// If no match was found between a and b, returns the indeces of the end of each slice, respectively. 6 | pub fn find_match(a: &[T], b: &[T]) -> (bool, usize, usize) { 7 | let (mut x, mut y) = (0, 0); 8 | let mut found_match = false; 9 | if !a.is_empty() && !b.is_empty() { 10 | let max_depth = a.len() + b.len() - 1; 11 | for depth in 0..max_depth { 12 | let x_lower_bound = max(depth as isize - b.len() as isize + 1, 0) as usize; 13 | x = min(depth, a.len() - 1); 14 | loop { 15 | y = depth - x; 16 | if a[x] == b[y] { 17 | found_match = true; 18 | break; 19 | } 20 | if x > x_lower_bound { 21 | x -= 1; 22 | } else { 23 | break; 24 | } 25 | } 26 | 27 | if found_match { 28 | break; 29 | } 30 | } 31 | } 32 | if !found_match { 33 | x = a.len(); 34 | y = b.len(); 35 | } 36 | (found_match, x, y) 37 | } 38 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod impls; 2 | #[cfg(test)] 3 | mod tests; 4 | pub mod utils; 5 | 6 | pub use diff_derive::Diff; 7 | pub use impls::*; 8 | 9 | /// A trait to diff and apply diffs between two structs 10 | /// The derive macro can be used on structs when all fields of the struct implement Diff 11 | /// Implementations are provided for bools, numeric types, Option types, and HashMaps 12 | pub trait Diff: Sized { 13 | /// The type associated with the structs' difference 14 | type Repr; 15 | 16 | /// Produces a diff between two structs 17 | fn diff(&self, other: &Self) -> Self::Repr; 18 | 19 | /// Produces a diff between two structs, using an external diffing implementation 20 | fn diff_custom>(&self, other: &Self, visitor: &D) -> D::Repr { 21 | visitor.diff(self, other) 22 | } 23 | 24 | /// Applies the diff directly to the struct 25 | fn apply(&mut self, diff: &Self::Repr); 26 | 27 | /// Applies the diff directly to the struct, using an external diffing implementation 28 | fn apply_custom>(&mut self, diff: &D::Repr, visitor: &D) { 29 | visitor.apply(self, diff) 30 | } 31 | 32 | /// Applies the diff to the struct and produces a new struct 33 | fn apply_new(&self, diff: &Self::Repr) -> Self { 34 | let mut new = Self::identity(); 35 | new.apply(&new.diff(self)); 36 | new.apply(diff); 37 | new 38 | } 39 | 40 | /// Applies the diff to the struct and produces a new struct, using an external diffing implementation 41 | fn apply_new_custom>(&self, diff: &D::Repr, visitor: &D) -> Self { 42 | let mut new = Self::identity(); 43 | new.apply_custom(&new.diff_custom(self, visitor), visitor); 44 | new.apply_custom(diff, visitor); 45 | new 46 | } 47 | 48 | /// The identity element of the struct 49 | /// ``` 50 | /// use diff::Diff; 51 | /// let s = 42; 52 | /// let i = ::identity(); 53 | /// assert_eq!(i.apply_new(&i.diff(&s)), s); 54 | /// ``` 55 | /// or mathematically speaking, `i + (s - i) = s` 56 | fn identity() -> Self; 57 | } 58 | 59 | /// A trait allowing a custom struct to handle the diffing implementation for a type 60 | pub trait Differ { 61 | type Repr; 62 | 63 | fn diff(&self, a: &T, b: &T) -> Self::Repr; 64 | 65 | fn apply(&self, a: &mut T, b: &Self::Repr); 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Diff 2 | 3 | A Rust trait for diffing and applying diffs between data structures. Features a derive macro. 4 | 5 | Abstractly speaking, a 'diff' between two structs `A` and `B` can be considered the change necessary to make to `A` to produce `B`. Succinctly, the pattern can be written as `A -> B = D and A <- D = B` 6 | 7 | Diff is automatically derived on several common base types, include: integers (i8, u32, etc), floats, non-zero integers, booleans, char, String, &str, and PathBuf. 8 | 9 | Diff is also derived on several container types, such as: Option, Box, Rc, Arc, HashMap, BTreeMap, HashSet, BTreeSet, tuples (up to length 18), arrays, and Vec's. 10 | 11 | Container types are subject to some type restrictions in order to diff. The inner type T always has to implement Diff. Depending on the container type, other restrictions apply, such as Debug, Clone, PartialEq. These restrictions may also apply to the diff type (T::Repr). The implementation of diffing Vec's is non-standard. It is faster and simpler than Myer's algorithm, but more error-prone on lists with many nearby duplicate elements. 12 | 13 | ## Derive macro 14 | 15 | The derive macro can be used on tuple or field structs to produce a new struct representing their difference. Note that this new struct is not considered to be the same type as the base struct, because certain data types cannot represent their own diff (bools, maps, lists, etc). Currently only 1 helper attribute is supported, `attr` which allows passing attributes (doc comments/derives) to the generated struct. The generated struct name is by default the name of the base struct plus "Diff". Example: 16 | 17 | ```rust 18 | #[derive(Debug, Default, PartialEq, Diff)] 19 | // this will apply the specified derives on the generated 'ProjectMetaDiff' struct 20 | #[diff(attr( 21 | #[derive(Debug, PartialEq)] 22 | ))] 23 | pub struct ProjectMeta { 24 | contributors: Vec, 25 | combined_work_hours: usize, 26 | } 27 | 28 | #[test] 29 | fn test_apply() { 30 | let mut base = ProjectMeta::default(); 31 | let contribution_a = ProjectMeta { 32 | contributors: vec!["Alice".into()], 33 | combined_work_hours: 3, 34 | }; 35 | let contribution_b = ProjectMeta { 36 | contributors: vec!["Bob".into(), "Candice".into()], 37 | combined_work_hours: 10, 38 | }; 39 | let expected = ProjectMeta { 40 | contributors: vec!["Bob".into(), "Candice".into(), "Alice".into()], 41 | combined_work_hours: 13, 42 | }; 43 | let diff_a = base.diff(&contribution_a); 44 | let diff_b = base.diff(&contribution_b); 45 | base.apply(&diff_a); 46 | base.apply(&diff_b); 47 | assert_eq!(base, expected); 48 | } 49 | ``` 50 | 51 | ## In action 52 | 53 | * [motion_lib](https://github.com/ultimate-research/motion_lib): a modding tool for the proprietary motion_lib.bin filetype in SSBU. Utilizing this crate, the project features functions for producing file differences as serialized yaml. With this functionality, users can determine quickly what changes were made to the regular file across game updates, and apply those changes to their modded files to keep them up to date. Vise versa, they can apply their modded file's diffs to the new version of the file to achieve the same result. 54 | 55 | ## In theory 56 | 57 | An object implementing Diff will translate well into human-readable diffs of arbitrary data types, avoiding the problems and false positives of diffing or patching plain-text versions of the serialized structs. 58 | -------------------------------------------------------------------------------- /tests/arbitrary.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unused_unit)] 2 | 3 | use diff::Diff; 4 | use quickcheck::quickcheck; 5 | use quickcheck_derive::Arbitrary; 6 | use std::collections::{BTreeMap, HashMap}; 7 | use std::sync::Arc; 8 | 9 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 10 | pub struct Unit; 11 | 12 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 13 | pub struct Basic { 14 | pub items: HashMap>, 15 | pub items_b: BTreeMap>, 16 | pub tuple: (u32, u16), 17 | pub arc: Arc, 18 | pub unit: Unit, 19 | } 20 | 21 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 22 | pub enum Enum { 23 | VarUnit, 24 | VarNamed { a: u32, b: u32 }, 25 | VarUnnamed(u32, Basic), 26 | } 27 | 28 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 29 | pub struct NamedGenerics { 30 | pub a: A, 31 | pub b: B, 32 | } 33 | 34 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 35 | pub struct UnnamedGenerics(A, B); 36 | 37 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 38 | pub enum EnumGenerics 39 | where 40 | A: PartialEq, 41 | B: PartialEq, 42 | { 43 | Named { a: A, b: B }, 44 | Unnamed(A, B), 45 | } 46 | 47 | pub trait Bound1 {} 48 | pub trait Bound2 {} 49 | 50 | impl Bound1 for u32 {} 51 | impl Bound2 for u32 {} 52 | 53 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 54 | pub struct NamedGenericsBoundsInternal { 55 | pub a: A, 56 | pub b: B, 57 | } 58 | 59 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 60 | pub struct NamedGenericsBoundsExternal 61 | where 62 | A: Bound1 + Bound2, 63 | { 64 | pub a: A, 65 | pub b: B, 66 | } 67 | 68 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 69 | pub struct NamedGenericsBoundsMixed 70 | where 71 | A: Bound2, 72 | { 73 | pub a: A, 74 | pub b: B, 75 | } 76 | 77 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 78 | pub struct UnnamedGenericsBoundsInternal(A, B); 79 | 80 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 81 | pub struct UnnamedGenericsBoundsExternal(A, B) 82 | where 83 | A: Bound1 + Bound2; 84 | 85 | #[derive(Clone, Arbitrary, Diff, Eq, PartialEq, Debug)] 86 | pub struct UnnamedGenericsBoundsMixed(A, B) 87 | where 88 | A: Bound2; 89 | 90 | fn tester(mut a: T, b: T) -> bool 91 | where 92 | T: Diff + Eq, 93 | { 94 | let diff = a.diff(&b); 95 | a.apply(&diff); 96 | a == b 97 | } 98 | 99 | #[test] 100 | fn test_basic() { 101 | quickcheck(tester as fn(Basic, Basic) -> bool); 102 | } 103 | 104 | #[test] 105 | fn test_named_generics() { 106 | quickcheck(tester as fn(NamedGenerics, NamedGenerics) -> bool); 107 | } 108 | 109 | #[test] 110 | fn test_unnamed_generics() { 111 | quickcheck(tester as fn(UnnamedGenerics, UnnamedGenerics) -> bool); 112 | } 113 | 114 | #[test] 115 | fn test_enum_generics() { 116 | quickcheck(tester as fn(EnumGenerics, EnumGenerics) -> bool); 117 | } 118 | 119 | #[test] 120 | fn test_enum() { 121 | quickcheck(tester as fn(Enum, Enum) -> bool); 122 | } 123 | 124 | #[test] 125 | fn test_named_generics_bounds_internal() { 126 | quickcheck( 127 | tester 128 | as fn( 129 | NamedGenericsBoundsInternal, 130 | NamedGenericsBoundsInternal, 131 | ) -> bool, 132 | ); 133 | } 134 | 135 | #[test] 136 | fn test_named_generics_bounds_external() { 137 | quickcheck( 138 | tester 139 | as fn( 140 | NamedGenericsBoundsExternal, 141 | NamedGenericsBoundsExternal, 142 | ) -> bool, 143 | ); 144 | } 145 | 146 | #[test] 147 | fn test_named_generics_bounds_mixed() { 148 | quickcheck( 149 | tester 150 | as fn(NamedGenericsBoundsMixed, NamedGenericsBoundsMixed) -> bool, 151 | ); 152 | } 153 | 154 | #[test] 155 | fn test_unnamed_generics_bounds_internal() { 156 | quickcheck( 157 | tester 158 | as fn( 159 | UnnamedGenericsBoundsInternal, 160 | UnnamedGenericsBoundsInternal, 161 | ) -> bool, 162 | ); 163 | } 164 | 165 | #[test] 166 | fn test_unnamed_generics_bounds_external() { 167 | quickcheck( 168 | tester 169 | as fn( 170 | UnnamedGenericsBoundsExternal, 171 | UnnamedGenericsBoundsExternal, 172 | ) -> bool, 173 | ); 174 | } 175 | 176 | #[test] 177 | fn test_unnamed_generics_bounds_mixed() { 178 | quickcheck( 179 | tester 180 | as fn( 181 | UnnamedGenericsBoundsMixed, 182 | UnnamedGenericsBoundsMixed, 183 | ) -> bool, 184 | ); 185 | } 186 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use std::borrow::Cow; 4 | use std::collections::{HashMap, HashSet}; 5 | use std::fmt::Debug; 6 | use std::hash::Hash; 7 | use std::marker::PhantomData; 8 | use std::path::PathBuf; 9 | use std::rc::Rc; 10 | 11 | fn identity_test(s: D) { 12 | assert_eq!(D::identity().apply_new(&D::identity().diff(&s)), s); 13 | } 14 | 15 | fn generate_map(parts: Vec<(K, V)>) -> HashMap { 16 | parts.into_iter().collect::>() 17 | } 18 | 19 | fn generate_set(parts: Vec) -> HashSet { 20 | parts.into_iter().collect::>() 21 | } 22 | 23 | #[test] 24 | fn numeric_diffs() { 25 | identity_test(true); 26 | identity_test(42_u8); 27 | identity_test(42_i8); 28 | identity_test(42_u16); 29 | identity_test(42_i16); 30 | identity_test(42_u32); 31 | identity_test(42_i32); 32 | identity_test(42.0_f32); 33 | identity_test(42.0_f64); 34 | } 35 | 36 | #[test] 37 | fn test_char_string() { 38 | identity_test('b'); 39 | identity_test(String::from("42")); 40 | assert_eq!('b'.diff(&'c'), Some('c')); 41 | assert_eq!('b'.diff(&'b'), None); 42 | assert_eq!( 43 | String::from("42").diff(&String::from("asdf")), 44 | Some(String::from("asdf")) 45 | ); 46 | assert_eq!(String::from("42").diff(&String::from("42")), None); 47 | } 48 | 49 | #[test] 50 | fn test_cow() { 51 | // Cow<'_, str> 52 | assert_eq!( 53 | Cow::from("42").diff(&Cow::from("asdf")), 54 | Some("asdf".into()) 55 | ); 56 | assert_eq!(Cow::from("42").diff(&Cow::from("42")), None); 57 | } 58 | 59 | #[test] 60 | fn test_opt() { 61 | assert_eq!(Some(10).diff(&Some(15)), OptionDiff::Some(5)); 62 | assert_eq!(None.apply_new(&OptionDiff::Some(5)), Some(5)); 63 | assert_eq!(Some(100).apply_new(&OptionDiff::None), None); 64 | identity_test(Some(42)) 65 | } 66 | 67 | #[test] 68 | fn test_maps() { 69 | let a = generate_map(vec![("a", 1), ("b", 2), ("x", 42)]); 70 | let b = generate_map(vec![("b", 3), ("c", 4), ("x", 42)]); 71 | let expected = HashMapDiff { 72 | altered: generate_map(vec![("b", 1), ("c", 4)]), 73 | removed: vec!["a"].into_iter().collect::>(), 74 | }; 75 | assert_eq!(a.diff(&b), expected); 76 | identity_test(a); 77 | } 78 | 79 | #[test] 80 | fn test_sets() { 81 | let a = generate_set(vec![1, 2, 42]); 82 | identity_test(a.clone()); 83 | 84 | let b = generate_set(vec![1, 4, 42]); 85 | let diff = a.diff(&b); 86 | let expected = HashSetDiff { 87 | added: vec![4].into_iter().collect::>(), 88 | removed: vec![2].into_iter().collect::>(), 89 | }; 90 | assert_eq!(diff, expected); 91 | 92 | let mut a_plus_diff = a; 93 | a_plus_diff.apply(&diff); 94 | assert_eq!(a_plus_diff, b); 95 | } 96 | 97 | #[test] 98 | fn test_path() { 99 | let a = PathBuf::from(r"/example/path/to/file.ext"); 100 | let b = PathBuf::from(r"/different/path"); 101 | identity_test(a.clone()); 102 | assert_eq!(a.diff(&b), Some(b.clone())); 103 | } 104 | 105 | #[derive(Debug, PartialEq, Diff)] 106 | #[diff(attr( 107 | #[derive(Debug, PartialEq)] 108 | ))] 109 | #[diff(name(TestDiff))] 110 | #[diff(path(crate))] 111 | struct TestStruct { 112 | a: bool, 113 | b: u32, 114 | } 115 | 116 | #[test] 117 | fn test_derive() { 118 | let a = TestStruct { a: false, b: 42 }; 119 | 120 | let b = TestStruct { a: true, b: 43 }; 121 | 122 | let diff = TestDiff { 123 | a: true.into(), 124 | b: 1, 125 | }; 126 | assert_eq!(a.diff(&b), diff); 127 | 128 | identity_test(a); 129 | } 130 | 131 | #[derive(Debug, PartialEq, Diff)] 132 | #[diff(attr( 133 | #[derive(Debug, PartialEq)] 134 | ))] 135 | #[diff(path(crate))] 136 | struct TestTupleStruct(i32); 137 | 138 | #[test] 139 | fn test_tuple_derive() { 140 | let a = TestTupleStruct(10); 141 | let b = TestTupleStruct(30); 142 | let diff = TestTupleStructDiff(20); 143 | assert_eq!(a.diff(&b), diff); 144 | } 145 | 146 | #[derive(Debug, Default, PartialEq, Diff)] 147 | #[diff(visibility(pub))] 148 | #[diff(path(crate))] 149 | struct ProjectMeta { 150 | contributors: Vec, 151 | combined_work_hours: usize, 152 | } 153 | 154 | #[test] 155 | fn test_apply() { 156 | let mut base = ProjectMeta::default(); 157 | let contribution_a = ProjectMeta { 158 | contributors: vec!["Alice".into()], 159 | combined_work_hours: 3, 160 | }; 161 | let contribution_b = ProjectMeta { 162 | contributors: vec!["Bob".into(), "Candice".into()], 163 | combined_work_hours: 10, 164 | }; 165 | let expected = ProjectMeta { 166 | contributors: vec!["Bob".into(), "Candice".into(), "Alice".into()], 167 | combined_work_hours: 13, 168 | }; 169 | let diff_a = base.diff(&contribution_a); 170 | let diff_b = base.diff(&contribution_b); 171 | base.apply(&diff_a); 172 | base.apply(&diff_b); 173 | assert_eq!(base, expected); 174 | } 175 | 176 | #[test] 177 | fn test_vecs() { 178 | let a = vec![0, 1, 2, 3, 4, 5, 6, 7]; 179 | let b = vec![0, /*1, 2*/ 3, 4, 42, 5, /*6 ->*/ 10, 7]; 180 | let diff = VecDiff(vec![ 181 | VecDiffType::Removed { index: 1, len: 2 }, 182 | VecDiffType::Inserted { 183 | index: 5, 184 | changes: vec![42], 185 | }, 186 | VecDiffType::Altered { 187 | index: 6, 188 | changes: vec![4], // add 4 to 6 189 | }, 190 | ]); 191 | assert_eq!(diff, a.diff(&b)); 192 | assert_eq!(a.apply_new(&diff), b); 193 | } 194 | 195 | #[test] 196 | fn test_arrays() { 197 | let array = [1, 2, 3, 4, 5]; 198 | identity_test(array); 199 | let other = [1, 2, 7, 4, 0]; 200 | let diff = array.diff(&other); 201 | assert_eq!( 202 | diff, 203 | ArrayDiff(vec![ 204 | ArrayDiffType { 205 | index: 2, 206 | change: 4 207 | }, 208 | ArrayDiffType { 209 | index: 4, 210 | change: -5 211 | } 212 | ]) 213 | ); 214 | assert_eq!(array.apply_new(&diff), other); 215 | } 216 | 217 | use serde::Serialize; 218 | 219 | #[derive(Default, PartialEq, Serialize, Diff)] 220 | #[diff(name(SpecialName))] 221 | #[diff(visibility(pub))] 222 | #[diff(attr( 223 | #[derive(Default, PartialEq, Serialize)] 224 | ))] 225 | #[diff(path(crate))] 226 | /// A struct with a lot of attributes 227 | struct MyTestStruct { 228 | #[diff(name(special_field_name))] 229 | #[diff(visibility(pub))] 230 | #[diff(attr( 231 | #[serde(rename = "name")] 232 | ))] 233 | /// This field has a lot of attributes too 234 | test_field: u32, 235 | } 236 | 237 | #[test] 238 | fn test_full_struct() { 239 | let base = MyTestStruct::default(); 240 | let other = MyTestStruct { test_field: 1 }; 241 | 242 | let diff = base.diff(&other); 243 | assert_eq!(diff.special_field_name, 1); 244 | } 245 | 246 | #[derive(Diff, PartialEq)] 247 | #[diff(attr(#[derive(Debug, PartialEq)]))] 248 | #[diff(path(crate))] 249 | struct PhantomDataTest { 250 | value: i32, 251 | phantom: PhantomData, 252 | } 253 | 254 | #[test] 255 | fn test_phantom_data() { 256 | let base = PhantomDataTest:: { 257 | value: 100, 258 | phantom: Default::default(), 259 | }; 260 | let other = PhantomDataTest:: { 261 | value: 142, 262 | phantom: Default::default(), 263 | }; 264 | assert_eq!( 265 | base.diff(&other), 266 | PhantomDataTestDiff:: { 267 | value: 42, 268 | phantom: Default::default(), 269 | } 270 | ); 271 | } 272 | 273 | #[test] 274 | fn test_box() { 275 | let array = [1, 2, 3, 4, 5]; 276 | identity_test(array); 277 | let other = [1, 2, 7, 4, 0]; 278 | 279 | let array = Box::new(array); 280 | let other = Box::new(other); 281 | 282 | let diff = array.diff(&other); 283 | assert_eq!( 284 | diff, 285 | Box::new(ArrayDiff(vec![ 286 | ArrayDiffType { 287 | index: 2, 288 | change: 4 289 | }, 290 | ArrayDiffType { 291 | index: 4, 292 | change: -5 293 | } 294 | ])) 295 | ); 296 | assert_eq!(array.apply_new(&diff), other); 297 | } 298 | 299 | #[derive(Debug, PartialEq, Diff)] 300 | #[diff(path(crate))] 301 | #[diff(attr(#[derive(Debug, PartialEq)]))] 302 | struct LinkedListNode 303 | where 304 | T::Repr: Debug + PartialEq, 305 | { 306 | value: T, 307 | child: Option>>, 308 | } 309 | 310 | #[test] 311 | fn test_box_recursive() { 312 | let node = LinkedListNode { 313 | value: 10, 314 | child: Some(Box::new(LinkedListNode { 315 | value: 2, 316 | child: Some(Box::new(LinkedListNode { 317 | value: 1, 318 | child: None, 319 | })), 320 | })), 321 | }; 322 | let other = LinkedListNode { 323 | value: 42, 324 | child: Some(Box::new(LinkedListNode { 325 | value: 2, 326 | child: None, 327 | })), 328 | }; 329 | let diff = node.diff(&other); 330 | assert_eq!( 331 | diff, 332 | LinkedListNodeDiff { 333 | value: 32, 334 | child: OptionDiff::Some(Box::new(LinkedListNodeDiff { 335 | value: 0, 336 | child: OptionDiff::None 337 | })), 338 | } 339 | ); 340 | assert_eq!(node.apply_new(&diff), other); 341 | } 342 | 343 | #[test] 344 | fn test_rc() { 345 | let array = [1, 2, 7, 4, 5]; 346 | identity_test(array); 347 | let other = [1, 2, 7, 4, 0]; 348 | 349 | let array = Rc::new(array); 350 | let other = Rc::new(other); 351 | 352 | let diff = array.diff(&other); 353 | assert_eq!( 354 | diff, 355 | ArrayDiff(vec![ArrayDiffType { 356 | index: 4, 357 | change: -5 358 | },]) 359 | ); 360 | assert_eq!(array.apply_new(&diff), other); 361 | } 362 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.19" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "autocfg" 16 | version = "0.1.8" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" 19 | dependencies = [ 20 | "autocfg 1.1.0", 21 | ] 22 | 23 | [[package]] 24 | name = "autocfg" 25 | version = "1.1.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "1.3.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 34 | 35 | [[package]] 36 | name = "cfg-if" 37 | version = "1.0.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 40 | 41 | [[package]] 42 | name = "cloudabi" 43 | version = "0.0.3" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 46 | dependencies = [ 47 | "bitflags", 48 | ] 49 | 50 | [[package]] 51 | name = "diff-struct" 52 | version = "0.5.3" 53 | dependencies = [ 54 | "diff_derive", 55 | "num", 56 | "quickcheck", 57 | "quickcheck_derive", 58 | "quickcheck_macros", 59 | "rand", 60 | "serde", 61 | ] 62 | 63 | [[package]] 64 | name = "diff_derive" 65 | version = "0.2.3" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "fe165e7ead196bbbf44c7ce11a7a21157b5c002ce46d7098ff9c556784a4912d" 68 | dependencies = [ 69 | "proc-macro2 1.0.47", 70 | "quote 1.0.21", 71 | "syn 1.0.103", 72 | ] 73 | 74 | [[package]] 75 | name = "env_logger" 76 | version = "0.6.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" 79 | dependencies = [ 80 | "log", 81 | "regex", 82 | ] 83 | 84 | [[package]] 85 | name = "fuchsia-cprng" 86 | version = "0.1.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 89 | 90 | [[package]] 91 | name = "libc" 92 | version = "0.2.135" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" 95 | 96 | [[package]] 97 | name = "log" 98 | version = "0.4.17" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 101 | dependencies = [ 102 | "cfg-if", 103 | ] 104 | 105 | [[package]] 106 | name = "memchr" 107 | version = "2.5.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 110 | 111 | [[package]] 112 | name = "num" 113 | version = "0.4.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" 116 | dependencies = [ 117 | "num-bigint", 118 | "num-complex", 119 | "num-integer", 120 | "num-iter", 121 | "num-rational", 122 | "num-traits", 123 | ] 124 | 125 | [[package]] 126 | name = "num-bigint" 127 | version = "0.4.3" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 130 | dependencies = [ 131 | "autocfg 1.1.0", 132 | "num-integer", 133 | "num-traits", 134 | ] 135 | 136 | [[package]] 137 | name = "num-complex" 138 | version = "0.4.2" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" 141 | dependencies = [ 142 | "num-traits", 143 | ] 144 | 145 | [[package]] 146 | name = "num-integer" 147 | version = "0.1.45" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 150 | dependencies = [ 151 | "autocfg 1.1.0", 152 | "num-traits", 153 | ] 154 | 155 | [[package]] 156 | name = "num-iter" 157 | version = "0.1.43" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" 160 | dependencies = [ 161 | "autocfg 1.1.0", 162 | "num-integer", 163 | "num-traits", 164 | ] 165 | 166 | [[package]] 167 | name = "num-rational" 168 | version = "0.4.1" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" 171 | dependencies = [ 172 | "autocfg 1.1.0", 173 | "num-bigint", 174 | "num-integer", 175 | "num-traits", 176 | ] 177 | 178 | [[package]] 179 | name = "num-traits" 180 | version = "0.2.15" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 183 | dependencies = [ 184 | "autocfg 1.1.0", 185 | ] 186 | 187 | [[package]] 188 | name = "proc-macro2" 189 | version = "0.4.30" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 192 | dependencies = [ 193 | "unicode-xid 0.1.0", 194 | ] 195 | 196 | [[package]] 197 | name = "proc-macro2" 198 | version = "1.0.47" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" 201 | dependencies = [ 202 | "unicode-ident", 203 | ] 204 | 205 | [[package]] 206 | name = "quickcheck" 207 | version = "0.8.5" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "9c35d9c36a562f37eca96e79f66d5fd56eefbc22560dacc4a864cabd2d277456" 210 | dependencies = [ 211 | "env_logger", 212 | "log", 213 | "rand", 214 | "rand_core 0.4.2", 215 | ] 216 | 217 | [[package]] 218 | name = "quickcheck_derive" 219 | version = "0.3.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "695566b1208e5a3e5a613d3326fcdb78203e1493c81514bfb4f4460ce0694deb" 222 | dependencies = [ 223 | "proc-macro2 1.0.47", 224 | "syn 1.0.103", 225 | "synstructure", 226 | ] 227 | 228 | [[package]] 229 | name = "quickcheck_macros" 230 | version = "0.8.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "d7dfc1c4a1e048f5cc7d36a4c4118dfcf31d217c79f4b9a61bad65d68185752c" 233 | dependencies = [ 234 | "proc-macro2 0.4.30", 235 | "quote 0.6.13", 236 | "syn 0.15.44", 237 | ] 238 | 239 | [[package]] 240 | name = "quote" 241 | version = "0.6.13" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 244 | dependencies = [ 245 | "proc-macro2 0.4.30", 246 | ] 247 | 248 | [[package]] 249 | name = "quote" 250 | version = "1.0.21" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 253 | dependencies = [ 254 | "proc-macro2 1.0.47", 255 | ] 256 | 257 | [[package]] 258 | name = "rand" 259 | version = "0.6.5" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 262 | dependencies = [ 263 | "autocfg 0.1.8", 264 | "libc", 265 | "rand_chacha", 266 | "rand_core 0.4.2", 267 | "rand_hc", 268 | "rand_isaac", 269 | "rand_jitter", 270 | "rand_os", 271 | "rand_pcg", 272 | "rand_xorshift", 273 | "winapi", 274 | ] 275 | 276 | [[package]] 277 | name = "rand_chacha" 278 | version = "0.1.1" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 281 | dependencies = [ 282 | "autocfg 0.1.8", 283 | "rand_core 0.3.1", 284 | ] 285 | 286 | [[package]] 287 | name = "rand_core" 288 | version = "0.3.1" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 291 | dependencies = [ 292 | "rand_core 0.4.2", 293 | ] 294 | 295 | [[package]] 296 | name = "rand_core" 297 | version = "0.4.2" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 300 | 301 | [[package]] 302 | name = "rand_hc" 303 | version = "0.1.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 306 | dependencies = [ 307 | "rand_core 0.3.1", 308 | ] 309 | 310 | [[package]] 311 | name = "rand_isaac" 312 | version = "0.1.1" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 315 | dependencies = [ 316 | "rand_core 0.3.1", 317 | ] 318 | 319 | [[package]] 320 | name = "rand_jitter" 321 | version = "0.1.4" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" 324 | dependencies = [ 325 | "libc", 326 | "rand_core 0.4.2", 327 | "winapi", 328 | ] 329 | 330 | [[package]] 331 | name = "rand_os" 332 | version = "0.1.3" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 335 | dependencies = [ 336 | "cloudabi", 337 | "fuchsia-cprng", 338 | "libc", 339 | "rand_core 0.4.2", 340 | "rdrand", 341 | "winapi", 342 | ] 343 | 344 | [[package]] 345 | name = "rand_pcg" 346 | version = "0.1.2" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 349 | dependencies = [ 350 | "autocfg 0.1.8", 351 | "rand_core 0.4.2", 352 | ] 353 | 354 | [[package]] 355 | name = "rand_xorshift" 356 | version = "0.1.1" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 359 | dependencies = [ 360 | "rand_core 0.3.1", 361 | ] 362 | 363 | [[package]] 364 | name = "rdrand" 365 | version = "0.4.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 368 | dependencies = [ 369 | "rand_core 0.3.1", 370 | ] 371 | 372 | [[package]] 373 | name = "regex" 374 | version = "1.6.0" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 377 | dependencies = [ 378 | "aho-corasick", 379 | "memchr", 380 | "regex-syntax", 381 | ] 382 | 383 | [[package]] 384 | name = "regex-syntax" 385 | version = "0.6.27" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 388 | 389 | [[package]] 390 | name = "serde" 391 | version = "1.0.147" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" 394 | dependencies = [ 395 | "serde_derive", 396 | ] 397 | 398 | [[package]] 399 | name = "serde_derive" 400 | version = "1.0.147" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" 403 | dependencies = [ 404 | "proc-macro2 1.0.47", 405 | "quote 1.0.21", 406 | "syn 1.0.103", 407 | ] 408 | 409 | [[package]] 410 | name = "syn" 411 | version = "0.15.44" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 414 | dependencies = [ 415 | "proc-macro2 0.4.30", 416 | "quote 0.6.13", 417 | "unicode-xid 0.1.0", 418 | ] 419 | 420 | [[package]] 421 | name = "syn" 422 | version = "1.0.103" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 425 | dependencies = [ 426 | "proc-macro2 1.0.47", 427 | "quote 1.0.21", 428 | "unicode-ident", 429 | ] 430 | 431 | [[package]] 432 | name = "synstructure" 433 | version = "0.12.6" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 436 | dependencies = [ 437 | "proc-macro2 1.0.47", 438 | "quote 1.0.21", 439 | "syn 1.0.103", 440 | "unicode-xid 0.2.4", 441 | ] 442 | 443 | [[package]] 444 | name = "unicode-ident" 445 | version = "1.0.5" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 448 | 449 | [[package]] 450 | name = "unicode-xid" 451 | version = "0.1.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 454 | 455 | [[package]] 456 | name = "unicode-xid" 457 | version = "0.2.4" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 460 | 461 | [[package]] 462 | name = "winapi" 463 | version = "0.3.9" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 466 | dependencies = [ 467 | "winapi-i686-pc-windows-gnu", 468 | "winapi-x86_64-pc-windows-gnu", 469 | ] 470 | 471 | [[package]] 472 | name = "winapi-i686-pc-windows-gnu" 473 | version = "0.4.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 476 | 477 | [[package]] 478 | name = "winapi-x86_64-pc-windows-gnu" 479 | version = "0.4.0" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 482 | -------------------------------------------------------------------------------- /src/impls.rs: -------------------------------------------------------------------------------- 1 | use super::utils::find_match; 2 | use super::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::borrow::Cow; 5 | use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; 6 | use std::fmt::{Debug, Formatter, Result as FmtResult}; 7 | use std::hash::Hash; 8 | use std::marker::PhantomData; 9 | use std::ops::Deref; 10 | use std::path::PathBuf; 11 | use std::rc::Rc; 12 | use std::sync::Arc; 13 | 14 | impl Diff for bool { 15 | type Repr = Option; 16 | 17 | fn diff(&self, other: &Self) -> Self::Repr { 18 | if self != other { 19 | Some(*other) 20 | } else { 21 | None 22 | } 23 | } 24 | 25 | fn apply(&mut self, diff: &Self::Repr) { 26 | if let Some(diff) = diff { 27 | *self = *diff; 28 | } 29 | } 30 | 31 | fn identity() -> Self { 32 | false 33 | } 34 | } 35 | 36 | impl Diff for Arc 37 | where 38 | T: Diff + Clone, 39 | { 40 | type Repr = T::Repr; 41 | 42 | fn diff(&self, other: &Self) -> Self::Repr { 43 | self.deref().diff(other.deref()) 44 | } 45 | 46 | fn apply(&mut self, diff: &Self::Repr) { 47 | match Arc::get_mut(self) { 48 | Some(m) => m.apply(diff), 49 | None => { 50 | let mut x = (**self).clone(); 51 | x.apply(diff); 52 | *self = Arc::new(x); 53 | } 54 | } 55 | } 56 | 57 | fn identity() -> Self { 58 | Arc::new(T::identity()) 59 | } 60 | } 61 | 62 | impl Diff for Box 63 | where 64 | T: Diff, 65 | { 66 | type Repr = Box; 67 | 68 | fn diff(&self, other: &Self) -> Self::Repr { 69 | Box::new(self.deref().diff(other.deref())) 70 | } 71 | 72 | fn apply(&mut self, diff: &Self::Repr) { 73 | self.as_mut().apply(diff.as_ref()) 74 | } 75 | 76 | fn identity() -> Self { 77 | Box::new(T::identity()) 78 | } 79 | } 80 | 81 | impl Diff for Rc 82 | where 83 | T: Diff + Clone, 84 | { 85 | type Repr = T::Repr; 86 | 87 | fn diff(&self, other: &Self) -> Self::Repr { 88 | self.deref().diff(other.deref()) 89 | } 90 | 91 | fn apply(&mut self, diff: &Self::Repr) { 92 | match Rc::get_mut(self) { 93 | Some(m) => m.apply(diff), 94 | None => { 95 | let mut x = (**self).clone(); 96 | x.apply(diff); 97 | *self = Rc::new(x); 98 | } 99 | } 100 | } 101 | 102 | fn identity() -> Self { 103 | Rc::new(T::identity()) 104 | } 105 | } 106 | 107 | macro_rules! diff_tuple { 108 | (($($ty:ident),*), ($($access:tt),*)) => { 109 | impl<$($ty),*> Diff for ($($ty),*,) 110 | where $($ty: Diff),* 111 | { 112 | type Repr = ($(<$ty>::Repr),*,); 113 | 114 | fn diff(&self, other: &Self) -> Self::Repr { 115 | ($(self.$access.diff(&other.$access)),*,) 116 | } 117 | 118 | fn apply(&mut self, diff: &Self::Repr) { 119 | $(self.$access.apply(&diff.$access));*; 120 | } 121 | 122 | fn identity() -> Self { 123 | ($(<$ty>::identity()),*,) 124 | } 125 | } 126 | } 127 | } 128 | 129 | diff_tuple!((A), (0)); 130 | diff_tuple!((A, B), (0, 1)); 131 | diff_tuple!((A, B, C), (0, 1, 2)); 132 | diff_tuple!((A, B, C, D), (0, 1, 2, 3)); 133 | diff_tuple!((A, B, C, D, F), (0, 1, 2, 3, 4)); 134 | diff_tuple!((A, B, C, D, F, G), (0, 1, 2, 3, 4, 5)); 135 | diff_tuple!((A, B, C, D, F, G, H), (0, 1, 2, 3, 4, 5, 6)); 136 | diff_tuple!((A, B, C, D, F, G, H, I), (0, 1, 2, 3, 4, 5, 6, 7)); 137 | diff_tuple!((A, B, C, D, F, G, H, I, J), (0, 1, 2, 3, 4, 5, 6, 7, 8)); 138 | diff_tuple!( 139 | (A, B, C, D, F, G, H, I, J, K), 140 | (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) 141 | ); 142 | diff_tuple!( 143 | (A, B, C, D, F, G, H, I, J, K, L), 144 | (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 145 | ); 146 | diff_tuple!( 147 | (A, B, C, D, F, G, H, I, J, K, L, M), 148 | (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) 149 | ); 150 | diff_tuple!( 151 | (A, B, C, D, F, G, H, I, J, K, L, M, N), 152 | (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) 153 | ); 154 | diff_tuple!( 155 | (A, B, C, D, F, G, H, I, J, K, L, M, N, O), 156 | (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) 157 | ); 158 | diff_tuple!( 159 | (A, B, C, D, F, G, H, I, J, K, L, M, N, O, P), 160 | (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) 161 | ); 162 | diff_tuple!( 163 | (A, B, C, D, F, G, H, I, J, K, L, M, N, O, P, Q), 164 | (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) 165 | ); 166 | diff_tuple!( 167 | (A, B, C, D, F, G, H, I, J, K, L, M, N, O, P, Q, R), 168 | (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) 169 | ); 170 | diff_tuple!( 171 | (A, B, C, D, F, G, H, I, J, K, L, M, N, O, P, Q, R, S), 172 | (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17) 173 | ); 174 | 175 | macro_rules! diff_int { 176 | ($($ty:ty),*) => { 177 | $(impl Diff for $ty { 178 | type Repr = $ty; 179 | 180 | fn diff(&self, other: &Self) -> Self::Repr { 181 | other.wrapping_sub(*self) 182 | } 183 | 184 | fn apply(&mut self, diff: &Self::Repr) { 185 | *self = self.wrapping_add(*diff); 186 | } 187 | 188 | fn identity() -> $ty { 189 | 0 190 | } 191 | })* 192 | }; 193 | } 194 | 195 | macro_rules! diff_float { 196 | ($($ty:ty),*) => { 197 | $(impl Diff for $ty { 198 | type Repr = $ty; 199 | 200 | fn diff(&self, other: &Self) -> Self::Repr { 201 | other - self 202 | } 203 | 204 | fn apply(&mut self, diff: &Self::Repr){ 205 | *self += diff; 206 | } 207 | 208 | fn identity() -> $ty { 209 | 0.0 210 | } 211 | })* 212 | }; 213 | } 214 | 215 | diff_int!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize); 216 | 217 | macro_rules! diff_non_zero_int { 218 | ($($ty:ty, $original:ty),*) => { 219 | #[cfg(feature = "impl_num")] 220 | $(impl Diff for $ty { 221 | type Repr = $original; 222 | 223 | fn diff(&self, other: &Self) -> Self::Repr { 224 | other.get().wrapping_sub(self.get()) 225 | } 226 | 227 | fn apply(&mut self, diff: &Self::Repr) { 228 | *self = <$ty>::new(self.get() + *diff).unwrap(); 229 | } 230 | 231 | fn identity() -> $ty { 232 | use num::traits::One; 233 | <$ty>::new(<$original>::one()).unwrap() 234 | } 235 | })* 236 | }; 237 | } 238 | 239 | #[cfg(feature = "impl_num")] 240 | use std::num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}; 241 | 242 | diff_non_zero_int!(NonZeroU8, u8); 243 | diff_non_zero_int!(NonZeroU16, u16); 244 | diff_non_zero_int!(NonZeroU32, u32); 245 | diff_non_zero_int!(NonZeroU64, u64); 246 | diff_non_zero_int!(NonZeroU128, u128); 247 | diff_non_zero_int!(NonZeroUsize, usize); 248 | 249 | diff_float!(f32, f64); 250 | 251 | impl Diff for char { 252 | type Repr = Option; 253 | 254 | fn diff(&self, other: &Self) -> Self::Repr { 255 | if self != other { 256 | Some(*other) 257 | } else { 258 | None 259 | } 260 | } 261 | 262 | fn apply(&mut self, diff: &Self::Repr) { 263 | if let Some(diff) = diff { 264 | *self = *diff 265 | } 266 | } 267 | 268 | fn identity() -> Self { 269 | '\x00' 270 | } 271 | } 272 | 273 | impl Diff for String { 274 | type Repr = Option; 275 | 276 | fn diff(&self, other: &Self) -> Self::Repr { 277 | if self != other { 278 | Some(other.clone()) 279 | } else { 280 | None 281 | } 282 | } 283 | 284 | fn apply(&mut self, diff: &Self::Repr) { 285 | if let Some(diff) = diff { 286 | *self = diff.clone() 287 | } 288 | } 289 | 290 | fn identity() -> Self { 291 | String::new() 292 | } 293 | } 294 | 295 | impl Diff for PathBuf { 296 | type Repr = Option; 297 | 298 | fn diff(&self, other: &Self) -> Self::Repr { 299 | if self != other { 300 | Some(other.clone()) 301 | } else { 302 | None 303 | } 304 | } 305 | 306 | fn apply(&mut self, diff: &Self::Repr) { 307 | if let Some(diff) = diff { 308 | *self = diff.clone() 309 | } 310 | } 311 | 312 | fn identity() -> Self { 313 | PathBuf::new() 314 | } 315 | } 316 | 317 | impl<'a> Diff for &'a str { 318 | type Repr = Option<&'a str>; 319 | 320 | fn diff(&self, other: &Self) -> Self::Repr { 321 | if self != other { 322 | Some(other) 323 | } else { 324 | None 325 | } 326 | } 327 | 328 | fn apply(&mut self, diff: &Self::Repr) { 329 | if let Some(diff) = diff { 330 | *self = diff 331 | } 332 | } 333 | 334 | fn identity() -> Self { 335 | Default::default() 336 | } 337 | } 338 | 339 | impl Diff for Cow<'_, T> 340 | where 341 | T: ToOwned + PartialEq + ?Sized, 342 | ::Owned: Clone + Default, 343 | { 344 | /// Note: This was done to make sure a diff is able to outlive its sources, 345 | /// which is the most desirable outcome if the diff is to be moved around 346 | /// and consumed much later (or even serialized into foreign programs) 347 | type Repr = Option<::Owned>; 348 | 349 | fn diff(&self, other: &Self) -> Self::Repr { 350 | if self != other { 351 | Some(other.clone().into_owned()) 352 | } else { 353 | None 354 | } 355 | } 356 | 357 | fn apply(&mut self, diff: &Self::Repr) { 358 | if let Some(diff) = diff { 359 | *self = Cow::Owned(diff.clone()) 360 | } 361 | } 362 | 363 | fn identity() -> Self { 364 | Default::default() 365 | } 366 | } 367 | 368 | #[derive(Serialize, Deserialize)] 369 | pub enum OptionDiff { 370 | Some(T::Repr), 371 | None, 372 | NoChange, 373 | } 374 | 375 | impl Diff for Option { 376 | type Repr = OptionDiff; 377 | 378 | fn diff(&self, other: &Self) -> Self::Repr { 379 | match (self, other) { 380 | (Some(value), Some(other_value)) => { 381 | if value == other_value { 382 | OptionDiff::NoChange 383 | } else { 384 | OptionDiff::Some(value.diff(other_value)) 385 | } 386 | } 387 | (Some(_), None) => OptionDiff::None, 388 | (None, Some(other_value)) => OptionDiff::Some(T::identity().diff(other_value)), 389 | (None, None) => OptionDiff::NoChange, 390 | } 391 | } 392 | 393 | fn apply(&mut self, diff: &Self::Repr) { 394 | match diff { 395 | OptionDiff::None => *self = None, 396 | OptionDiff::Some(change) => { 397 | if let Some(value) = self { 398 | value.apply(change); 399 | } else { 400 | *self = Some(T::identity().apply_new(change)) 401 | } 402 | } 403 | _ => {} 404 | } 405 | } 406 | 407 | fn identity() -> Self { 408 | None 409 | } 410 | } 411 | 412 | impl Debug for OptionDiff 413 | where 414 | T::Repr: Debug, 415 | { 416 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 417 | match &self { 418 | OptionDiff::Some(change) => f.debug_tuple("Some").field(change).finish(), 419 | OptionDiff::None => write!(f, "None"), 420 | OptionDiff::NoChange => write!(f, "NoChange"), 421 | } 422 | } 423 | } 424 | 425 | impl PartialEq for OptionDiff 426 | where 427 | T::Repr: PartialEq, 428 | { 429 | fn eq(&self, other: &Self) -> bool { 430 | match (self, other) { 431 | (OptionDiff::Some(a), OptionDiff::Some(b)) => a == b, 432 | (OptionDiff::None, OptionDiff::None) => true, 433 | (OptionDiff::NoChange, OptionDiff::NoChange) => true, 434 | _ => false, 435 | } 436 | } 437 | } 438 | 439 | impl Clone for OptionDiff 440 | where 441 | T::Repr: Clone, 442 | { 443 | fn clone(&self) -> Self { 444 | match self { 445 | OptionDiff::Some(a) => OptionDiff::Some(a.clone()), 446 | OptionDiff::None => OptionDiff::None, 447 | OptionDiff::NoChange => OptionDiff::NoChange, 448 | } 449 | } 450 | } 451 | 452 | /// The diff struct used to compare two [HashMap]'s 453 | #[derive(Serialize, Deserialize)] 454 | #[serde(bound(serialize = "V::Repr: Serialize, K: Serialize"))] 455 | #[serde(bound(deserialize = "V::Repr: Deserialize<'de>, K: Deserialize<'de>"))] 456 | pub struct HashMapDiff { 457 | /// Values that are changed or added 458 | pub altered: HashMap::Repr>, 459 | /// Values that are removed 460 | pub removed: HashSet, 461 | } 462 | 463 | /// The diff struct used to compare two [BTreeMap]'s 464 | #[derive(Serialize, Deserialize)] 465 | #[serde(bound(serialize = "V::Repr: Serialize, K: Serialize"))] 466 | #[serde(bound(deserialize = "V::Repr: Deserialize<'de>, K: Deserialize<'de>"))] 467 | pub struct BTreeMapDiff { 468 | /// Values that are changed or added 469 | pub altered: BTreeMap::Repr>, 470 | /// Values that are removed 471 | pub removed: BTreeSet, 472 | } 473 | 474 | macro_rules! diff_map { 475 | ($ty: ident, $diffty: ident, $diffkey: ident, ($($constraints:tt)*)) => { 476 | impl Diff for $ty 477 | where 478 | K: Clone, 479 | V: PartialEq, 480 | { 481 | type Repr = $diffty; 482 | 483 | fn diff(&self, other: &Self) -> Self::Repr { 484 | let mut diff = $diffty { 485 | altered: $ty::new(), 486 | removed: $diffkey::new(), 487 | }; 488 | // can we do better than this? 489 | for (key, value) in self { 490 | if let Some(other_value) = other.get(key) { 491 | // don't store values that don't change 492 | if value != other_value { 493 | diff.altered.insert(key.clone(), value.diff(other_value)); 494 | } 495 | } else { 496 | diff.removed.insert(key.clone()); 497 | } 498 | } 499 | for (key, value) in other { 500 | if let None = self.get(key) { 501 | diff.altered.insert(key.clone(), V::identity().diff(value)); 502 | } 503 | } 504 | diff 505 | } 506 | 507 | // basically inexpensive 508 | fn apply(&mut self, diff: &Self::Repr) { 509 | diff.removed.iter().for_each(|del| { 510 | self.remove(del); 511 | }); 512 | for (key, change) in &diff.altered { 513 | if let Some(original) = self.get_mut(key) { 514 | original.apply(change); 515 | } else { 516 | self.insert(key.clone(), V::identity().apply_new(change)); 517 | } 518 | } 519 | } 520 | 521 | fn identity() -> Self { 522 | $ty::new() 523 | } 524 | } 525 | 526 | impl Debug for $diffty 527 | where 528 | K: Debug, 529 | V::Repr: Debug, 530 | { 531 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 532 | f.debug_struct(stringify!($diffty)) 533 | .field("altered", &self.altered) 534 | .field("removed", &self.removed) 535 | .finish() 536 | } 537 | } 538 | 539 | impl PartialEq for $diffty 540 | where 541 | V::Repr: PartialEq, 542 | { 543 | fn eq(&self, other: &Self) -> bool { 544 | self.altered == other.altered && self.removed == other.removed 545 | } 546 | } 547 | 548 | impl Clone for $diffty 549 | where 550 | K: Clone, 551 | V::Repr: Clone, 552 | { 553 | fn clone(&self) -> Self { 554 | $diffty { 555 | altered: self.altered.clone(), 556 | removed: self.removed.clone(), 557 | } 558 | } 559 | } 560 | } 561 | } 562 | 563 | diff_map!(HashMap, HashMapDiff, HashSet, (Hash + Eq)); 564 | diff_map!(BTreeMap, BTreeMapDiff, BTreeSet, (Ord)); 565 | 566 | /// The diff struct used to compare two [HashSet]'s 567 | #[derive(Serialize, Deserialize)] 568 | #[serde(bound(serialize = "T: Serialize"))] 569 | #[serde(bound(deserialize = "T: Deserialize<'de>"))] 570 | pub struct HashSetDiff { 571 | /// Values that are added 572 | pub added: HashSet, 573 | /// Values that are removed 574 | pub removed: HashSet, 575 | } 576 | 577 | /// The diff struct used to compare two [BTreeMap]'s 578 | #[derive(Serialize, Deserialize)] 579 | #[serde(bound(serialize = "T: Serialize"))] 580 | #[serde(bound(deserialize = "T: Deserialize<'de>"))] 581 | pub struct BTreeSetDiff { 582 | /// Values that are added 583 | pub added: BTreeSet, 584 | /// Values that are removed 585 | pub removed: BTreeSet, 586 | } 587 | 588 | macro_rules! diff_set { 589 | ($ty: ident, $diffty: ident, $diffkey: ident, ($($constraints:tt)*)) => { 590 | impl Diff for $ty 591 | where 592 | T: Clone, 593 | { 594 | type Repr = $diffty; 595 | 596 | fn diff(&self, other: &Self) -> Self::Repr { 597 | let mut diff = $diffty { 598 | added: $diffkey::new(), 599 | removed: $diffkey::new(), 600 | }; 601 | for value in self { 602 | if !other.contains(value) { 603 | diff.removed.insert(value.clone()); 604 | } 605 | } 606 | for value in other { 607 | if !self.contains(value) { 608 | diff.added.insert(value.clone()); 609 | } 610 | } 611 | diff 612 | } 613 | 614 | // basically inexpensive 615 | fn apply(&mut self, diff: &Self::Repr) { 616 | diff.removed.iter().for_each(|del| { 617 | self.remove(del); 618 | }); 619 | diff.added.iter().for_each(|add| { 620 | self.insert(add.clone()); 621 | }); 622 | } 623 | 624 | fn identity() -> Self { 625 | $ty::new() 626 | } 627 | } 628 | 629 | impl Debug for $diffty 630 | where 631 | T: Debug, 632 | { 633 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 634 | f.debug_struct(stringify!($diffty)) 635 | .field("added", &self.added) 636 | .field("removed", &self.removed) 637 | .finish() 638 | } 639 | } 640 | 641 | impl PartialEq for $diffty 642 | where 643 | T: PartialEq, 644 | { 645 | fn eq(&self, other: &Self) -> bool { 646 | self.added == other.added && self.removed == other.removed 647 | } 648 | } 649 | 650 | impl Clone for $diffty 651 | where 652 | T: Clone, 653 | { 654 | fn clone(&self) -> Self { 655 | $diffty { 656 | added: self.added.clone(), 657 | removed: self.removed.clone(), 658 | } 659 | } 660 | } 661 | } 662 | } 663 | 664 | diff_set!(HashSet, HashSetDiff, HashSet, (Hash + Eq)); 665 | diff_set!(BTreeSet, BTreeSetDiff, BTreeSet, (Ord)); 666 | 667 | /// The type of change to make to a vec 668 | #[derive(Serialize, Deserialize)] 669 | #[serde(bound(serialize = "T::Repr: Serialize"))] 670 | #[serde(bound(deserialize = "T::Repr: Deserialize<'de>"))] 671 | pub enum VecDiffType { 672 | Removed { index: usize, len: usize }, 673 | Altered { index: usize, changes: Vec }, 674 | Inserted { index: usize, changes: Vec }, 675 | } 676 | 677 | /// The collection of difference-vec's 678 | #[derive(Serialize, Deserialize)] 679 | #[serde(bound(serialize = "T::Repr: Serialize"))] 680 | #[serde(bound(deserialize = "T::Repr: Deserialize<'de>"))] 681 | pub struct VecDiff(pub Vec>); 682 | 683 | impl Diff for Vec { 684 | type Repr = VecDiff; 685 | 686 | fn diff(&self, other: &Self) -> Self::Repr { 687 | let mut changes = Vec::new(); 688 | let mut pos_x = 0; 689 | let mut pos_y = 0; 690 | loop { 691 | let (is_match, deletions, insertions) = find_match(&self[pos_x..], &other[pos_y..]); 692 | 693 | // TODO: simplify logic here 694 | if deletions == 0 || insertions == 0 { 695 | if deletions > 0 { 696 | changes.push(VecDiffType::Removed { 697 | index: pos_x, 698 | len: deletions, 699 | }); 700 | } else if insertions > 0 { 701 | changes.push(VecDiffType::Inserted { 702 | index: pos_x, 703 | changes: other[pos_y..pos_y + insertions] 704 | .iter() 705 | .map(|new| T::identity().diff(new)) 706 | .collect(), 707 | }); 708 | } 709 | } else if deletions == insertions { 710 | changes.push(VecDiffType::Altered { 711 | index: pos_x, 712 | changes: self[pos_x..pos_x + deletions] 713 | .iter() 714 | .zip(other[pos_y..pos_y + insertions].iter()) 715 | .map(|(a, b)| a.diff(b)) 716 | .collect(), 717 | }); 718 | } else if deletions > insertions { 719 | changes.push(VecDiffType::Altered { 720 | index: pos_x, 721 | changes: self[pos_x..pos_x + insertions] 722 | .iter() 723 | .zip(other[pos_y..pos_y + insertions].iter()) 724 | .map(|(a, b)| a.diff(b)) 725 | .collect(), 726 | }); 727 | changes.push(VecDiffType::Removed { 728 | index: pos_x + insertions, 729 | len: deletions - insertions, 730 | }); 731 | } else { 732 | changes.push(VecDiffType::Altered { 733 | index: pos_x, 734 | changes: self[pos_x..pos_x + deletions] 735 | .iter() 736 | .zip(other[pos_y..pos_y + deletions].iter()) 737 | .map(|(a, b)| a.diff(b)) 738 | .collect(), 739 | }); 740 | changes.push(VecDiffType::Inserted { 741 | index: pos_x + deletions, 742 | changes: other[pos_y + deletions..pos_y + insertions] 743 | .iter() 744 | .map(|new| T::identity().diff(new)) 745 | .collect(), 746 | }); 747 | } 748 | 749 | if is_match { 750 | pos_x += deletions + 1; 751 | pos_y += insertions + 1; 752 | } else { 753 | break; 754 | } 755 | } 756 | VecDiff(changes) 757 | } 758 | 759 | fn apply(&mut self, diff: &Self::Repr) { 760 | let mut relative_index = 0_isize; 761 | for change in &diff.0 { 762 | match change { 763 | VecDiffType::Removed { index, len } => { 764 | let index = (*index as isize + relative_index) as usize; 765 | self.drain(index..index + len); 766 | relative_index -= *len as isize; 767 | } 768 | VecDiffType::Inserted { index, changes } => { 769 | let index = (*index as isize + relative_index) as usize; 770 | self.splice( 771 | index..index, 772 | changes.iter().map(|d| T::identity().apply_new(d)), 773 | ); 774 | relative_index += changes.len() as isize; 775 | } 776 | VecDiffType::Altered { index, changes } => { 777 | let index = (*index as isize + relative_index) as usize; 778 | let range = index..index + changes.len(); 779 | for (value, diff) in self[range].iter_mut().zip(changes.iter()) { 780 | value.apply(diff); 781 | } 782 | } 783 | } 784 | } 785 | } 786 | 787 | fn identity() -> Self { 788 | Vec::new() 789 | } 790 | } 791 | 792 | impl Debug for VecDiffType 793 | where 794 | T::Repr: Debug, 795 | { 796 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 797 | match self { 798 | VecDiffType::Removed { index, len } => f 799 | .debug_struct("Removed") 800 | .field("index", index) 801 | .field("len", len) 802 | .finish(), 803 | VecDiffType::Altered { index, changes } => f 804 | .debug_struct("Altered") 805 | .field("index", index) 806 | .field("changes", changes) 807 | .finish(), 808 | VecDiffType::Inserted { index, changes } => f 809 | .debug_struct("Inserted") 810 | .field("index", index) 811 | .field("changes", changes) 812 | .finish(), 813 | } 814 | } 815 | } 816 | 817 | impl PartialEq for VecDiffType 818 | where 819 | T::Repr: PartialEq, 820 | { 821 | fn eq(&self, other: &Self) -> bool { 822 | match (self, other) { 823 | ( 824 | VecDiffType::Removed { index, len }, 825 | VecDiffType::Removed { 826 | index: ref index_, 827 | len: ref len_, 828 | }, 829 | ) => index == index_ && len == len_, 830 | ( 831 | VecDiffType::Altered { index, changes }, 832 | VecDiffType::Altered { 833 | index: ref index_, 834 | changes: ref changes_, 835 | }, 836 | ) => index == index_ && changes == changes_, 837 | ( 838 | VecDiffType::Inserted { index, changes }, 839 | VecDiffType::Inserted { 840 | index: ref index_, 841 | changes: ref changes_, 842 | }, 843 | ) => index == index_ && changes == changes_, 844 | _ => false, 845 | } 846 | } 847 | } 848 | 849 | impl Clone for VecDiffType 850 | where 851 | T::Repr: Clone, 852 | { 853 | fn clone(&self) -> Self { 854 | match self { 855 | VecDiffType::Removed { index, len } => VecDiffType::Removed { 856 | index: *index, 857 | len: *len, 858 | }, 859 | VecDiffType::Altered { index, changes } => VecDiffType::Altered { 860 | index: *index, 861 | changes: changes.clone(), 862 | }, 863 | VecDiffType::Inserted { index, changes } => VecDiffType::Inserted { 864 | index: *index, 865 | changes: changes.clone(), 866 | }, 867 | } 868 | } 869 | } 870 | 871 | impl Debug for VecDiff 872 | where 873 | T::Repr: Debug, 874 | { 875 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 876 | f.debug_list().entries(self.0.iter()).finish() 877 | } 878 | } 879 | 880 | impl PartialEq for VecDiff 881 | where 882 | T::Repr: PartialEq, 883 | { 884 | fn eq(&self, other: &Self) -> bool { 885 | self.0 == other.0 886 | } 887 | } 888 | 889 | impl Clone for VecDiff 890 | where 891 | T::Repr: Clone, 892 | { 893 | fn clone(&self) -> Self { 894 | Self(self.0.clone()) 895 | } 896 | } 897 | 898 | /// The type of change to make to an array 899 | #[derive(Serialize, Deserialize)] 900 | #[serde(bound(serialize = "T::Repr: Serialize"))] 901 | #[serde(bound(deserialize = "T::Repr: Deserialize<'de>"))] 902 | pub struct ArrayDiffType { 903 | pub index: usize, 904 | pub change: T::Repr, 905 | } 906 | 907 | /// The collection of difference-vec's 908 | #[derive(Serialize, Deserialize)] 909 | #[serde(bound(serialize = "T::Repr: Serialize"))] 910 | #[serde(bound(deserialize = "T::Repr: Deserialize<'de>"))] 911 | pub struct ArrayDiff(pub Vec>); 912 | 913 | impl Diff for [T; N] { 914 | type Repr = ArrayDiff; 915 | 916 | fn diff(&self, other: &Self) -> Self::Repr { 917 | ArrayDiff( 918 | self.iter() 919 | .zip(other.iter()) 920 | .enumerate() 921 | .filter_map(|(index, (self_el, other_el))| { 922 | self_el.ne(other_el).then(|| ArrayDiffType { 923 | index, 924 | change: self_el.diff(other_el), 925 | }) 926 | }) 927 | .collect(), 928 | ) 929 | } 930 | 931 | fn apply(&mut self, diff: &Self::Repr) { 932 | for ArrayDiffType { index, change } in &diff.0 { 933 | self[*index].apply(change); 934 | } 935 | } 936 | 937 | fn identity() -> Self { 938 | std::array::from_fn(|_| T::identity()) 939 | } 940 | } 941 | 942 | impl Debug for ArrayDiffType 943 | where 944 | T::Repr: Debug, 945 | { 946 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 947 | f.debug_struct("ArrayDiffType") 948 | .field("index", &self.index) 949 | .field("change", &self.change) 950 | .finish() 951 | } 952 | } 953 | 954 | impl PartialEq for ArrayDiffType 955 | where 956 | T::Repr: PartialEq, 957 | { 958 | fn eq(&self, other: &Self) -> bool { 959 | self.index == other.index && self.change == other.change 960 | } 961 | } 962 | 963 | impl Clone for ArrayDiffType 964 | where 965 | T::Repr: Clone, 966 | { 967 | fn clone(&self) -> Self { 968 | Self { 969 | index: self.index, 970 | change: self.change.clone(), 971 | } 972 | } 973 | } 974 | 975 | impl Debug for ArrayDiff 976 | where 977 | T::Repr: Debug, 978 | { 979 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 980 | f.debug_list().entries(self.0.iter()).finish() 981 | } 982 | } 983 | 984 | impl PartialEq for ArrayDiff 985 | where 986 | T::Repr: PartialEq, 987 | { 988 | fn eq(&self, other: &Self) -> bool { 989 | self.0 == other.0 990 | } 991 | } 992 | 993 | impl Clone for ArrayDiff 994 | where 995 | T::Repr: Clone, 996 | { 997 | fn clone(&self) -> Self { 998 | Self(self.0.clone()) 999 | } 1000 | } 1001 | 1002 | impl Diff for PhantomData { 1003 | type Repr = PhantomData; 1004 | 1005 | fn diff(&self, _other: &Self) -> Self::Repr { 1006 | PhantomData::default() 1007 | } 1008 | 1009 | fn apply(&mut self, _diff: &Self::Repr) {} 1010 | 1011 | fn identity() -> Self { 1012 | PhantomData::default() 1013 | } 1014 | } 1015 | --------------------------------------------------------------------------------