├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benchmarks ├── .gitignore ├── Cargo.toml ├── benches │ ├── basic.rs │ └── large.rs ├── rust-toolchain.toml └── src │ ├── basic │ ├── apply.rs │ ├── full.rs │ ├── generate.rs │ ├── mod.rs │ └── mutate.rs │ ├── large │ ├── apply.rs │ ├── full.rs │ ├── generate.rs │ ├── mod.rs │ └── mutate.rs │ └── lib.rs ├── derive ├── .gitignore ├── Cargo.toml └── src │ ├── difference.rs │ ├── lib.rs │ ├── parse.rs │ └── shared.rs ├── src ├── collections │ ├── mod.rs │ ├── ordered_array_like.rs │ ├── rope │ │ ├── mod.rs │ │ └── slots.rs │ ├── unordered_array_like.rs │ ├── unordered_map_like.rs │ └── unordered_map_like_recursive.rs └── lib.rs └── tests ├── derives.rs ├── enums.rs ├── expose.rs ├── integration.rs └── types.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: CI 4 | 5 | jobs: 6 | rustfmt: 7 | name: Rustfmt 8 | runs-on: ${{ matrix.config.os }} 9 | strategy: 10 | fail-fast: true 11 | matrix: 12 | config: 13 | - { os: ubuntu-latest, target: 'x86_64-unknown-linux-gnu' } 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: stable 19 | target: ${{ matrix.config.target }} 20 | override: true 21 | - uses: actions-rs/cargo@v1 22 | with: 23 | command: fmt 24 | args: --check 25 | 26 | clippy: 27 | name: Clippy 28 | runs-on: ${{ matrix.config.os }} 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | config: 33 | - { os: ubuntu-latest, target: 'x86_64-unknown-linux-gnu' } 34 | steps: 35 | - uses: actions/checkout@v2 36 | - uses: actions-rs/toolchain@v1 37 | with: 38 | toolchain: stable 39 | target: ${{ matrix.config.target }} 40 | override: true 41 | - uses: actions-rs/cargo@v1 42 | with: 43 | command: clippy 44 | 45 | test_all: 46 | name: TestAll 47 | runs-on: ${{ matrix.config.os }} 48 | strategy: 49 | fail-fast: false 50 | matrix: 51 | config: 52 | - { os: ubuntu-latest, target: 'x86_64-unknown-linux-gnu' } 53 | - { os: macos-latest, target: 'x86_64-apple-darwin' } 54 | - { os: windows-latest, target: 'x86_64-pc-windows-msvc' } 55 | 56 | steps: 57 | - uses: actions/checkout@v2 58 | - uses: actions-rs/toolchain@v1 59 | with: 60 | toolchain: stable 61 | target: ${{ matrix.config.target }} 62 | override: true 63 | - uses: actions-rs/cargo@v1 64 | with: 65 | command: test 66 | args: --all-features 67 | 68 | test_serde: 69 | name: TestSerde 70 | runs-on: ${{ matrix.config.os }} 71 | strategy: 72 | fail-fast: false 73 | matrix: 74 | config: 75 | - { os: ubuntu-latest, target: 'x86_64-unknown-linux-gnu' } 76 | - { os: macos-latest, target: 'x86_64-apple-darwin' } 77 | - { os: windows-latest, target: 'x86_64-pc-windows-msvc' } 78 | 79 | steps: 80 | - uses: actions/checkout@v2 81 | - uses: actions-rs/toolchain@v1 82 | with: 83 | toolchain: stable 84 | target: ${{ matrix.config.target }} 85 | override: true 86 | - uses: actions-rs/cargo@v1 87 | with: 88 | command: test 89 | args: --features serde 90 | 91 | test_setters: 92 | name: TestSetters 93 | runs-on: ${{ matrix.config.os }} 94 | strategy: 95 | fail-fast: false 96 | matrix: 97 | config: 98 | - { os: ubuntu-latest, target: 'x86_64-unknown-linux-gnu' } 99 | - { os: macos-latest, target: 'x86_64-apple-darwin' } 100 | - { os: windows-latest, target: 'x86_64-pc-windows-msvc' } 101 | 102 | steps: 103 | - uses: actions/checkout@v2 104 | - uses: actions-rs/toolchain@v1 105 | with: 106 | toolchain: stable 107 | target: ${{ matrix.config.target }} 108 | override: true 109 | - uses: actions-rs/cargo@v1 110 | with: 111 | command: test 112 | args: --features generated_setters 113 | 114 | test_min: 115 | name: TestMin 116 | runs-on: ${{ matrix.config.os }} 117 | strategy: 118 | fail-fast: false 119 | matrix: 120 | config: 121 | - { os: ubuntu-latest, target: 'x86_64-unknown-linux-gnu' } 122 | - { os: macos-latest, target: 'x86_64-apple-darwin' } 123 | - { os: windows-latest, target: 'x86_64-pc-windows-msvc' } 124 | 125 | steps: 126 | - uses: actions/checkout@v2 127 | - uses: actions-rs/toolchain@v1 128 | with: 129 | toolchain: stable 130 | target: ${{ matrix.config.target }} 131 | override: true 132 | - uses: actions-rs/cargo@v1 133 | with: 134 | command: test 135 | 136 | build_release: 137 | name: BuildRelease 138 | runs-on: ${{ matrix.config.os }} 139 | strategy: 140 | fail-fast: false 141 | matrix: 142 | config: 143 | - { os: ubuntu-latest, target: 'x86_64-unknown-linux-gnu' } 144 | - { os: macos-latest, target: 'x86_64-apple-darwin' } 145 | - { os: windows-latest, target: 'x86_64-pc-windows-msvc' } 146 | 147 | steps: 148 | - uses: actions/checkout@v2 149 | - uses: actions-rs/toolchain@v1 150 | with: 151 | toolchain: stable 152 | target: ${{ matrix.config.target }} 153 | override: true 154 | - uses: actions-rs/cargo@v1 155 | with: 156 | command: build 157 | args: --release --all-features 158 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /.vscode -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "structdiff" 3 | version = "0.7.3" 4 | edition = "2021" 5 | license = "Apache-2.0 OR MIT" 6 | repository = "https://github.com/knickish/structdiff" 7 | description = """zero-dependency crate for generating and applying partial diffs between struct instances""" 8 | keywords = ["delta-compression", "difference"] 9 | categories = ["compression"] 10 | rust-version = "1.82.0" 11 | 12 | [dependencies] 13 | nanoserde = { version = "^0.1.37", optional = true } 14 | rustc-hash = { version = "1.1.0", optional = true } 15 | serde = { version = "^1.0.0", optional = true, features = ["derive"] } 16 | structdiff-derive = { path = "derive", version = "=0.7.3" } 17 | 18 | [features] 19 | "default" = [] 20 | "nanoserde" = ["dep:nanoserde", "structdiff-derive/nanoserde"] 21 | "serde" = ["dep:serde", "structdiff-derive/serde"] 22 | "debug_diffs" = ["structdiff-derive/debug_diffs"] 23 | "generated_setters" = ["structdiff-derive/generated_setters"] 24 | "rustc_hash" = ["dep:rustc-hash"] 25 | "debug_asserts" = [] 26 | 27 | [dev-dependencies] 28 | bincode = "1.3.3" 29 | assert_unordered = "0.3.5" 30 | nanorand = "0.7.0" 31 | pretty_assertions = "1.4.1" 32 | 33 | [lints.rust] 34 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(unused)'] } 35 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 Kirk Nickish 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # structdiff 2 | 3 | A lightweight, zero-dependency struct diffing library which allows changed fields to be collected and applied. Derive `Difference` on a struct, then use the `StructDiff` trait to make and apply diffs. Supports optional serialization of the generated diff types with `serde` or `nanoserde` for ease of use. 4 | 5 | [![Crates.io][crates_img]][crates_lnk] 6 | 7 | [crates_img]: https://img.shields.io/crates/v/structdiff.svg 8 | [crates_lnk]: https://crates.io/crates/structdiff 9 | 10 | ## Example: 11 | 12 | ```rust 13 | use structdiff::{Difference, StructDiff}; 14 | 15 | #[derive(Debug, PartialEq, Clone, Difference)] 16 | struct Example { 17 | field1: f64, 18 | #[difference(skip)] 19 | field2: Vec, 20 | #[difference(collection_strategy="unordered_array_like")] 21 | field3: BTreeSet, 22 | } 23 | 24 | let first = Example { 25 | field1: 0.0, 26 | field2: vec![], 27 | field3: vec![1, 2, 3].into_iter().collect(), 28 | }; 29 | 30 | let second = Example { 31 | field1: 3.14, 32 | field2: vec![1], 33 | field3: vec![2, 3, 4].into_iter().collect(), 34 | }; 35 | 36 | let diffs = first.diff(&second); 37 | // diffs is now a Vec of differences between the two instances, 38 | // with length equal to number of changed/unskipped fields 39 | assert_eq!(diffs.len(), 2); 40 | 41 | let diffed = first.apply(diffs); 42 | // diffed is now equal to second, except for skipped field 43 | assert_eq!(diffed.field1, second.field1); 44 | assert_eq!(&diffed.field3, &second.field3); 45 | assert_ne!(diffed, second); 46 | ``` 47 | 48 | For more examples take a look at [integration tests](/tests) 49 | 50 | ## Derive macro attributes 51 | - Field level 52 | - `#[difference(skip)]` - Do not consider this field when creating a diff 53 | - `#[difference(recurse)]` - Generate a StructDiff for this field when creating a diff 54 | - `#[difference(collection_strategy = {})]` 55 | - `"ordered_array_like"` - Generates a minimal changeset for ordered, array-like collections of items which implement `PartialEq`. (uses levenshtein difference) 56 | - `"unordered_array_like"` - Generates a minimal changeset for unordered, array-like collections of items which implement `Hash + Eq`. 57 | - `"unordered_map_like"` - Generates a minimal changeset for unordered, map-like collections for which the key implements `Hash + Eq`. 58 | - `#[difference(map_equality = {})]` - Used with `unordered_map_like` 59 | - `"key_only"` - only replace a key-value pair for which the key has changed 60 | - `"key_and_value"` - replace a key-value pair if either the key or value has changed 61 | - `#[difference(setter)]` - Generate setters for this struct field 62 | - `#[difference(setter_name = {})]` - Use this name instead of the default value when generating a setter for this field (used on field) 63 | - Struct Level 64 | - `#[difference(setters)]` - Generate setters for all fields in the struct 65 | - Example: for the `field1` of the `Example` struct used above, a function with the signature `set_field1_with_diff(&mut self, value: Option) -> Option<::Diff>` will be generated. Useful when a single field will be changed in a struct with many fields, as it saves the comparison of all other fields. 66 | - `#[difference(expose)]`/`#[difference(expose = "MyDiffTypeName")]` - expose the generated difference type (optionally, with the specified name) 67 | 68 | ## Optional features 69 | - [`nanoserde`, `serde`] - Serialization of `Difference` derived associated types. Allows diffs to easily be sent over network. 70 | - `debug_diffs` - Derive `Debug` on the generated diff type 71 | - `generated_setters` - Enable generation of setters for struct fields. These setters automatically return a diff if a field's value is changed by the assignment. 72 | - `rustc_hash` - Use the (non-cryptographic) hash implementation from the `rustc-hash` crate instead of the default hasher. Much faster diff generation for collections at the cost of a dependency. 73 | 74 | ### Development status 75 | This is being used actively for my own projects, although it's mostly working now. PRs will be accepted for either more tests or functionality. -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | flamegraph.svg 4 | perf.data* -------------------------------------------------------------------------------- /benchmarks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "structdiff-benchmarks" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | assert_unordered = "0.3.5" 10 | structdiff = { path = "..", features = ["serde", "debug_diffs", "__rope_benchmarks"] } 11 | nanorand = { version = "0.7.0" } 12 | diff-struct = { version = "0.5.3", optional = true} 13 | serde = { version = "^1.0.0", features = ["derive"] } 14 | serde-diff = { version = "0.4.1", optional = true} 15 | bincode = { version = "1.3.3" } 16 | criterion = "0.5.1" 17 | 18 | [features] 19 | default = ["compare"] 20 | compare = ["dep:serde-diff", "dep:diff-struct"] 21 | 22 | [profile.release] 23 | lto = "fat" 24 | opt-level = 3 25 | debug = true 26 | 27 | [profile.bench] 28 | lto = "fat" 29 | opt-level = 3 30 | debug = true 31 | 32 | [[bench]] 33 | name = "basic" 34 | harness = false 35 | 36 | [[bench]] 37 | name = "large" 38 | harness = false 39 | -------------------------------------------------------------------------------- /benchmarks/benches/basic.rs: -------------------------------------------------------------------------------- 1 | use criterion::criterion_main; 2 | 3 | extern crate structdiff_benchmarks; 4 | 5 | criterion_main!( 6 | structdiff_benchmarks::basic::apply::benches, 7 | structdiff_benchmarks::basic::generate::benches, 8 | structdiff_benchmarks::basic::mutate::benches, 9 | structdiff_benchmarks::basic::full::benches, 10 | ); 11 | -------------------------------------------------------------------------------- /benchmarks/benches/large.rs: -------------------------------------------------------------------------------- 1 | use criterion::criterion_main; 2 | 3 | extern crate structdiff_benchmarks; 4 | 5 | criterion_main!( 6 | structdiff_benchmarks::large::apply::benches, 7 | structdiff_benchmarks::large::generate::benches, 8 | structdiff_benchmarks::large::mutate::benches, 9 | structdiff_benchmarks::large::full::benches, 10 | ); 11 | -------------------------------------------------------------------------------- /benchmarks/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" -------------------------------------------------------------------------------- /benchmarks/src/basic/apply.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, criterion_group, BatchSize, Criterion}; 4 | use nanorand::WyRand; 5 | use structdiff::StructDiff; 6 | 7 | use crate::TestBench; 8 | 9 | const SAMPLE_SIZE: usize = 1000; 10 | const MEASUREMENT_TIME: Duration = Duration::from_secs(25); 11 | const SEED: u64 = 42; 12 | 13 | #[cfg(feature = "compare")] 14 | criterion_group!( 15 | benches, 16 | mutate_application, 17 | diff_struct_bench::mutate, 18 | serde_diff_bench::mutate 19 | ); 20 | #[cfg(not(feature = "compare"))] 21 | criterion_group!(benches, mutate_application); 22 | 23 | const GROUP_NAME: &str = "application"; 24 | 25 | fn mutate_application(c: &mut Criterion) { 26 | const BENCH_NAME: &str = "mutate_application"; 27 | 28 | let mut group = c.benchmark_group(GROUP_NAME); 29 | group 30 | .sample_size(SAMPLE_SIZE) 31 | .measurement_time(MEASUREMENT_TIME); 32 | group.bench_function(BENCH_NAME, |b| { 33 | b.iter_batched( 34 | || { 35 | let mut rng = WyRand::new_seed(SEED); 36 | let first = TestBench::generate_random(&mut rng); 37 | let second = first.clone().random_mutate(&mut rng); 38 | let diff = StructDiff::diff(&first, &second); 39 | (first, diff) 40 | }, 41 | |(first, diff)| { 42 | black_box(StructDiff::apply(first, diff)); 43 | }, 44 | BatchSize::LargeInput, 45 | ) 46 | }); 47 | group.finish(); 48 | } 49 | 50 | #[cfg(feature = "compare")] 51 | mod diff_struct_bench { 52 | use super::{ 53 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 54 | }; 55 | use criterion::BatchSize; 56 | use diff::Diff; 57 | 58 | pub(super) fn mutate(c: &mut Criterion) { 59 | const BENCH_NAME: &str = "diff_struct_mutate_application"; 60 | let mut group = c.benchmark_group(GROUP_NAME); 61 | group 62 | .sample_size(SAMPLE_SIZE) 63 | .measurement_time(MEASUREMENT_TIME); 64 | group.bench_function(BENCH_NAME, |b| { 65 | b.iter_batched( 66 | || { 67 | let mut rng = WyRand::new_seed(SEED); 68 | let first = black_box(TestBench::generate_random(&mut rng)); 69 | let second = black_box(first.clone().random_mutate(&mut rng)); 70 | let diff = Diff::diff(&first, &second); 71 | (first, diff) 72 | }, 73 | |(mut first, diff)| { 74 | black_box(Diff::apply(&mut first, &diff)); 75 | }, 76 | BatchSize::LargeInput, 77 | ) 78 | }); 79 | group.finish(); 80 | } 81 | } 82 | 83 | #[cfg(feature = "compare")] 84 | mod serde_diff_bench { 85 | use super::{ 86 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 87 | }; 88 | use bincode::Options; 89 | use criterion::BatchSize; 90 | 91 | pub(super) fn mutate(c: &mut Criterion) { 92 | const BENCH_NAME: &str = "serde_diff_mutate_application"; 93 | 94 | let mut group = c.benchmark_group(GROUP_NAME); 95 | group 96 | .sample_size(SAMPLE_SIZE) 97 | .measurement_time(MEASUREMENT_TIME); 98 | group.bench_function(BENCH_NAME, |b| { 99 | b.iter_batched( 100 | || { 101 | let mut rng = WyRand::new_seed(SEED); 102 | let first = black_box(TestBench::generate_random(&mut rng)); 103 | let second = black_box(first.clone().random_mutate(&mut rng)); 104 | let options = bincode::DefaultOptions::new() 105 | .with_fixint_encoding() 106 | .allow_trailing_bytes(); 107 | let diff = black_box( 108 | options 109 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 110 | .unwrap(), 111 | ); 112 | (first, diff, options) 113 | }, 114 | |(mut first, mut diff, options)| { 115 | let mut deserializer = 116 | black_box(bincode::Deserializer::from_slice(&mut diff[..], options)); 117 | serde_diff::Apply::apply(&mut deserializer, &mut first).unwrap(); 118 | }, 119 | BatchSize::LargeInput, 120 | ) 121 | }); 122 | group.finish(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /benchmarks/src/basic/full.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, criterion_group, Criterion}; 4 | use nanorand::WyRand; 5 | use structdiff::StructDiff; 6 | 7 | use crate::TestBench; 8 | 9 | const SAMPLE_SIZE: usize = 1000; 10 | const MEASUREMENT_TIME: Duration = Duration::from_secs(25); 11 | const SEED: u64 = 42; 12 | 13 | #[cfg(feature = "compare")] 14 | criterion_group!( 15 | benches, 16 | full, 17 | diff_struct_bench::bench, 18 | serde_diff_bench::bench 19 | ); 20 | #[cfg(not(feature = "compare"))] 21 | criterion_group!(benches, full); 22 | 23 | const GROUP_NAME: &str = "full"; 24 | 25 | fn full(c: &mut Criterion) { 26 | const BENCH_NAME: &str = "full"; 27 | let mut rng = WyRand::new_seed(SEED); 28 | let mut first = black_box(TestBench::generate_random(&mut rng)); 29 | 30 | let second = black_box(TestBench::generate_random(&mut rng)); 31 | let mut diff: Vec<::Diff> = Vec::new(); 32 | let mut group = c.benchmark_group(GROUP_NAME); 33 | group 34 | .sample_size(SAMPLE_SIZE) 35 | .measurement_time(MEASUREMENT_TIME); 36 | group.bench_function(BENCH_NAME, |b| { 37 | b.iter(|| { 38 | diff = black_box(StructDiff::diff(&first, &second)); 39 | black_box(first.apply_mut(diff.clone())); 40 | }) 41 | }); 42 | group.finish(); 43 | first.assert_eq(second, &diff); 44 | } 45 | 46 | #[cfg(feature = "compare")] 47 | mod diff_struct_bench { 48 | use super::{ 49 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 50 | }; 51 | use diff::Diff; 52 | 53 | pub(super) fn bench(c: &mut Criterion) { 54 | const BENCH_NAME: &str = "diff_struct_full"; 55 | 56 | let mut rng = WyRand::new_seed(SEED); 57 | let mut first = black_box(TestBench::generate_random(&mut rng)); 58 | let second = black_box(TestBench::generate_random(&mut rng)); 59 | let mut group = c.benchmark_group(GROUP_NAME); 60 | group 61 | .sample_size(SAMPLE_SIZE) 62 | .measurement_time(MEASUREMENT_TIME); 63 | group.bench_function(BENCH_NAME, |b| { 64 | b.iter(|| { 65 | let diff = black_box(Diff::diff(&first, &second)); 66 | black_box(Diff::apply(&mut first, &diff)) 67 | }) 68 | }); 69 | group.finish(); 70 | assert_eq!(first.b, second.b); 71 | } 72 | } 73 | 74 | #[cfg(feature = "compare")] 75 | mod serde_diff_bench { 76 | use super::{ 77 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 78 | }; 79 | use bincode::Options; 80 | 81 | pub(super) fn bench(c: &mut Criterion) { 82 | const BENCH_NAME: &str = "serde_diff_full"; 83 | 84 | let mut rng = WyRand::new_seed(SEED); 85 | let mut first = black_box(TestBench::generate_random(&mut rng)); 86 | let second = black_box(TestBench::generate_random(&mut rng)); 87 | let options = bincode::DefaultOptions::new() 88 | .with_fixint_encoding() 89 | .allow_trailing_bytes(); 90 | let mut group = c.benchmark_group(GROUP_NAME); 91 | group 92 | .sample_size(SAMPLE_SIZE) 93 | .measurement_time(MEASUREMENT_TIME); 94 | group.bench_function(BENCH_NAME, |b| { 95 | b.iter(|| { 96 | let mut diff = black_box( 97 | options 98 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 99 | .unwrap(), 100 | ); 101 | let mut deserializer = 102 | black_box(bincode::Deserializer::from_slice(&mut diff[..], options)); 103 | serde_diff::Apply::apply(&mut deserializer, &mut first).unwrap(); 104 | }) 105 | }); 106 | group.finish(); 107 | assert_eq!(first.b, second.b); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /benchmarks/src/basic/generate.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, criterion_group, BatchSize, Criterion}; 4 | use nanorand::WyRand; 5 | use structdiff::StructDiff; 6 | 7 | use crate::TestBench; 8 | 9 | const SAMPLE_SIZE: usize = 1000; 10 | const MEASUREMENT_TIME: Duration = Duration::from_secs(25); 11 | const SEED: u64 = 42; 12 | 13 | #[cfg(feature = "compare")] 14 | criterion_group!( 15 | benches, 16 | mutate_generation_ref, 17 | mutate_generation_owned, 18 | diff_struct_bench::mutate, 19 | serde_diff_bench::mutate 20 | ); 21 | #[cfg(not(feature = "compare"))] 22 | criterion_group!(benches, mutate_generation_ref, mutate_generation_owned); 23 | 24 | const GROUP_NAME: &str = "generation"; 25 | 26 | fn mutate_generation_ref(c: &mut Criterion) { 27 | const BENCH_NAME: &str = "mutate_ref"; 28 | let mut group = c.benchmark_group(GROUP_NAME); 29 | group 30 | .sample_size(SAMPLE_SIZE) 31 | .measurement_time(MEASUREMENT_TIME); 32 | group.bench_function(BENCH_NAME, |b| { 33 | b.iter_batched( 34 | || { 35 | let mut rng = WyRand::new_seed(SEED); 36 | let first = TestBench::generate_random(&mut rng); 37 | let second = first.clone().random_mutate(&mut rng); 38 | (first, second) 39 | }, 40 | |(first, second)| { 41 | black_box(StructDiff::diff_ref(&first, &second)); 42 | }, 43 | BatchSize::LargeInput, 44 | ) 45 | }); 46 | group.finish(); 47 | } 48 | 49 | fn mutate_generation_owned(c: &mut Criterion) { 50 | const BENCH_NAME: &str = "mutate_owned"; 51 | let mut group = c.benchmark_group(GROUP_NAME); 52 | group 53 | .sample_size(SAMPLE_SIZE) 54 | .measurement_time(MEASUREMENT_TIME); 55 | group.bench_function(BENCH_NAME, |b| { 56 | b.iter_batched( 57 | || { 58 | let mut rng = WyRand::new_seed(SEED); 59 | let first = TestBench::generate_random(&mut rng); 60 | let second = first.clone().random_mutate(&mut rng); 61 | (first, second) 62 | }, 63 | |(first, second)| { 64 | black_box(StructDiff::diff(&first, &second)); 65 | }, 66 | BatchSize::LargeInput, 67 | ) 68 | }); 69 | group.finish(); 70 | } 71 | 72 | #[cfg(feature = "compare")] 73 | mod diff_struct_bench { 74 | use super::{ 75 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 76 | }; 77 | use criterion::BatchSize; 78 | use diff::Diff; 79 | 80 | pub(super) fn mutate(c: &mut Criterion) { 81 | const BENCH_NAME: &str = "diff_struct_mutate"; 82 | let mut group = c.benchmark_group(GROUP_NAME); 83 | group 84 | .sample_size(SAMPLE_SIZE) 85 | .measurement_time(MEASUREMENT_TIME); 86 | group.bench_function(BENCH_NAME, |b| { 87 | b.iter_batched( 88 | || { 89 | let mut rng = WyRand::new_seed(SEED); 90 | let first = TestBench::generate_random(&mut rng); 91 | let second = first.clone().random_mutate(&mut rng); 92 | (first, second) 93 | }, 94 | |(first, second)| { 95 | black_box(Diff::diff(&first, &second)); 96 | }, 97 | BatchSize::LargeInput, 98 | ) 99 | }); 100 | group.finish(); 101 | } 102 | } 103 | 104 | #[cfg(feature = "compare")] 105 | mod serde_diff_bench { 106 | use super::{ 107 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 108 | }; 109 | use bincode::Options; 110 | use criterion::BatchSize; 111 | 112 | pub(super) fn mutate(c: &mut Criterion) { 113 | const BENCH_NAME: &str = "serde_diff_mutate"; 114 | 115 | let mut group = c.benchmark_group(GROUP_NAME); 116 | group 117 | .sample_size(SAMPLE_SIZE) 118 | .measurement_time(MEASUREMENT_TIME); 119 | group.bench_function(BENCH_NAME, |b| { 120 | b.iter_batched( 121 | || { 122 | let mut rng = WyRand::new_seed(SEED); 123 | let first = TestBench::generate_random(&mut rng); 124 | let second = first.clone().random_mutate(&mut rng); 125 | let options = bincode::DefaultOptions::new() 126 | .with_fixint_encoding() 127 | .allow_trailing_bytes(); 128 | 129 | (first, second, options) 130 | }, 131 | |(first, second, options)| { 132 | black_box( 133 | options 134 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 135 | .unwrap(), 136 | ); 137 | }, 138 | BatchSize::LargeInput, 139 | ) 140 | }); 141 | group.finish(); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /benchmarks/src/basic/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod apply; 2 | pub mod full; 3 | pub mod generate; 4 | pub mod mutate; 5 | -------------------------------------------------------------------------------- /benchmarks/src/basic/mutate.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, criterion_group, BatchSize, Criterion}; 4 | use nanorand::WyRand; 5 | use structdiff::StructDiff; 6 | 7 | use crate::TestBench; 8 | 9 | const SAMPLE_SIZE: usize = 1000; 10 | const MEASUREMENT_TIME: Duration = Duration::from_secs(25); 11 | const SEED: u64 = 42; 12 | 13 | #[cfg(feature = "compare")] 14 | criterion_group!( 15 | benches, 16 | mutate, 17 | diff_struct_bench::mutate, 18 | serde_diff_bench::mutate 19 | ); 20 | #[cfg(not(feature = "compare"))] 21 | criterion_group!(benches, mutate); 22 | 23 | const GROUP_NAME: &str = "mutate"; 24 | 25 | fn mutate(c: &mut Criterion) { 26 | const BENCH_NAME: &str = "mutate"; 27 | let mut group = c.benchmark_group(GROUP_NAME); 28 | group 29 | .sample_size(SAMPLE_SIZE) 30 | .measurement_time(MEASUREMENT_TIME); 31 | group.bench_function(BENCH_NAME, |b| { 32 | b.iter_batched( 33 | || { 34 | let mut rng = WyRand::new_seed(SEED); 35 | let first = TestBench::generate_random(&mut rng); 36 | let second = first.clone().random_mutate(&mut rng); 37 | (first, second) 38 | }, 39 | |(first, second)| { 40 | let diff = black_box(StructDiff::diff(&first, &second)); 41 | black_box(StructDiff::apply(first, diff)); 42 | }, 43 | BatchSize::LargeInput, 44 | ) 45 | }); 46 | group.finish(); 47 | } 48 | 49 | #[cfg(feature = "compare")] 50 | mod diff_struct_bench { 51 | use super::{ 52 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 53 | }; 54 | use criterion::BatchSize; 55 | use diff::Diff; 56 | 57 | pub(super) fn mutate(c: &mut Criterion) { 58 | const BENCH_NAME: &str = "diff_struct_mutate"; 59 | 60 | let mut group = c.benchmark_group(GROUP_NAME); 61 | group 62 | .sample_size(SAMPLE_SIZE) 63 | .measurement_time(MEASUREMENT_TIME); 64 | group.bench_function(BENCH_NAME, |b| { 65 | b.iter_batched( 66 | || { 67 | let mut rng = WyRand::new_seed(SEED); 68 | let first = black_box(TestBench::generate_random(&mut rng)); 69 | let second = black_box(first.clone().random_mutate(&mut rng)); 70 | (first, second) 71 | }, 72 | |(mut first, second)| { 73 | let diff = black_box(Diff::diff(&first, &second)); 74 | black_box(Diff::apply(&mut first, &diff)) 75 | }, 76 | BatchSize::LargeInput, 77 | ) 78 | }); 79 | group.finish(); 80 | } 81 | } 82 | 83 | #[cfg(feature = "compare")] 84 | mod serde_diff_bench { 85 | use super::{ 86 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 87 | }; 88 | use bincode::Options; 89 | use criterion::BatchSize; 90 | 91 | pub(super) fn mutate(c: &mut Criterion) { 92 | const BENCH_NAME: &str = "serde_diff_mutate"; 93 | let mut group = c.benchmark_group(GROUP_NAME); 94 | group 95 | .sample_size(SAMPLE_SIZE) 96 | .measurement_time(MEASUREMENT_TIME); 97 | group.bench_function(BENCH_NAME, |b| { 98 | b.iter_batched( 99 | || { 100 | let mut rng = WyRand::new_seed(SEED); 101 | let first = black_box(TestBench::generate_random(&mut rng)); 102 | let second = black_box(first.clone().random_mutate(&mut rng)); 103 | let options = bincode::DefaultOptions::new() 104 | .with_fixint_encoding() 105 | .allow_trailing_bytes(); 106 | (first, second, options) 107 | }, 108 | |(mut first, second, options)| { 109 | let mut diff = black_box( 110 | options 111 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 112 | .unwrap(), 113 | ); 114 | let mut deserializer = 115 | black_box(bincode::Deserializer::from_slice(&mut diff[..], options)); 116 | serde_diff::Apply::apply(&mut deserializer, &mut first).unwrap(); 117 | }, 118 | BatchSize::LargeInput, 119 | ) 120 | }); 121 | group.finish(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /benchmarks/src/large/apply.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, criterion_group, BatchSize, Criterion}; 4 | use nanorand::WyRand; 5 | use structdiff::StructDiff; 6 | 7 | use crate::TestBench; 8 | const SAMPLE_SIZE: usize = 1000; 9 | const MEASUREMENT_TIME: Duration = Duration::from_secs(25); 10 | const SEED: u64 = 42; 11 | 12 | #[cfg(feature = "compare")] 13 | criterion_group!( 14 | benches, 15 | mutate_application, 16 | diff_struct_bench::mutate, 17 | serde_diff_bench::mutate 18 | ); 19 | #[cfg(not(feature = "compare"))] 20 | criterion_group!(benches, mutate_application); 21 | 22 | const GROUP_NAME: &str = "large_application"; 23 | 24 | fn mutate_application(c: &mut Criterion) { 25 | const BENCH_NAME: &str = "mutate_application"; 26 | 27 | let mut group = c.benchmark_group(GROUP_NAME); 28 | group 29 | .sample_size(SAMPLE_SIZE) 30 | .measurement_time(MEASUREMENT_TIME); 31 | group.bench_function(BENCH_NAME, |b| { 32 | b.iter_batched( 33 | || { 34 | let mut rng = WyRand::new_seed(SEED); 35 | let first = TestBench::generate_random_large(&mut rng); 36 | let second = first.clone().random_mutate_large(&mut rng); 37 | let diff = StructDiff::diff(&first, &second); 38 | (first, diff) 39 | }, 40 | |(first, diff)| { 41 | black_box(StructDiff::apply(first, diff)); 42 | }, 43 | BatchSize::LargeInput, 44 | ) 45 | }); 46 | group.finish(); 47 | } 48 | 49 | #[cfg(feature = "compare")] 50 | mod diff_struct_bench { 51 | use super::{ 52 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 53 | }; 54 | use criterion::BatchSize; 55 | use diff::Diff; 56 | 57 | pub(super) fn mutate(c: &mut Criterion) { 58 | const BENCH_NAME: &str = "diff_struct_mutate"; 59 | let mut group = c.benchmark_group(GROUP_NAME); 60 | group 61 | .sample_size(SAMPLE_SIZE) 62 | .measurement_time(MEASUREMENT_TIME); 63 | group.bench_function(BENCH_NAME, |b| { 64 | b.iter_batched( 65 | || { 66 | let mut rng = WyRand::new_seed(SEED); 67 | let first = black_box(TestBench::generate_random_large(&mut rng)); 68 | let second = black_box(first.clone().random_mutate_large(&mut rng)); 69 | let diff = Diff::diff(&first, &second); 70 | (first, diff) 71 | }, 72 | |(mut first, diff)| { 73 | black_box(Diff::apply(&mut first, &diff)); 74 | }, 75 | BatchSize::LargeInput, 76 | ) 77 | }); 78 | group.finish(); 79 | } 80 | } 81 | 82 | #[cfg(feature = "compare")] 83 | mod serde_diff_bench { 84 | use super::{ 85 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 86 | }; 87 | use bincode::Options; 88 | use criterion::BatchSize; 89 | 90 | pub(super) fn mutate(c: &mut Criterion) { 91 | const BENCH_NAME: &str = "serde_diff_mutate"; 92 | 93 | let mut group = c.benchmark_group(GROUP_NAME); 94 | group 95 | .sample_size(SAMPLE_SIZE) 96 | .measurement_time(MEASUREMENT_TIME); 97 | group.bench_function(BENCH_NAME, |b| { 98 | b.iter_batched( 99 | || { 100 | let mut rng = WyRand::new_seed(SEED); 101 | let first = black_box(TestBench::generate_random_large(&mut rng)); 102 | let second = black_box(first.clone().random_mutate_large(&mut rng)); 103 | let options = bincode::DefaultOptions::new() 104 | .with_fixint_encoding() 105 | .allow_trailing_bytes(); 106 | let diff = black_box( 107 | options 108 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 109 | .unwrap(), 110 | ); 111 | (first, diff, options) 112 | }, 113 | |(mut first, mut diff, options)| { 114 | let mut deserializer = 115 | black_box(bincode::Deserializer::from_slice(&mut diff[..], options)); 116 | serde_diff::Apply::apply(&mut deserializer, &mut first).unwrap(); 117 | }, 118 | BatchSize::LargeInput, 119 | ) 120 | }); 121 | group.finish(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /benchmarks/src/large/full.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, criterion_group, Criterion}; 4 | use nanorand::WyRand; 5 | use structdiff::StructDiff; 6 | 7 | use crate::TestBench; 8 | const SAMPLE_SIZE: usize = 1000; 9 | const MEASUREMENT_TIME: Duration = Duration::from_secs(25); 10 | const SEED: u64 = 42; 11 | 12 | #[cfg(feature = "compare")] 13 | criterion_group!( 14 | benches, 15 | full, 16 | diff_struct_bench::bench, 17 | serde_diff_bench::bench 18 | ); 19 | #[cfg(not(feature = "compare"))] 20 | criterion_group!(benches, full); 21 | 22 | const GROUP_NAME: &str = "large_full"; 23 | 24 | fn full(c: &mut Criterion) { 25 | const BENCH_NAME: &str = "owned"; 26 | let mut rng = WyRand::new_seed(SEED); 27 | let mut first = black_box(TestBench::generate_random_large(&mut rng)); 28 | 29 | let second = black_box(TestBench::generate_random_large(&mut rng)); 30 | let mut diff: Vec<::Diff> = Vec::new(); 31 | let mut group = c.benchmark_group(GROUP_NAME); 32 | group 33 | .sample_size(SAMPLE_SIZE) 34 | .measurement_time(MEASUREMENT_TIME); 35 | group.bench_function(BENCH_NAME, |b| { 36 | b.iter(|| { 37 | diff = black_box(StructDiff::diff(&first, &second)); 38 | black_box(first.apply_mut(diff.clone())); 39 | }) 40 | }); 41 | group.finish(); 42 | first.assert_eq(second, &diff); 43 | } 44 | 45 | #[cfg(feature = "compare")] 46 | mod diff_struct_bench { 47 | use super::{ 48 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 49 | }; 50 | use diff::Diff; 51 | 52 | pub(super) fn bench(c: &mut Criterion) { 53 | const BENCH_NAME: &str = "diff_struct_full"; 54 | let mut rng = WyRand::new_seed(SEED); 55 | let mut first = black_box(TestBench::generate_random_large(&mut rng)); 56 | let second = black_box(TestBench::generate_random_large(&mut rng)); 57 | let mut group = c.benchmark_group(GROUP_NAME); 58 | group 59 | .sample_size(SAMPLE_SIZE) 60 | .measurement_time(MEASUREMENT_TIME); 61 | group.bench_function(BENCH_NAME, |b| { 62 | b.iter(|| { 63 | let diff = black_box(Diff::diff(&first, &second)); 64 | black_box(Diff::apply(&mut first, &diff)) 65 | }) 66 | }); 67 | group.finish(); 68 | assert_eq!(first.b, second.b); 69 | } 70 | } 71 | 72 | #[cfg(feature = "compare")] 73 | mod serde_diff_bench { 74 | use super::{ 75 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 76 | }; 77 | use bincode::Options; 78 | 79 | pub(super) fn bench(c: &mut Criterion) { 80 | const BENCH_NAME: &str = "serde_diff_full"; 81 | let mut rng = WyRand::new_seed(SEED); 82 | let mut first = black_box(TestBench::generate_random_large(&mut rng)); 83 | let second = black_box(TestBench::generate_random_large(&mut rng)); 84 | let options = bincode::DefaultOptions::new() 85 | .with_fixint_encoding() 86 | .allow_trailing_bytes(); 87 | let mut group = c.benchmark_group(GROUP_NAME); 88 | group 89 | .sample_size(SAMPLE_SIZE) 90 | .measurement_time(MEASUREMENT_TIME); 91 | group.bench_function(BENCH_NAME, |b| { 92 | b.iter(|| { 93 | let mut diff = black_box( 94 | options 95 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 96 | .unwrap(), 97 | ); 98 | let mut deserializer = 99 | black_box(bincode::Deserializer::from_slice(&mut diff[..], options)); 100 | serde_diff::Apply::apply(&mut deserializer, &mut first).unwrap(); 101 | }) 102 | }); 103 | group.finish(); 104 | assert_eq!(first.b, second.b); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /benchmarks/src/large/generate.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, criterion_group, BatchSize, Criterion}; 4 | use nanorand::WyRand; 5 | use structdiff::StructDiff; 6 | 7 | use crate::TestBench; 8 | const SAMPLE_SIZE: usize = 1000; 9 | const MEASUREMENT_TIME: Duration = Duration::from_secs(25); 10 | const SEED: u64 = 42; 11 | 12 | #[cfg(feature = "compare")] 13 | criterion_group!( 14 | benches, 15 | mutate_generation_ref, 16 | mutate_generation_owned, 17 | diff_struct_bench::mutate, 18 | serde_diff_bench::mutate 19 | ); 20 | #[cfg(not(feature = "compare"))] 21 | criterion_group!(benches, mutate_generation_ref, mutate_generation_owned); 22 | 23 | const GROUP_NAME: &str = "large_generation"; 24 | 25 | fn mutate_generation_ref(c: &mut Criterion) { 26 | const BENCH_NAME: &str = "mutate_ref"; 27 | let mut group = c.benchmark_group(GROUP_NAME); 28 | group 29 | .sample_size(SAMPLE_SIZE) 30 | .measurement_time(MEASUREMENT_TIME); 31 | group.bench_function(BENCH_NAME, |b| { 32 | b.iter_batched( 33 | || { 34 | let mut rng = WyRand::new_seed(SEED); 35 | let first = TestBench::generate_random_large(&mut rng); 36 | let second = first.clone().random_mutate_large(&mut rng); 37 | (first, second) 38 | }, 39 | |(first, second)| { 40 | black_box(StructDiff::diff_ref(&first, &second)); 41 | }, 42 | BatchSize::LargeInput, 43 | ) 44 | }); 45 | group.finish(); 46 | } 47 | 48 | fn mutate_generation_owned(c: &mut Criterion) { 49 | const BENCH_NAME: &str = "mutate_owned"; 50 | let mut group = c.benchmark_group(GROUP_NAME); 51 | group 52 | .sample_size(SAMPLE_SIZE) 53 | .measurement_time(MEASUREMENT_TIME); 54 | group.bench_function(BENCH_NAME, |b| { 55 | b.iter_batched( 56 | || { 57 | let mut rng = WyRand::new_seed(SEED); 58 | let first = TestBench::generate_random_large(&mut rng); 59 | let second = first.clone().random_mutate_large(&mut rng); 60 | (first, second) 61 | }, 62 | |(first, second)| { 63 | black_box(StructDiff::diff(&first, &second)); 64 | }, 65 | BatchSize::LargeInput, 66 | ) 67 | }); 68 | group.finish(); 69 | } 70 | 71 | #[cfg(feature = "compare")] 72 | mod diff_struct_bench { 73 | use super::{ 74 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 75 | }; 76 | use criterion::BatchSize; 77 | use diff::Diff; 78 | 79 | pub(super) fn mutate(c: &mut Criterion) { 80 | const BENCH_NAME: &str = "diff_struct_mutate"; 81 | let mut group = c.benchmark_group(GROUP_NAME); 82 | group 83 | .sample_size(SAMPLE_SIZE) 84 | .measurement_time(MEASUREMENT_TIME); 85 | group.bench_function(BENCH_NAME, |b| { 86 | b.iter_batched( 87 | || { 88 | let mut rng = WyRand::new_seed(SEED); 89 | let first = TestBench::generate_random_large(&mut rng); 90 | let second = first.clone().random_mutate_large(&mut rng); 91 | (first, second) 92 | }, 93 | |(first, second)| { 94 | black_box(Diff::diff(&first, &second)); 95 | }, 96 | BatchSize::LargeInput, 97 | ) 98 | }); 99 | group.finish(); 100 | } 101 | } 102 | 103 | #[cfg(feature = "compare")] 104 | mod serde_diff_bench { 105 | use super::{ 106 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 107 | }; 108 | use bincode::Options; 109 | use criterion::BatchSize; 110 | 111 | pub(super) fn mutate(c: &mut Criterion) { 112 | const BENCH_NAME: &str = "serde_diff_mutate"; 113 | 114 | let mut group = c.benchmark_group(GROUP_NAME); 115 | group 116 | .sample_size(SAMPLE_SIZE) 117 | .measurement_time(MEASUREMENT_TIME); 118 | group.bench_function(BENCH_NAME, |b| { 119 | b.iter_batched( 120 | || { 121 | let mut rng = WyRand::new_seed(SEED); 122 | let first = TestBench::generate_random_large(&mut rng); 123 | let second = first.clone().random_mutate_large(&mut rng); 124 | let options = bincode::DefaultOptions::new() 125 | .with_fixint_encoding() 126 | .allow_trailing_bytes(); 127 | 128 | (first, second, options) 129 | }, 130 | |(first, second, options)| { 131 | black_box( 132 | options 133 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 134 | .unwrap(), 135 | ); 136 | }, 137 | BatchSize::LargeInput, 138 | ) 139 | }); 140 | group.finish(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /benchmarks/src/large/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod apply; 2 | pub mod full; 3 | pub mod generate; 4 | pub mod mutate; 5 | -------------------------------------------------------------------------------- /benchmarks/src/large/mutate.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, criterion_group, BatchSize, Criterion}; 4 | use nanorand::WyRand; 5 | use structdiff::StructDiff; 6 | 7 | use crate::TestBench; 8 | const SAMPLE_SIZE: usize = 1000; 9 | const MEASUREMENT_TIME: Duration = Duration::from_secs(25); 10 | const SEED: u64 = 42; 11 | 12 | #[cfg(feature = "compare")] 13 | criterion_group!( 14 | benches, 15 | mutate, 16 | diff_struct_bench::mutate, 17 | serde_diff_bench::mutate 18 | ); 19 | #[cfg(not(feature = "compare"))] 20 | criterion_group!(benches, mutate); 21 | 22 | const GROUP_NAME: &str = "large_mutate"; 23 | 24 | fn mutate(c: &mut Criterion) { 25 | const BENCH_NAME: &str = "mutate"; 26 | let mut group = c.benchmark_group(GROUP_NAME); 27 | group 28 | .sample_size(SAMPLE_SIZE) 29 | .measurement_time(MEASUREMENT_TIME); 30 | group.bench_function(BENCH_NAME, |b| { 31 | b.iter_batched( 32 | || { 33 | let mut rng = WyRand::new_seed(SEED); 34 | let first = TestBench::generate_random_large(&mut rng); 35 | let second = first.clone().random_mutate_large(&mut rng); 36 | (first, second) 37 | }, 38 | |(first, second)| { 39 | let diff = black_box(StructDiff::diff(&first, &second)); 40 | black_box(StructDiff::apply(first, diff)); 41 | }, 42 | BatchSize::LargeInput, 43 | ) 44 | }); 45 | group.finish(); 46 | } 47 | 48 | #[cfg(feature = "compare")] 49 | mod diff_struct_bench { 50 | use super::{ 51 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 52 | }; 53 | use criterion::BatchSize; 54 | use diff::Diff; 55 | 56 | pub(super) fn mutate(c: &mut Criterion) { 57 | const BENCH_NAME: &str = "diff_struct_mutate"; 58 | 59 | let mut group = c.benchmark_group(GROUP_NAME); 60 | group 61 | .sample_size(SAMPLE_SIZE) 62 | .measurement_time(MEASUREMENT_TIME); 63 | group.bench_function(BENCH_NAME, |b| { 64 | b.iter_batched( 65 | || { 66 | let mut rng = WyRand::new_seed(SEED); 67 | let first = black_box(TestBench::generate_random_large(&mut rng)); 68 | let second = black_box(first.clone().random_mutate_large(&mut rng)); 69 | (first, second) 70 | }, 71 | |(mut first, second)| { 72 | let diff = black_box(Diff::diff(&first, &second)); 73 | black_box(Diff::apply(&mut first, &diff)) 74 | }, 75 | BatchSize::LargeInput, 76 | ) 77 | }); 78 | group.finish(); 79 | } 80 | } 81 | 82 | #[cfg(feature = "compare")] 83 | mod serde_diff_bench { 84 | use super::{ 85 | black_box, Criterion, TestBench, WyRand, GROUP_NAME, MEASUREMENT_TIME, SAMPLE_SIZE, SEED, 86 | }; 87 | use bincode::Options; 88 | use criterion::BatchSize; 89 | 90 | pub(super) fn mutate(c: &mut Criterion) { 91 | const BENCH_NAME: &str = "serde_diff_mutate"; 92 | let mut group = c.benchmark_group(GROUP_NAME); 93 | group 94 | .sample_size(SAMPLE_SIZE) 95 | .measurement_time(MEASUREMENT_TIME); 96 | group.bench_function(BENCH_NAME, |b| { 97 | b.iter_batched( 98 | || { 99 | let mut rng = WyRand::new_seed(SEED); 100 | let first = black_box(TestBench::generate_random_large(&mut rng)); 101 | let second = black_box(first.clone().random_mutate_large(&mut rng)); 102 | let options = bincode::DefaultOptions::new() 103 | .with_fixint_encoding() 104 | .allow_trailing_bytes(); 105 | (first, second, options) 106 | }, 107 | |(mut first, second, options)| { 108 | let mut diff = black_box( 109 | options 110 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 111 | .unwrap(), 112 | ); 113 | let mut deserializer = 114 | black_box(bincode::Deserializer::from_slice(&mut diff[..], options)); 115 | serde_diff::Apply::apply(&mut deserializer, &mut first).unwrap(); 116 | }, 117 | BatchSize::LargeInput, 118 | ) 119 | }); 120 | group.finish(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /benchmarks/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use assert_unordered::assert_eq_unordered_sort; 4 | use nanorand::{Rng, WyRand}; 5 | use structdiff::{Difference, StructDiff}; 6 | 7 | pub mod basic; 8 | pub mod large; 9 | 10 | #[derive(Debug, Difference, PartialEq, Clone, serde::Serialize, serde::Deserialize)] 11 | #[cfg_attr(feature = "compare", derive(diff::Diff))] 12 | #[cfg_attr(feature = "compare", diff(attr( 13 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 14 | )))] 15 | #[cfg_attr(feature = "compare", derive(serde_diff::SerdeDiff))] 16 | pub struct TestBench { 17 | pub a: String, 18 | pub b: i32, 19 | #[difference(collection_strategy = "unordered_array_like")] 20 | #[cfg_attr(feature = "compare", serde_diff(opaque))] 21 | pub c: HashSet, 22 | #[difference(collection_strategy = "ordered_array_like")] 23 | pub d: Vec, 24 | #[difference(collection_strategy = "unordered_map_like", map_equality = "key_only")] 25 | pub e: HashMap, 26 | #[difference( 27 | collection_strategy = "unordered_map_like", 28 | map_equality = "key_and_value" 29 | )] 30 | pub f: HashMap, 31 | } 32 | 33 | fn rand_string(rng: &mut WyRand) -> String { 34 | let base = vec![(); rng.generate_range::(5..15) as usize]; 35 | base.into_iter() 36 | .map(|_| rng.generate::() as u32) 37 | .filter_map(char::from_u32) 38 | .collect::() 39 | } 40 | 41 | fn rand_string_large(rng: &mut WyRand) -> String { 42 | let base = vec![(); rng.generate::() as usize]; 43 | base.into_iter() 44 | .map(|_| rng.generate::()) 45 | .filter_map(char::from_u32) 46 | .collect::() 47 | } 48 | 49 | impl TestBench { 50 | pub fn generate_random(rng: &mut WyRand) -> TestBench { 51 | TestBench { 52 | a: rand_string(rng), 53 | b: rng.generate::(), 54 | c: (0..rng.generate_range::(5..15)) 55 | .map(|_| rand_string(rng)) 56 | .into_iter() 57 | .collect(), 58 | d: (0..rng.generate_range::(5..15)) 59 | .map(|_| rand_string(rng)) 60 | .into_iter() 61 | .collect(), 62 | e: (0..rng.generate_range::(5..15)) 63 | .map(|_| (rng.generate::(), rand_string(rng))) 64 | .into_iter() 65 | .collect(), 66 | f: (0..rng.generate_range::(5..15)) 67 | .map(|_| (rng.generate::(), rand_string(rng))) 68 | .into_iter() 69 | .collect(), 70 | } 71 | } 72 | 73 | pub fn generate_random_large(rng: &mut WyRand) -> TestBench { 74 | TestBench { 75 | a: rand_string_large(rng), 76 | b: rng.generate::(), 77 | c: (0..rng.generate_range::(0..(u16::MAX / 5))) 78 | .map(|_| rand_string(rng)) 79 | .into_iter() 80 | .collect(), 81 | d: (0..rng.generate_range::(0..(u16::MAX / 5))) 82 | .map(|_| rand_string(rng)) 83 | .into_iter() 84 | .collect(), 85 | e: (0..rng.generate_range::(0..(u16::MAX / 5))) 86 | .map(|_| (rng.generate::(), rand_string(rng))) 87 | .into_iter() 88 | .collect(), 89 | f: (0..rng.generate_range::(0..(u16::MAX / 5))) 90 | .map(|_| (rng.generate::(), rand_string(rng))) 91 | .into_iter() 92 | .collect(), 93 | } 94 | } 95 | 96 | pub fn random_mutate(self, rng: &mut WyRand) -> Self { 97 | match rng.generate_range(0..6) { 98 | 0 => Self { 99 | a: rand_string(rng), 100 | ..self 101 | }, 102 | 1 => Self { 103 | b: rng.generate::(), 104 | ..self 105 | }, 106 | 2 => Self { 107 | c: self 108 | .c 109 | .into_iter() 110 | .filter(|_| rng.generate_range(0..100) < 30_u8) 111 | .collect::>() 112 | .into_iter() 113 | .map(|v| { 114 | if rng.generate_range(0..100) < 25_u8 { 115 | rand_string(rng) 116 | } else { 117 | v 118 | } 119 | }) 120 | .collect::>() 121 | .into_iter() 122 | .chain( 123 | (0..rng.generate_range::(0..(u8::MAX / 4))) 124 | .map(|_| rand_string(rng)), 125 | ) 126 | .collect(), 127 | ..self 128 | }, 129 | 3 => Self { 130 | d: self 131 | .d 132 | .into_iter() 133 | .filter(|_| rng.generate_range(0..100) < 30_u8) 134 | .collect::>() 135 | .into_iter() 136 | .map(|v| { 137 | if rng.generate_range(0..100) < 25_u8 { 138 | rand_string(rng) 139 | } else { 140 | v 141 | } 142 | }) 143 | .collect::>() 144 | .into_iter() 145 | .chain( 146 | (0..rng.generate_range::(0..(u8::MAX / 4))) 147 | .map(|_| rand_string(rng)), 148 | ) 149 | .collect(), 150 | ..self 151 | }, 152 | 4 => Self { 153 | e: self 154 | .e 155 | .into_iter() 156 | .filter(|_| rng.generate_range(0..100) < 25_u8) 157 | .collect::>() 158 | .into_iter() 159 | .map(|v| { 160 | if rng.generate_range(0..100) < 25_u8 { 161 | (rng.generate::(), rand_string(rng)) 162 | } else { 163 | v 164 | } 165 | }) 166 | .collect::>() 167 | .into_iter() 168 | .chain( 169 | (0..rng.generate_range::(0..(u8::MAX / 4))) 170 | .map(|_| (rng.generate::(), rand_string(rng))), 171 | ) 172 | .collect(), 173 | ..self 174 | }, 175 | 5 => Self { 176 | f: self 177 | .f 178 | .into_iter() 179 | .filter(|_| rng.generate_range(0..100) < 25_u8) 180 | .collect::>() 181 | .into_iter() 182 | .map(|v| { 183 | if rng.generate_range(0..100) < 25_u8 { 184 | (rng.generate::(), rand_string(rng)) 185 | } else { 186 | v 187 | } 188 | }) 189 | .collect::>() 190 | .into_iter() 191 | .chain( 192 | (0..rng.generate_range::(0..(u8::MAX / 4))) 193 | .map(|_| (rng.generate::(), rand_string(rng))), 194 | ) 195 | .collect(), 196 | ..self 197 | }, 198 | _ => self, 199 | } 200 | } 201 | 202 | pub fn random_mutate_large(self, rng: &mut WyRand) -> Self { 203 | match rng.generate_range(0..6) { 204 | 0 => Self { 205 | a: rand_string_large(rng), 206 | ..self 207 | }, 208 | 1 => Self { 209 | b: rng.generate::(), 210 | ..self 211 | }, 212 | 2 => Self { 213 | c: self 214 | .c 215 | .into_iter() 216 | .filter(|_| rng.generate_range(0..100) < 30_u8) 217 | .collect::>() 218 | .into_iter() 219 | .map(|v| { 220 | if rng.generate_range(0..100) < 25_u8 { 221 | rand_string(rng) 222 | } else { 223 | v 224 | } 225 | }) 226 | .collect::>() 227 | .into_iter() 228 | .chain( 229 | (0..rng.generate_range::(0..(u16::MAX / 5))) 230 | .map(|_| rand_string(rng)), 231 | ) 232 | .collect(), 233 | ..self 234 | }, 235 | 3 => Self { 236 | d: self 237 | .d 238 | .into_iter() 239 | .filter(|_| rng.generate_range(0..100) < 30_u8) 240 | .collect::>() 241 | .into_iter() 242 | .map(|v| { 243 | if rng.generate_range(0..100) < 25_u8 { 244 | rand_string(rng) 245 | } else { 246 | v 247 | } 248 | }) 249 | .collect::>() 250 | .into_iter() 251 | .chain( 252 | (0..rng.generate_range::(0..(u16::MAX / 5))) 253 | .map(|_| rand_string(rng)), 254 | ) 255 | .collect(), 256 | ..self 257 | }, 258 | 4 => Self { 259 | e: self 260 | .e 261 | .into_iter() 262 | .filter(|_| rng.generate_range(0..100) < 25_u8) 263 | .collect::>() 264 | .into_iter() 265 | .map(|v| { 266 | if rng.generate_range(0..100) < 25_u8 { 267 | (rng.generate::(), rand_string(rng)) 268 | } else { 269 | v 270 | } 271 | }) 272 | .collect::>() 273 | .into_iter() 274 | .chain( 275 | (0..rng.generate_range::(0..(u16::MAX / 5))) 276 | .map(|_| (rng.generate::(), rand_string(rng))), 277 | ) 278 | .collect(), 279 | ..self 280 | }, 281 | 5 => Self { 282 | f: self 283 | .f 284 | .into_iter() 285 | .filter(|_| rng.generate_range(0..100) < 25_u8) 286 | .collect::>() 287 | .into_iter() 288 | .map(|v| { 289 | if rng.generate_range(0..100) < 25_u8 { 290 | (rng.generate::(), rand_string(rng)) 291 | } else { 292 | v 293 | } 294 | }) 295 | .collect::>() 296 | .into_iter() 297 | .chain( 298 | (0..rng.generate_range::(0..(u16::MAX / 5))) 299 | .map(|_| (rng.generate::(), rand_string(rng))), 300 | ) 301 | .collect(), 302 | ..self 303 | }, 304 | _ => self, 305 | } 306 | } 307 | 308 | #[track_caller] 309 | pub fn assert_eq(self, right: TestBench, diff: &Vec<::Diff>) { 310 | assert_eq!(self.a, right.a, "{:?}", diff); 311 | assert_eq!(self.b, right.b, "{:?}", diff); 312 | assert_eq_unordered_sort!(self.c, right.c, "{:?}", diff); 313 | assert_eq_unordered_sort!(self.d, right.d, "{:?}", diff); 314 | assert_eq_unordered_sort!( 315 | self.e.iter().map(|x| x.0).collect::>(), 316 | right.e.iter().map(|x| x.0).collect::>(), 317 | "{:?}", 318 | diff 319 | ); 320 | assert_eq_unordered_sort!(self.f, right.f, "{:?}", diff); 321 | } 322 | } 323 | 324 | #[cfg(test)] 325 | mod size_tests { 326 | use bincode::Options; 327 | 328 | use super::*; 329 | 330 | #[test] 331 | fn test_sizes_basic() { 332 | structdiff_size::size_basic(); 333 | #[cfg(feature = "compare")] 334 | { 335 | serde_diff_size::size_basic(); 336 | diff_struct_size::size_basic(); 337 | } 338 | } 339 | 340 | #[ignore] 341 | #[test] 342 | fn test_sizes_large() { 343 | structdiff_size::size_large(); 344 | #[cfg(feature = "compare")] 345 | { 346 | serde_diff_size::size_large(); 347 | diff_struct_size::size_large(); 348 | } 349 | } 350 | 351 | #[test] 352 | fn test_sizes_basic_mut() { 353 | structdiff_size_mut::size_basic_mut(); 354 | #[cfg(feature = "compare")] 355 | { 356 | serde_diff_size_mut::size_basic_mut(); 357 | diff_struct_size_mut::size_basic_mut(); 358 | } 359 | } 360 | 361 | #[ignore] 362 | #[test] 363 | fn test_sizes_large_mut() { 364 | structdiff_size_mut::size_large_mut(); 365 | #[cfg(feature = "compare")] 366 | { 367 | serde_diff_size_mut::size_large_mut(); 368 | diff_struct_size_mut::size_large_mut(); 369 | } 370 | } 371 | 372 | mod structdiff_size { 373 | use super::*; 374 | 375 | pub fn size_basic() { 376 | let mut bytes = 0_u64; 377 | let mut rng = WyRand::new(); 378 | for _i in 0..100 { 379 | let first = std::hint::black_box(TestBench::generate_random(&mut rng)); 380 | let second = std::hint::black_box(TestBench::generate_random(&mut rng)); 381 | let diff = StructDiff::diff(&first, &second); 382 | bytes += bincode::serialized_size(&diff).unwrap(); 383 | } 384 | println!("StructDiff - small: {} bytes", bytes as f64 / 100.0) 385 | } 386 | 387 | pub fn size_large() { 388 | let mut bytes = 0_u64; 389 | let mut rng = WyRand::new(); 390 | for _i in 0..100 { 391 | let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); 392 | let second = std::hint::black_box(TestBench::generate_random_large(&mut rng)); 393 | bytes += bincode::serialized_size(&StructDiff::diff(&first, &second)).unwrap(); 394 | } 395 | println!("StructDiff - large: {} bytes", bytes as f64 / 100.0) 396 | } 397 | } 398 | 399 | #[cfg(feature = "compare")] 400 | mod diff_struct_size { 401 | use diff::Diff; 402 | 403 | use super::*; 404 | 405 | pub fn size_basic() { 406 | let mut bytes = 0_u64; 407 | let mut rng = WyRand::new(); 408 | for _i in 0..100 { 409 | let first = std::hint::black_box(TestBench::generate_random(&mut rng)); 410 | let second = std::hint::black_box(TestBench::generate_random(&mut rng)); 411 | let diff = Diff::diff(&first, &second); 412 | bytes += bincode::serialized_size(&diff).unwrap(); 413 | } 414 | 415 | println!("Diff-Struct - small: {} bytes", bytes as f64 / 100.0) 416 | } 417 | 418 | pub fn size_large() { 419 | let mut bytes = 0_u64; 420 | let mut rng = WyRand::new(); 421 | for _ in 0..100 { 422 | let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); 423 | let second = std::hint::black_box(TestBench::generate_random_large(&mut rng)); 424 | bytes += bincode::serialized_size(&Diff::diff(&first, &second)).unwrap(); 425 | } 426 | println!("Diff-Struct - large: {} bytes", bytes as f64 / 100.0) 427 | } 428 | } 429 | 430 | #[cfg(feature = "compare")] 431 | mod serde_diff_size { 432 | use super::*; 433 | 434 | pub fn size_basic() { 435 | let mut bytes = 0_u64; 436 | let mut rng = WyRand::new(); 437 | let options = bincode::DefaultOptions::new() 438 | .with_fixint_encoding() 439 | .allow_trailing_bytes(); 440 | for _ in 0..100 { 441 | let first = std::hint::black_box(TestBench::generate_random(&mut rng)); 442 | let second = std::hint::black_box(TestBench::generate_random(&mut rng)); 443 | let diff = std::hint::black_box( 444 | options 445 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 446 | .unwrap(), 447 | ); 448 | bytes += bincode::serialized_size(&diff).unwrap(); 449 | } 450 | println!("Serde-Diff - small: {} bytes", bytes as f64 / 100.0) 451 | } 452 | 453 | pub fn size_large() { 454 | let mut bytes = 0_u64; 455 | let mut rng = WyRand::new(); 456 | let options = bincode::DefaultOptions::new() 457 | .with_fixint_encoding() 458 | .allow_trailing_bytes(); 459 | for _ in 0..100 { 460 | let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); 461 | let second = std::hint::black_box(TestBench::generate_random_large(&mut rng)); 462 | let diff = std::hint::black_box( 463 | options 464 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 465 | .unwrap(), 466 | ); 467 | bytes += bincode::serialized_size(&diff).unwrap(); 468 | } 469 | println!("Serde-Diff - large: {} bytes", bytes as f64 / 100.0) 470 | } 471 | } 472 | 473 | mod structdiff_size_mut { 474 | use super::*; 475 | 476 | pub fn size_basic_mut() { 477 | let mut bytes = 0_u64; 478 | let mut rng = WyRand::new(); 479 | for _ in 0..100 { 480 | let first = std::hint::black_box(TestBench::generate_random(&mut rng)); 481 | let second = std::hint::black_box(first.clone().random_mutate(&mut rng)); 482 | let diff = StructDiff::diff(&first, &second); 483 | 484 | bytes += bincode::serialized_size(&diff).unwrap(); 485 | } 486 | println!("StructDiff - mut small: {} bytes", bytes as f64 / 100.0) 487 | } 488 | 489 | pub fn size_large_mut() { 490 | let mut bytes = 0_u64; 491 | let mut rng = WyRand::new(); 492 | for _ in 0..100 { 493 | let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); 494 | let second = std::hint::black_box(first.clone().random_mutate_large(&mut rng)); 495 | let diff = StructDiff::diff(&first, &second); 496 | bytes += bincode::serialized_size(&diff).unwrap(); 497 | } 498 | println!("StructDiff - mut large: {} bytes", bytes as f64 / 100.0) 499 | } 500 | } 501 | 502 | #[cfg(feature = "compare")] 503 | mod diff_struct_size_mut { 504 | use diff::Diff; 505 | 506 | use super::*; 507 | 508 | pub fn size_basic_mut() { 509 | let mut bytes = 0_u64; 510 | let mut rng = WyRand::new(); 511 | let options = bincode::DefaultOptions::new() 512 | .with_fixint_encoding() 513 | .allow_trailing_bytes(); 514 | for _ in 0..100 { 515 | let first = std::hint::black_box(TestBench::generate_random(&mut rng)); 516 | let second = std::hint::black_box(first.clone().random_mutate(&mut rng)); 517 | let diff = 518 | std::hint::black_box(options.serialize(&Diff::diff(&first, &second)).unwrap()); 519 | 520 | bytes += bincode::serialized_size(&diff).unwrap(); 521 | } 522 | println!("Diff-Struct - mut small: {} bytes", bytes as f64 / 100.0) 523 | } 524 | 525 | pub fn size_large_mut() { 526 | let mut bytes = 0_u64; 527 | let mut rng = WyRand::new(); 528 | let options = bincode::DefaultOptions::new() 529 | .with_fixint_encoding() 530 | .allow_trailing_bytes(); 531 | for _ in 0..100 { 532 | let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); 533 | let second = std::hint::black_box(first.clone().random_mutate_large(&mut rng)); 534 | let diff = 535 | std::hint::black_box(options.serialize(&Diff::diff(&first, &second)).unwrap()); 536 | bytes += bincode::serialized_size(&diff).unwrap(); 537 | } 538 | println!("Diff-Struct - mut large: {} bytes", bytes as f64 / 100.0) 539 | } 540 | } 541 | 542 | #[cfg(feature = "compare")] 543 | mod serde_diff_size_mut { 544 | use bincode::Options; 545 | 546 | use super::*; 547 | 548 | pub fn size_basic_mut() { 549 | let mut bytes = 0_u64; 550 | let mut rng = WyRand::new(); 551 | let options = bincode::DefaultOptions::new() 552 | .with_fixint_encoding() 553 | .allow_trailing_bytes(); 554 | for _ in 0..100 { 555 | let first = std::hint::black_box(TestBench::generate_random(&mut rng)); 556 | let second = std::hint::black_box(first.clone().random_mutate(&mut rng)); 557 | let diff = std::hint::black_box( 558 | options 559 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 560 | .unwrap(), 561 | ); 562 | 563 | bytes += bincode::serialized_size(&diff).unwrap(); 564 | } 565 | println!("Serde-Diff - mut small: {} bytes", bytes as f64 / 100.0) 566 | } 567 | 568 | pub fn size_large_mut() { 569 | let mut bytes = 0_u64; 570 | let mut rng = WyRand::new(); 571 | let options = bincode::DefaultOptions::new() 572 | .with_fixint_encoding() 573 | .allow_trailing_bytes(); 574 | for _ in 0..100 { 575 | let first = std::hint::black_box(TestBench::generate_random_large(&mut rng)); 576 | let second = std::hint::black_box(first.clone().random_mutate_large(&mut rng)); 577 | let diff = std::hint::black_box( 578 | options 579 | .serialize(&serde_diff::Diff::serializable(&first, &second)) 580 | .unwrap(), 581 | ); 582 | bytes += bincode::serialized_size(&diff).unwrap(); 583 | } 584 | println!("Serde-Diff - mut large: {} bytes", bytes as f64 / 100.0) 585 | } 586 | } 587 | } 588 | -------------------------------------------------------------------------------- /derive/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "structdiff-derive" 3 | version = "0.7.3" 4 | authors = ["Makepad ", "Fedor ", "Kirk proc_macro::TokenStream { 17 | let input = parse::parse_data(input); 18 | 19 | let ts = match &input { 20 | parse::Data::Struct(struct_) if struct_.named => derive_struct_diff_struct(struct_), 21 | parse::Data::Enum(enum_) => derive_struct_diff_enum(enum_), 22 | _ => unimplemented!("Only structs and enums are supported"), 23 | }; 24 | 25 | ts 26 | } 27 | -------------------------------------------------------------------------------- /derive/src/shared.rs: -------------------------------------------------------------------------------- 1 | macro_rules! l { 2 | ($target:ident, $line:expr) => { 3 | $target.push_str($line) 4 | }; 5 | 6 | ($target:ident, $line:expr, $($param:expr),*) => { 7 | $target.push_str(&::alloc::format!($line, $($param,)*)) 8 | }; 9 | } 10 | 11 | #[derive(Debug, Default)] 12 | pub enum MapStrategy { 13 | KeyOnly, 14 | #[default] 15 | KeyAndValue, 16 | } 17 | 18 | #[derive(Debug)] 19 | pub enum CollectionStrategy { 20 | OrderedArrayLike, 21 | UnorderedArrayLikeHash, 22 | UnorderedMapLikeHash(MapStrategy), 23 | } 24 | 25 | #[cfg(feature = "generated_setters")] 26 | pub fn attrs_setter(attributes: &[crate::parse::Attribute]) -> (bool, bool, Option) { 27 | let skip = attributes 28 | .iter() 29 | .any(|attr| attr.tokens.len() == 1 && attr.tokens[0] == "skip_setter"); 30 | let local = attributes 31 | .iter() 32 | .any(|attr| attr.tokens.len() == 1 && attr.tokens[0] == "setter"); 33 | 34 | let Some(name_override) = attributes.iter().find_map(|attr| { 35 | if attr.tokens.len() == 2 && attr.tokens[0] == "setter_name" { 36 | Some(attr.tokens[1].clone()) 37 | } else { 38 | None 39 | } 40 | }) else { 41 | return (local, skip, None); 42 | }; 43 | 44 | (local, skip, Some(name_override)) 45 | } 46 | 47 | #[cfg(feature = "generated_setters")] 48 | pub fn attrs_all_setters(attributes: &[crate::parse::Attribute]) -> bool { 49 | attributes 50 | .iter() 51 | .any(|attr| attr.tokens.len() == 1 && attr.tokens[0] == "setters") 52 | } 53 | 54 | pub fn attrs_recurse(attributes: &[crate::parse::Attribute]) -> bool { 55 | attributes 56 | .iter() 57 | .any(|attr| attr.tokens.len() == 1 && attr.tokens[0] == "recurse") 58 | } 59 | 60 | pub fn attrs_skip(attributes: &[crate::parse::Attribute]) -> bool { 61 | attributes 62 | .iter() 63 | .any(|attr| attr.tokens.len() == 1 && attr.tokens[0] == "skip") 64 | } 65 | 66 | pub fn attrs_collection_type(attributes: &[crate::parse::Attribute]) -> Option { 67 | attributes.iter().find_map(|attr| { 68 | if attr.tokens.len() == 2 && attr.tokens[0] == "collection_strategy" { 69 | let strategy = match attr.tokens[1].clone().as_str() { 70 | "ordered_array_like" => CollectionStrategy::OrderedArrayLike, 71 | "unordered_array_like" => CollectionStrategy::UnorderedArrayLikeHash, 72 | "unordered_map_like" => { 73 | let map_compare_type = attrs_map_strategy(attributes).unwrap_or_default(); 74 | CollectionStrategy::UnorderedMapLikeHash(map_compare_type) 75 | } 76 | _ => { 77 | return None; 78 | } 79 | }; 80 | Some(strategy) 81 | } else { 82 | None 83 | } 84 | }) 85 | } 86 | 87 | pub fn attrs_map_strategy(attributes: &[crate::parse::Attribute]) -> Option { 88 | attributes.iter().find_map(|attr| { 89 | if attr.tokens.len() == 2 && attr.tokens[0] == "map_equality" { 90 | let strategy = match attr.tokens[1].clone().as_str() { 91 | "key_only" => MapStrategy::KeyOnly, 92 | "key_and_value" => MapStrategy::KeyAndValue, 93 | _ => { 94 | return None; 95 | } 96 | }; 97 | Some(strategy) 98 | } else { 99 | None 100 | } 101 | }) 102 | } 103 | 104 | pub fn attrs_expose(attributes: &[crate::parse::Attribute]) -> Option> { 105 | attributes.iter().find_map(|attr| match attr.tokens.len() { 106 | 1 if attr.tokens[0].starts_with("expose") => Some(None), 107 | 2.. if attr.tokens[0] == "expose" => Some(Some((&attr.tokens[1]).to_string())), 108 | _ => return None, 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /src/collections/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod rope; 2 | 3 | pub mod unordered_array_like; 4 | pub mod unordered_map_like; 5 | pub mod unordered_map_like_recursive; 6 | 7 | pub mod ordered_array_like; 8 | -------------------------------------------------------------------------------- /src/collections/rope/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering::{Equal, Greater, Less}, 3 | collections::VecDeque, 4 | ops::{Index, IndexMut, RangeBounds}, 5 | }; 6 | 7 | mod slots; 8 | 9 | const MAX_SLOT_SIZE: usize = 16; 10 | const BASE_SLOT_SIZE: usize = 8; 11 | const UNDERSIZED_SLOT: usize = 1; 12 | 13 | type Container = slots::ArrayMap; 14 | 15 | #[cfg_attr(test, derive(Clone))] 16 | pub struct Rope(Vec>); 17 | 18 | pub struct Iter<'rope, T> { 19 | self_ref: &'rope Rope, 20 | key: usize, 21 | in_key: usize, 22 | exhausted: bool, 23 | } 24 | pub struct IntoIter { 25 | self_own: VecDeque>, 26 | internal: Option< as IntoIterator>::IntoIter>, 27 | } 28 | 29 | impl Index for Rope { 30 | type Output = T; 31 | 32 | fn index(&self, index: usize) -> &Self::Output { 33 | let mut seen = 0; 34 | for entry in self.0.iter() { 35 | seen += entry.len(); 36 | if seen > index { 37 | seen -= entry.len(); 38 | return &entry[index - seen]; 39 | } 40 | } 41 | panic!("Index is {index} but len is {seen}") 42 | } 43 | } 44 | 45 | impl IndexMut for Rope { 46 | fn index_mut(&mut self, index: usize) -> &mut T { 47 | let mut seen = 0; 48 | for entry in self.0.iter_mut() { 49 | seen += entry.len(); 50 | if seen > index { 51 | seen -= entry.len(); 52 | return &mut entry[index - seen]; 53 | } 54 | } 55 | panic!("Index is {index} but len is {seen}") 56 | } 57 | } 58 | 59 | impl<'rope, T: 'rope> Iterator for Iter<'rope, T> { 60 | type Item = &'rope T; 61 | 62 | fn next(&mut self) -> Option { 63 | if self.exhausted { 64 | return None; 65 | } 66 | 67 | let ret = &self.self_ref.0[self.key][self.in_key]; 68 | 69 | self.in_key += 1; 70 | if self.in_key >= self.self_ref.0[self.key].len() { 71 | self.in_key = 0; 72 | self.key += 1; 73 | } 74 | 75 | if self.key >= self.self_ref.0.len() { 76 | self.exhausted = true; 77 | } 78 | 79 | Some(ret) 80 | } 81 | } 82 | 83 | impl Iterator for IntoIter { 84 | type Item = T; 85 | 86 | fn next(&mut self) -> Option { 87 | if let ret @ Some(_) = self.internal.as_mut().and_then(|internal| internal.next()) { 88 | return ret; 89 | } 90 | 91 | while let Some(mut vec_iter) = self.self_own.pop_front().map(IntoIterator::into_iter) { 92 | let ret @ Some(_) = vec_iter.next() else { 93 | continue; 94 | }; 95 | 96 | self.internal = Some(vec_iter); 97 | return ret; 98 | } 99 | 100 | None 101 | } 102 | } 103 | 104 | impl IntoIterator for Rope { 105 | type Item = T; 106 | 107 | type IntoIter = IntoIter; 108 | 109 | fn into_iter(self) -> Self::IntoIter { 110 | IntoIter { 111 | self_own: self.0.into(), 112 | internal: None, 113 | } 114 | } 115 | } 116 | 117 | impl<'rope, T: 'rope> IntoIterator for &'rope Rope { 118 | type Item = &'rope T; 119 | 120 | type IntoIter = Iter<'rope, T>; 121 | 122 | fn into_iter(self) -> Self::IntoIter { 123 | Iter { 124 | self_ref: self, 125 | key: 0, 126 | in_key: 0, 127 | exhausted: self.0.is_empty() || self.0[0].is_empty(), 128 | } 129 | } 130 | } 131 | 132 | impl FromIterator for Rope { 133 | fn from_iter>(iter: C) -> Self { 134 | let mut iter = iter.into_iter().peekable(); 135 | let mut map = Vec::new(); 136 | while iter.peek().is_some() { 137 | let arrmap = slots::ArrayMap::from_iter(iter.by_ref().take(8)); 138 | map.push(arrmap); 139 | if map.last().unwrap().len() != 8 { 140 | break; 141 | } 142 | } 143 | 144 | Self(map) 145 | } 146 | } 147 | 148 | impl Default for Rope { 149 | fn default() -> Self { 150 | Self::new() 151 | } 152 | } 153 | 154 | impl Rope { 155 | pub fn new() -> Self { 156 | Self(Vec::from([slots::ArrayMap::new()])) 157 | } 158 | 159 | pub fn iter(&self) -> Iter<'_, T> { 160 | self.into_iter() 161 | } 162 | 163 | #[inline] 164 | fn _key_for_index(&self, index: usize) -> usize { 165 | let mut seen = 0; 166 | for (idx, entry) in self.0.iter().enumerate() { 167 | seen += entry.len(); 168 | if seen > index { 169 | return idx; 170 | } 171 | } 172 | self.0.len() 173 | } 174 | 175 | #[inline] 176 | fn key_with_count_for_index(&self, index: usize) -> (usize, usize) { 177 | let mut seen = 0; 178 | for (idx, entry) in self.0.iter().enumerate() { 179 | seen += entry.len(); 180 | if seen > index { 181 | seen -= entry.len(); 182 | return (idx, seen); 183 | } 184 | } 185 | (self.0.len(), seen) 186 | } 187 | 188 | #[inline] 189 | fn key_with_count_for_index_from_prev( 190 | &self, 191 | index: usize, 192 | prev: usize, 193 | mut seen: usize, 194 | ) -> (usize, usize) { 195 | if seen > index { 196 | // it's in the same chunk, return early 197 | return (prev, seen); 198 | } 199 | for (idx, entry) in self.0.iter().enumerate().skip(prev) { 200 | seen += entry.len(); 201 | if seen > index { 202 | seen -= entry.len(); 203 | return (idx, seen); 204 | } 205 | } 206 | (self.0.len(), seen) 207 | } 208 | 209 | #[inline] 210 | pub fn len(&self) -> usize { 211 | self.0 212 | .iter() 213 | .map(slots::ArrayMap::len) 214 | .fold(0, std::ops::Add::add) 215 | } 216 | 217 | #[inline] 218 | pub fn is_empty(&self) -> bool { 219 | self.len() == 0 220 | } 221 | 222 | fn rebalance_from_key(&mut self, start_key: usize) { 223 | let mut carry = VecDeque::with_capacity(16); 224 | let mut hold = Container::new(); 225 | for key in start_key..(self.0.len()) { 226 | let entry = self.0.get_mut(key).unwrap(); 227 | if entry.is_empty() { 228 | continue; 229 | } 230 | 231 | const LOW: usize = BASE_SLOT_SIZE - (BASE_SLOT_SIZE / 2); 232 | const HIGH: usize = BASE_SLOT_SIZE + (BASE_SLOT_SIZE / 2); 233 | if (LOW..=HIGH).contains(&entry.len()) && carry.is_empty() { 234 | break; 235 | } 236 | 237 | // put the empty holder in the list for now 238 | std::mem::swap(entry, &mut hold); 239 | 240 | // adjust size of hold, either taking elements from later chunks or carrying them 241 | match (hold.len().cmp(&BASE_SLOT_SIZE), carry.is_empty()) { 242 | (Less, carry_empty) => { 243 | if !carry_empty { 244 | carry.extend(hold.drain(..)); 245 | hold.extend(carry.drain(..BASE_SLOT_SIZE.min(carry.len()))); 246 | } 247 | 248 | let mut iter = self.0.iter_mut().skip(key); 249 | while let (Some(take_from), false) = (iter.next(), hold.len() == BASE_SLOT_SIZE) 250 | { 251 | hold.extend(take_from.drain( 252 | ..(BASE_SLOT_SIZE.saturating_sub(hold.len())).min(take_from.len()), 253 | )); 254 | } 255 | } 256 | (Equal, true) => (), 257 | (Equal | Greater, false) => { 258 | carry.extend(hold.drain(..)); 259 | hold.extend(carry.drain(..BASE_SLOT_SIZE.min(carry.len()))); 260 | } 261 | (Greater, true) => { 262 | carry.extend(hold.drain(BASE_SLOT_SIZE..)); 263 | } 264 | } 265 | 266 | // take the empty holder back and leave the values in the map entry 267 | std::mem::swap(self.0.get_mut(key).unwrap(), &mut hold); 268 | } 269 | 270 | assert!(hold.is_empty()); 271 | 272 | self.0.retain(|v| !v.is_empty()); 273 | 274 | // fix up the last entry with any carried values 275 | match (carry.len(), self.0.last_mut()) { 276 | (0, ..) => { 277 | return; 278 | } 279 | (_, Some(l_entry)) => { 280 | l_entry.extend( 281 | carry.drain(..(BASE_SLOT_SIZE.saturating_sub(l_entry.len())).min(carry.len())), 282 | ); 283 | } 284 | _ => (), 285 | } 286 | 287 | // add any remaining carry values into new slots at the end 288 | while carry.len() > BASE_SLOT_SIZE { 289 | self.0.push(Container::from_iter( 290 | carry.drain(..BASE_SLOT_SIZE.min(carry.len())), 291 | )); 292 | } 293 | if !carry.is_empty() { 294 | self.0.push(Container::from_iter(carry)); 295 | } 296 | } 297 | 298 | pub fn insert(&mut self, index: usize, element: T) { 299 | let (key, count) = self.key_with_count_for_index(index); 300 | if key == self.0.len() { 301 | self.0.push(Container::new()); 302 | } 303 | let vec = self.0.get_mut(key).unwrap(); 304 | vec.insert(index - count, element); 305 | if vec.len() == MAX_SLOT_SIZE { 306 | self.rebalance_from_key(key); 307 | } 308 | } 309 | 310 | pub fn remove(&mut self, index: usize) { 311 | let (key, count) = self.key_with_count_for_index(index); 312 | let Some(vec) = self.0.get_mut(key) else { 313 | panic!( 314 | "Failed to remove item with index {index} from rope with {} elements", 315 | self.len() 316 | ); 317 | }; 318 | vec.remove(index - count); 319 | if (0..=UNDERSIZED_SLOT).contains(&vec.len()) { 320 | self.rebalance_from_key(key.saturating_sub(1)); 321 | } 322 | } 323 | 324 | pub fn drain(&mut self, range: R) 325 | where 326 | R: RangeBounds, 327 | { 328 | use std::ops::Bound; 329 | 330 | let (l_idx, r_idx) = match (range.start_bound(), range.end_bound()) { 331 | (Bound::Included(l_i), Bound::Included(r_i)) => (*l_i, *r_i), 332 | (Bound::Included(l_i), Bound::Excluded(r_e)) => (*l_i, r_e - 1), 333 | (Bound::Included(l_i), Bound::Unbounded) => (*l_i, self.len() - 1), 334 | (Bound::Excluded(l_e), Bound::Included(r_i)) => (l_e + 1, *r_i), 335 | (Bound::Excluded(l_e), Bound::Excluded(r_e)) => (l_e + 1, r_e - 1), 336 | (Bound::Excluded(l_e), Bound::Unbounded) => (l_e + 1, self.len() - 1), 337 | (Bound::Unbounded, Bound::Included(r_i)) => (0, *r_i), 338 | (Bound::Unbounded, Bound::Excluded(r_e)) => (0, r_e - 1), 339 | (Bound::Unbounded, Bound::Unbounded) => (0, self.len() - 1), 340 | }; 341 | 342 | let (l_key, l_key_count) = self.key_with_count_for_index(l_idx); 343 | let (r_key, r_key_count) = 344 | self.key_with_count_for_index_from_prev(r_idx, l_key, l_key_count); 345 | 346 | match l_key == r_key { 347 | true => { 348 | let v = self.0.get_mut(l_key).expect("we just looked this key up"); 349 | v.drain((l_idx - l_key_count)..=(r_idx - l_key_count)); 350 | if v.len() <= UNDERSIZED_SLOT { 351 | self.rebalance_from_key(l_key.saturating_sub(1)); 352 | } 353 | } 354 | false => { 355 | let l_mut = self.0.get_mut(l_key).unwrap(); 356 | l_mut.drain((l_idx - l_key_count)..); 357 | let l_len = l_mut.len(); 358 | let r_mut = self.0.get_mut(r_key).unwrap(); 359 | r_mut.drain(..=(r_idx - r_key_count)); 360 | let r_len = r_mut.len(); 361 | let _ = self.0.drain((l_key + 1)..r_key); 362 | 363 | if l_len <= UNDERSIZED_SLOT || r_len <= UNDERSIZED_SLOT { 364 | self.rebalance_from_key(l_key); 365 | } 366 | } 367 | } 368 | } 369 | 370 | pub fn swap(&mut self, a: usize, b: usize) { 371 | let [a, b] = [a.min(b), a.max(b)]; 372 | let (l_key, l_key_count) = self.key_with_count_for_index(a); 373 | let (r_key, r_key_count) = self.key_with_count_for_index_from_prev(b, l_key, l_key_count); 374 | match l_key == r_key { 375 | true => self 376 | .0 377 | .get_mut(l_key) 378 | .unwrap() 379 | .swap(a - l_key_count, b - l_key_count), 380 | false => { 381 | let (l, r) = self.0.split_at_mut(r_key); 382 | std::mem::swap(&mut l[l_key][a - l_key_count], &mut r[0][b - r_key_count]); 383 | } 384 | } 385 | } 386 | } 387 | 388 | #[cfg(test)] 389 | mod test { 390 | use nanorand::{Rng, WyRand}; 391 | 392 | use super::{Rope, BASE_SLOT_SIZE, MAX_SLOT_SIZE}; 393 | 394 | #[derive(Debug, Clone)] 395 | pub enum Mutation { 396 | Insert(T, usize), 397 | Remove(usize), 398 | Swap(usize, usize), 399 | Drain(usize, usize), 400 | } 401 | 402 | pub(crate) trait Random { 403 | fn generate_random(rng: &mut WyRand) -> Self; 404 | fn generate_random_large(rng: &mut WyRand) -> Self; 405 | fn random_mutate(self, mutation: Mutation) -> Self; 406 | } 407 | 408 | pub fn rand_string(rng: &mut WyRand) -> String { 409 | let base = vec![(); 8]; 410 | base.into_iter() 411 | .map(|_| rng.generate_range::(65..=90)) 412 | .filter_map(char::from_u32) 413 | .collect::() 414 | } 415 | 416 | impl Mutation { 417 | pub fn random_mutation(rng: &mut WyRand, len: usize) -> Option> { 418 | match rng.generate_range(0..4) { 419 | 0 => Some(Self::Insert(rand_string(rng), rng.generate_range(0..=len))), 420 | 1 => match len == 0 { 421 | false => Some(Self::Remove(rng.generate_range(0..len))), 422 | true => None, 423 | }, 424 | 2 => { 425 | if len == 0 { 426 | return None; 427 | } 428 | let l = rng.generate_range(0..len); 429 | let r = rng.generate_range(0..len); 430 | if l == r { 431 | None 432 | } else { 433 | Some(Self::Swap(l, r)) 434 | } 435 | } 436 | 3 => { 437 | let l = rng.generate_range(0..len); 438 | let r = rng.generate_range(l..len); 439 | Some(Self::Drain(l, r)) 440 | } 441 | _ => None, 442 | } 443 | } 444 | } 445 | 446 | impl Random for Vec { 447 | fn generate_random(rng: &mut WyRand) -> Self { 448 | (0..rng.generate_range::(5..15)) 449 | .map(|_| rand_string(rng)) 450 | .collect() 451 | } 452 | 453 | fn generate_random_large(rng: &mut WyRand) -> Self { 454 | (0..rng.generate_range::(0..(u16::MAX / 5))) 455 | .map(|_| rand_string(rng)) 456 | .collect() 457 | } 458 | 459 | fn random_mutate(mut self, mutation: Mutation) -> Self { 460 | match mutation { 461 | Mutation::Insert(s, i) => self.insert(i, s), 462 | Mutation::Remove(i) => { 463 | self.remove(i); 464 | } 465 | Mutation::Swap(l, r) => self.swap(l, r), 466 | Mutation::Drain(l, r) => { 467 | self.drain(l..=r); 468 | } 469 | } 470 | self 471 | } 472 | } 473 | 474 | impl std::fmt::Display for Rope { 475 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 476 | let mut seen = 0; 477 | for vals in self.0.iter() { 478 | write!(f, "{seen}: [")?; 479 | for val in vals { 480 | write!(f, "{},", val)?; 481 | } 482 | writeln!(f, "];")?; 483 | seen += vals.len(); 484 | } 485 | Ok(()) 486 | } 487 | } 488 | 489 | impl Random for Rope { 490 | fn generate_random(rng: &mut WyRand) -> Self { 491 | (0..rng.generate_range::(5..15)) 492 | .map(|_| rand_string(rng)) 493 | .collect() 494 | } 495 | 496 | fn generate_random_large(rng: &mut WyRand) -> Self { 497 | (0..rng.generate_range::(0..(u16::MAX / 5))) 498 | .map(|_| rand_string(rng)) 499 | .collect() 500 | } 501 | 502 | fn random_mutate(mut self, mutation: Mutation) -> Self { 503 | match mutation { 504 | Mutation::Insert(s, i) => self.insert(i, s), 505 | Mutation::Remove(i) => self.remove(i), 506 | Mutation::Swap(l, r) => self.swap(l, r), 507 | Mutation::Drain(l, r) => self.drain(l..=r), 508 | } 509 | self 510 | } 511 | } 512 | 513 | fn test(generator: impl Fn(&mut WyRand) -> Vec, count: usize) { 514 | let mut rng = WyRand::new(); 515 | let mut start_vec = generator(&mut rng); 516 | let mut start_rope = start_vec.clone().into_iter().collect::>(); 517 | assert_eq!( 518 | start_rope.clone().into_iter().collect::>(), 519 | start_vec 520 | ); 521 | for _ in 0..count { 522 | let prev_rope = start_rope.clone(); 523 | let Some(mutation) = Mutation::random_mutation(&mut rng, start_vec.len()) else { 524 | continue; 525 | }; 526 | 527 | let sr_clone = start_rope.clone(); 528 | let mut_clone = mutation.clone(); 529 | let result = std::panic::catch_unwind(|| { 530 | sr_clone.random_mutate(mut_clone); 531 | }); 532 | 533 | let Ok(_) = result else { 534 | println!("{:?}", mutation); 535 | println!("prev_rope: {}", prev_rope); 536 | panic!("Caught panic"); 537 | }; 538 | 539 | start_rope = start_rope.random_mutate(mutation.clone()); 540 | start_vec = start_vec.random_mutate(mutation.clone()); 541 | 542 | if start_rope.clone().into_iter().collect::>() != start_vec { 543 | println!("{:?}", mutation); 544 | println!("prev_rope: {}", prev_rope); 545 | println!("curr_rope: {}", start_rope); 546 | } 547 | pretty_assertions::assert_eq!( 548 | (&start_rope).into_iter().cloned().collect::>(), 549 | start_rope.clone().into_iter().collect::>() 550 | ); 551 | pretty_assertions::assert_eq!( 552 | (&start_rope).into_iter().cloned().collect::>(), 553 | start_vec 554 | ); 555 | } 556 | } 557 | 558 | #[test] 559 | fn paired_small() { 560 | test(Vec::generate_random, 1_000_000) 561 | } 562 | 563 | #[test] 564 | fn paired_large() { 565 | test(Vec::generate_random_large, 100_000) 566 | } 567 | 568 | #[test] 569 | #[should_panic] 570 | fn get_from_empty() { 571 | #[expect(clippy::unnecessary_operation)] 572 | Rope::<()>::new()[0]; 573 | } 574 | 575 | #[test] 576 | #[should_panic] 577 | fn get_past_end() { 578 | #[expect(clippy::unnecessary_operation)] 579 | Rope::<()>::from_iter([(), ()])[2]; 580 | } 581 | 582 | #[test] 583 | fn get_last() { 584 | for i in 1..33 { 585 | assert_eq!( 586 | Rope::from_iter(vec![(); i].into_iter().enumerate().map(|(i, _)| i))[i - 1], 587 | i - 1 588 | ); 589 | } 590 | } 591 | 592 | #[test] 593 | fn delete_rebalance() { 594 | let arr_map = { 595 | let mut collection = Rope::from_iter((0..).take(BASE_SLOT_SIZE)); 596 | for i in (BASE_SLOT_SIZE..).take(9) { 597 | collection.insert(i, i); 598 | } 599 | collection.remove(MAX_SLOT_SIZE); 600 | collection 601 | }; 602 | 603 | let vec = { 604 | let mut collection = Vec::from_iter((0..).take(BASE_SLOT_SIZE)); 605 | for i in (BASE_SLOT_SIZE..).take(9) { 606 | collection.insert(i, i); 607 | } 608 | 609 | collection.remove(MAX_SLOT_SIZE); 610 | collection 611 | }; 612 | 613 | assert_eq!( 614 | vec.iter().collect::>(), 615 | arr_map.iter().collect::>() 616 | ); 617 | assert_eq!( 618 | vec.into_iter().collect::>(), 619 | arr_map.into_iter().collect::>() 620 | ); 621 | } 622 | } 623 | -------------------------------------------------------------------------------- /src/collections/rope/slots.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Debug, 3 | ops::{Index, IndexMut, RangeBounds}, 4 | }; 5 | 6 | #[derive(Clone)] 7 | pub(crate) struct ArrayMap([Option<(u8, T)>; N], usize); 8 | 9 | impl Debug for ArrayMap { 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 11 | f.debug_tuple("ArrayMap") 12 | .field(&self.0.iter().filter_map(|o| o.as_ref()).collect::>()) 13 | .field(&self.1) 14 | .finish() 15 | } 16 | } 17 | 18 | impl ArrayMap { 19 | #[inline] 20 | fn find_empty_slot(&self) -> Option { 21 | self.0.iter().position(Option::is_none) 22 | } 23 | 24 | #[inline] 25 | fn find_empty_slots(&self) -> [Option; N] { 26 | let mut ret = [const { None }; N]; 27 | for (slot, idx) in self 28 | .0 29 | .iter() 30 | .enumerate() 31 | .filter(|i| i.1.is_none()) 32 | .map(|e| e.0) 33 | .zip(0..) 34 | { 35 | ret[idx] = Some(slot); 36 | } 37 | ret 38 | } 39 | 40 | fn get_lookups(&self) -> [Option; N] { 41 | let list_of_logical_indices: [Option; N] = std::array::from_fn(|storage_idx| { 42 | self.0[storage_idx] 43 | .as_ref() 44 | .map(|(logical_idx, _)| Some(*logical_idx)) 45 | .unwrap_or_default() 46 | }); 47 | let mut lookup_tmp: [(usize, Option); N] = 48 | std::array::from_fn(|storage_idx| (storage_idx, list_of_logical_indices[storage_idx])); 49 | lookup_tmp.sort_unstable_by_key(|(_storage_idx, logical_idx)| *logical_idx); 50 | let start_of_somes = lookup_tmp 51 | .iter() 52 | .position(|(_storage, logical)| logical.is_some()) 53 | .unwrap_or_default(); 54 | let lookups = std::array::from_fn(|i| { 55 | lookup_tmp 56 | .get(start_of_somes + i) 57 | .map(|(storage, _logical)| *storage) 58 | }); 59 | lookups 60 | } 61 | } 62 | 63 | impl ArrayMap { 64 | pub const fn new() -> Self { 65 | if N > u8::MAX as usize { 66 | panic!("N > u8::MAX is unsupported"); 67 | } 68 | Self([const { None }; N], 0) 69 | } 70 | 71 | pub const fn len(&self) -> usize { 72 | self.1 73 | } 74 | 75 | pub const fn is_empty(&self) -> bool { 76 | self.1 == 0 77 | } 78 | 79 | pub fn insert(&mut self, position: usize, value: T) { 80 | assert!( 81 | position < N, 82 | "Position {position} is greater than max position of {}", 83 | N - 1 84 | ); 85 | assert!( 86 | self.0.iter().any(Option::is_none), 87 | "No space to insert in ArrayMap with len = {}", 88 | self.1 89 | ); 90 | 91 | // bump each following index number 92 | self.0 93 | .iter_mut() 94 | .filter_map(|o| o.as_mut().map(|some| &mut some.0)) 95 | .filter(|i| **i as usize >= position) 96 | .for_each(|i| *i += 1); 97 | 98 | // find a free slot and put it in 99 | let slot_idx = self.find_empty_slot().expect("failed to find free slot"); 100 | self.0[slot_idx] = Some((position as u8, value)); 101 | 102 | self.1 += 1; 103 | } 104 | 105 | pub fn remove(&mut self, position: usize) -> T { 106 | let u8_idx = position as u8; 107 | let Some(val) = self 108 | .0 109 | .iter_mut() 110 | .find(|o| o.as_ref().map(|(i, _)| *i == u8_idx).unwrap_or_default()) 111 | .and_then(Option::take) 112 | .map(|o| o.1) 113 | else { 114 | panic!("No element found at position {}", position); 115 | }; 116 | 117 | // lower each following index number 118 | self.0 119 | .iter_mut() 120 | .filter_map(|o| o.as_mut().map(|some| &mut some.0)) 121 | .filter(|i| **i > u8_idx) 122 | .for_each(|i| *i -= 1); 123 | 124 | self.1 -= 1; 125 | val 126 | } 127 | 128 | pub fn swap(&mut self, a: usize, b: usize) { 129 | if a == b { 130 | return; 131 | } 132 | 133 | let mut indices = [a, b].map(|find| { 134 | self.0 135 | .iter() 136 | .position(|o| o.as_ref().map(|(i, _)| *i == find as u8).unwrap_or(false)) 137 | .unwrap_or_else(|| panic!("unable to find item at idx: {}", find)) 138 | }); 139 | indices.sort(); 140 | let [lower, upper] = indices; 141 | let (l, r) = self.0.split_at_mut(upper); 142 | std::mem::swap( 143 | l[lower].as_mut().map(|o| &mut o.0).unwrap(), 144 | r[0].as_mut().map(|o| &mut o.0).unwrap(), 145 | ); 146 | } 147 | 148 | pub fn drain(&mut self, range: R) -> Drain 149 | where 150 | R: RangeBounds, 151 | { 152 | // stores the highest removed value, so that later ones can be decremented 153 | let mut max_removed: Option = None; 154 | // store the length before removing things 155 | let before = self.1; 156 | 157 | let removals = self 158 | .0 159 | .iter_mut() 160 | .filter(|o| matches!(o, Some((i, _)) if range.contains(&(*i as usize)))); 161 | 162 | // move the items to the new list and decrement the item count 163 | let mut drained = [const { None }; N]; 164 | for (idx, removal) in removals.enumerate() { 165 | let removal_logical_idx = removal.as_ref().map(|o| o.0).unwrap(); 166 | max_removed = max_removed 167 | .map(|current| Some(current.max(removal_logical_idx))) 168 | .unwrap_or_else(|| Some(removal_logical_idx)); 169 | drained[idx] = removal.take(); 170 | self.1 -= 1; 171 | } 172 | 173 | drained.sort_by_key(|e| e.as_ref().map(|o| o.0)); 174 | let ret = Self::from_iter(drained.into_iter().filter_map(|e| e.map(|o| o.1))); 175 | 176 | // decrement all indices after the last removed index by the 177 | // number of items drained 178 | if let Some(max) = max_removed { 179 | let removed_count = before.abs_diff(self.1) as u8; 180 | 181 | self.0 182 | .iter_mut() 183 | .filter_map(|o| o.as_mut().map(|some| &mut some.0)) 184 | .filter(|i| **i > max) 185 | .for_each(|i| *i -= removed_count); 186 | } 187 | 188 | Drain(ret.into_iter()) 189 | } 190 | 191 | pub fn extend>(&mut self, values: I) { 192 | let free_slots = self.find_empty_slots(); 193 | 194 | for (v, loc) in values.zip(free_slots.into_iter().flatten()) { 195 | self.0[loc] = Some((self.1 as u8, v)); 196 | self.1 += 1; 197 | } 198 | } 199 | } 200 | 201 | pub struct Drain( as IntoIterator>::IntoIter); 202 | 203 | impl Iterator for Drain { 204 | type Item = T; 205 | 206 | fn next(&mut self) -> Option { 207 | self.0.next() 208 | } 209 | } 210 | 211 | impl DoubleEndedIterator for Drain { 212 | fn next_back(&mut self) -> Option { 213 | self.0.next_back() 214 | } 215 | } 216 | 217 | impl Index for ArrayMap { 218 | type Output = T; 219 | 220 | fn index(&self, index: usize) -> &Self::Output { 221 | self.0 222 | .iter() 223 | .filter_map(|o| o.as_ref()) 224 | .find(|(idx, _)| *idx as usize == index) 225 | .map(|(_, v)| v) 226 | .unwrap_or_else(|| panic!("No element found at index {index}")) 227 | } 228 | } 229 | 230 | impl IndexMut for ArrayMap { 231 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 232 | self.0 233 | .iter_mut() 234 | .filter_map(|o| o.as_mut()) 235 | .find(|(idx, _)| *idx as usize == index) 236 | .map(|(_, v)| v) 237 | .unwrap() 238 | } 239 | } 240 | 241 | // self.iter() 242 | const _: () = { 243 | pub struct Iter<'rope, T, const N: usize> { 244 | self_ref: &'rope ArrayMap, 245 | // list of indices into the backing array, stored in logical array order 246 | lookups: [Option; N], 247 | pos: usize, 248 | } 249 | 250 | impl<'rope, T, const N: usize> Iterator for Iter<'rope, T, N> { 251 | type Item = &'rope T; 252 | 253 | fn next(&mut self) -> Option { 254 | let v_ref = self 255 | .lookups 256 | .get(self.pos) 257 | .copied() 258 | .flatten() 259 | .and_then(|idx| self.self_ref.0.get(idx)) 260 | .and_then(|v| v.as_ref().map(|(_, v)| v))?; 261 | 262 | self.pos += 1; 263 | Some(v_ref) 264 | } 265 | } 266 | 267 | impl<'rope, T, const N: usize> IntoIterator for &'rope ArrayMap { 268 | type Item = &'rope T; 269 | 270 | type IntoIter = Iter<'rope, T, N>; 271 | 272 | fn into_iter(self) -> Self::IntoIter { 273 | let lookups = self.get_lookups(); 274 | Iter { 275 | self_ref: self, 276 | lookups, 277 | 278 | pos: 0, 279 | } 280 | } 281 | } 282 | }; 283 | 284 | // self.into_iter() 285 | const _: () = { 286 | pub struct Iter { 287 | self_owned: ArrayMap, 288 | // list of indices into the backing array, stored in logical array order 289 | lookups: [Option; N], 290 | pos: usize, 291 | rev_pos: usize, 292 | } 293 | 294 | impl Iterator for Iter { 295 | type Item = T; 296 | 297 | fn next(&mut self) -> Option { 298 | let v_ref = self 299 | .lookups 300 | .get(self.pos) 301 | .copied() 302 | .flatten() 303 | .and_then(|idx| self.self_owned.0.get_mut(idx)) 304 | .and_then(|v| v.take().map(|(_, v)| v))?; 305 | 306 | self.pos += 1; 307 | Some(v_ref) 308 | } 309 | } 310 | 311 | impl DoubleEndedIterator for Iter { 312 | fn next_back(&mut self) -> Option { 313 | let v_ref = self 314 | .lookups 315 | .get(self.rev_pos) 316 | .copied() 317 | .flatten() 318 | .and_then(|idx| self.self_owned.0.get_mut(idx)) 319 | .and_then(|v| v.take().map(|(_, v)| v))?; 320 | 321 | self.rev_pos += 1; 322 | Some(v_ref) 323 | } 324 | } 325 | 326 | impl IntoIterator for ArrayMap { 327 | type Item = T; 328 | 329 | type IntoIter = Iter; 330 | 331 | fn into_iter(self) -> Self::IntoIter { 332 | let lookups = self.get_lookups(); 333 | let rev_pos = self.len().saturating_sub(1); 334 | Iter { 335 | self_owned: self, 336 | lookups, 337 | 338 | pos: 0, 339 | rev_pos, 340 | } 341 | } 342 | } 343 | }; 344 | 345 | impl FromIterator for ArrayMap { 346 | fn from_iter>(iter: C) -> Self { 347 | let mut ret = Self::new(); 348 | let mut count = 0; 349 | for (i, v) in iter.into_iter().enumerate() { 350 | ret.0[i] = Some((i as u8, v)); 351 | count += 1; 352 | } 353 | 354 | ret.1 = count; 355 | ret 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /src/collections/unordered_map_like.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "nanoserde")] 2 | use nanoserde::{DeBin, SerBin}; 3 | #[cfg(feature = "serde")] 4 | use serde::{Deserialize, Serialize}; 5 | #[cfg(not(feature = "rustc_hash"))] 6 | type HashMap = std::collections::HashMap; 7 | #[cfg(feature = "rustc_hash")] 8 | type HashMap = 9 | std::collections::HashMap>; 10 | 11 | use std::hash::Hash; 12 | 13 | #[derive(Debug, Clone)] 14 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 15 | pub(crate) enum UnorderedMapLikeChange { 16 | InsertMany(K, V, usize), 17 | RemoveMany(K, usize), 18 | InsertSingle(K, V), 19 | RemoveSingle(K), 20 | } 21 | 22 | impl<'a, K: Clone, V: Clone> From> 23 | for UnorderedMapLikeChange 24 | { 25 | fn from(value: UnorderedMapLikeChange<&'a K, &'a V>) -> Self { 26 | match value { 27 | UnorderedMapLikeChange::InsertMany(key, value, count) => { 28 | UnorderedMapLikeChange::InsertMany(key.clone(), value.clone(), count) 29 | } 30 | UnorderedMapLikeChange::RemoveMany(key, count) => { 31 | UnorderedMapLikeChange::RemoveMany(key.clone(), count) 32 | } 33 | UnorderedMapLikeChange::InsertSingle(key, value) => { 34 | UnorderedMapLikeChange::InsertSingle(key.clone(), value.clone()) 35 | } 36 | UnorderedMapLikeChange::RemoveSingle(key) => { 37 | UnorderedMapLikeChange::RemoveSingle(key.clone()) 38 | } 39 | } 40 | } 41 | } 42 | 43 | #[derive(Debug, Clone)] 44 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 45 | pub(crate) enum UnorderedMapLikeDiffInternal { 46 | Replace(Vec<(K, V)>), 47 | Modify(Vec>), 48 | } 49 | 50 | #[repr(transparent)] 51 | #[derive(Debug, Clone)] 52 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 53 | pub struct UnorderedMapLikeDiff(UnorderedMapLikeDiffInternal); 54 | 55 | impl<'a, K: Clone, V: Clone> From> 56 | for UnorderedMapLikeDiff 57 | { 58 | fn from(value: UnorderedMapLikeDiff<&'a K, &'a V>) -> Self { 59 | let new_inner = match value.0 { 60 | UnorderedMapLikeDiffInternal::Replace(replace) => { 61 | UnorderedMapLikeDiffInternal::Replace( 62 | replace 63 | .into_iter() 64 | .map(|(k, v)| (k.clone(), v.clone())) 65 | .collect(), 66 | ) 67 | } 68 | UnorderedMapLikeDiffInternal::Modify(modify) => { 69 | UnorderedMapLikeDiffInternal::Modify(modify.into_iter().map(Into::into).collect()) 70 | } 71 | }; 72 | Self(new_inner) 73 | } 74 | } 75 | 76 | fn collect_into_key_eq_map< 77 | 'a, 78 | K: Hash + PartialEq + Eq + 'a, 79 | V: 'a, 80 | B: Iterator, 81 | >( 82 | list: B, 83 | ) -> HashMap<&'a K, (&'a V, usize)> { 84 | let mut map: HashMap<&K, (&V, usize)> = HashMap::default(); 85 | map.reserve(list.size_hint().1.unwrap_or_default()); 86 | for (key, value) in list { 87 | match map.get_mut(&key) { 88 | Some((_, count)) => *count += 1, 89 | None => { 90 | map.insert(key, (value, 1_usize)); 91 | } 92 | } 93 | } 94 | map 95 | } 96 | 97 | fn collect_into_key_value_eq_map< 98 | 'a, 99 | K: Hash + PartialEq + Eq + 'a, 100 | V: PartialEq + 'a, 101 | B: Iterator, 102 | >( 103 | list: B, 104 | ) -> HashMap<&'a K, (&'a V, usize)> { 105 | let mut map: HashMap<&K, (&V, usize)> = HashMap::default(); 106 | map.reserve(list.size_hint().1.unwrap_or_default()); 107 | 108 | for (key, value) in list { 109 | match map.get_mut(&key) { 110 | Some((ref current_val, count)) => match current_val == &value { 111 | true => *count += 1, 112 | false => { 113 | map.insert(key, (value, 1_usize)); 114 | } 115 | }, 116 | None => { 117 | map.insert(key, (value, 1_usize)); 118 | } 119 | } 120 | } 121 | map 122 | } 123 | 124 | enum Operation { 125 | Insert, 126 | Remove, 127 | } 128 | 129 | impl UnorderedMapLikeChange { 130 | fn new(item: (K, V), count: usize, insert_or_remove: Operation) -> Self { 131 | #[cfg(feature = "debug_asserts")] 132 | debug_assert_ne!(count, 0); 133 | match (insert_or_remove, count) { 134 | (Operation::Insert, 1) => UnorderedMapLikeChange::InsertSingle(item.0, item.1), 135 | (Operation::Insert, val) => UnorderedMapLikeChange::InsertMany(item.0, item.1, val), 136 | (Operation::Remove, 1) => UnorderedMapLikeChange::RemoveSingle(item.0), 137 | 138 | (Operation::Remove, val) => UnorderedMapLikeChange::RemoveMany(item.0, val), 139 | } 140 | } 141 | } 142 | 143 | pub fn unordered_hashcmp< 144 | 'a, 145 | #[cfg(feature = "nanoserde")] K: Hash + Clone + PartialEq + Eq + SerBin + DeBin + std::fmt::Debug + 'a, 146 | #[cfg(not(feature = "nanoserde"))] K: Hash + Clone + PartialEq + Eq + 'a, 147 | V: Clone + PartialEq + std::fmt::Debug + 'a, 148 | B: Iterator, 149 | >( 150 | previous: B, 151 | current: B, 152 | key_only: bool, 153 | ) -> Option> { 154 | let (mut previous, current) = if key_only { 155 | ( 156 | collect_into_key_eq_map(previous), 157 | collect_into_key_eq_map(current), 158 | ) 159 | } else { 160 | ( 161 | collect_into_key_value_eq_map(previous), 162 | collect_into_key_value_eq_map(current), 163 | ) 164 | }; 165 | 166 | if (current.len() as isize) < ((previous.len() as isize) - (current.len() as isize)) { 167 | return Some(UnorderedMapLikeDiff(UnorderedMapLikeDiffInternal::Replace( 168 | current 169 | .into_iter() 170 | .flat_map(|(k, (v, count))| std::iter::repeat_n((k, v), count)) 171 | .collect(), 172 | ))); 173 | } 174 | 175 | let mut ret: Vec> = 176 | Vec::with_capacity((previous.len() + current.len()) >> 1); 177 | 178 | for (&k, &(v, current_count)) in current.iter() { 179 | match previous.remove(&k) { 180 | Some((prev_val, prev_count)) if prev_val == v => { 181 | match (current_count as i128) - (prev_count as i128) { 182 | add if add > 1 => ret.push(UnorderedMapLikeChange::new( 183 | (k, v), 184 | add as usize, 185 | Operation::Insert, 186 | )), 187 | add if add == 1 => ret.push(UnorderedMapLikeChange::new( 188 | (k, v), 189 | add as usize, 190 | Operation::Insert, 191 | )), 192 | sub if sub < 0 => ret.push(UnorderedMapLikeChange::new( 193 | (k, v), 194 | -sub as usize, 195 | Operation::Remove, 196 | )), 197 | sub if sub == -1 => ret.push(UnorderedMapLikeChange::new( 198 | (k, v), 199 | -sub as usize, 200 | Operation::Remove, 201 | )), 202 | _ => (), 203 | } 204 | } 205 | Some((prev_val, prev_count)) if prev_val != v => { 206 | ret.push(UnorderedMapLikeChange::new( 207 | (k, prev_val), 208 | prev_count, 209 | Operation::Remove, 210 | )); 211 | ret.push(UnorderedMapLikeChange::new( 212 | (k, v), 213 | current_count, 214 | Operation::Insert, 215 | )); 216 | } 217 | Some(_) => unreachable!(), 218 | None => ret.push(UnorderedMapLikeChange::new( 219 | (k, v), 220 | current_count, 221 | Operation::Insert, 222 | )), 223 | } 224 | } 225 | 226 | for (k, (v, count)) in previous.into_iter() { 227 | ret.push(UnorderedMapLikeChange::new( 228 | (k, v), 229 | count, 230 | Operation::Remove, 231 | )) 232 | } 233 | 234 | ret.shrink_to_fit(); 235 | 236 | match ret.is_empty() { 237 | true => None, 238 | false => Some(UnorderedMapLikeDiff(UnorderedMapLikeDiffInternal::Modify( 239 | ret, 240 | ))), 241 | } 242 | } 243 | 244 | pub fn apply_unordered_hashdiffs< 245 | #[cfg(feature = "nanoserde")] K: Hash + Clone + PartialEq + Eq + SerBin + DeBin + 'static, 246 | #[cfg(not(feature = "nanoserde"))] K: Hash + Clone + PartialEq + Eq + 'static, 247 | V: Clone + 'static, 248 | B: IntoIterator, 249 | >( 250 | list: B, 251 | diffs: UnorderedMapLikeDiff, 252 | ) -> Box> { 253 | let diffs = match diffs { 254 | UnorderedMapLikeDiff(UnorderedMapLikeDiffInternal::Replace(replacement)) => { 255 | return Box::new(replacement.into_iter()); 256 | } 257 | UnorderedMapLikeDiff(UnorderedMapLikeDiffInternal::Modify(diffs)) => diffs, 258 | }; 259 | 260 | let (insertions, removals): (Vec<_>, Vec<_>) = diffs.into_iter().partition(|x| match &x { 261 | UnorderedMapLikeChange::InsertMany(..) | UnorderedMapLikeChange::InsertSingle(..) => true, 262 | UnorderedMapLikeChange::RemoveMany(..) | UnorderedMapLikeChange::RemoveSingle(..) => false, 263 | }); 264 | let holder: Vec<_> = list.into_iter().collect(); 265 | // let ref_holder: Vec<_> = holder.iter().map(|(k, v)| (k, v)).collect(); 266 | let mut list_hash = collect_into_key_eq_map(holder.iter().map(|t| (&t.0, &t.1))); 267 | 268 | for remove in removals { 269 | match remove { 270 | UnorderedMapLikeChange::RemoveMany(key, count) => match list_hash.get_mut(&key) { 271 | Some(val) if val.1 > count => { 272 | val.1 -= count; 273 | } 274 | Some(val) if val.1 <= count => { 275 | list_hash.remove(&key); 276 | } 277 | _ => (), 278 | }, 279 | UnorderedMapLikeChange::RemoveSingle(key) => match list_hash.get_mut(&key) { 280 | Some(val) if val.1 > 1 => { 281 | val.1 -= 1; 282 | } 283 | Some(val) if val.1 <= 1 => { 284 | list_hash.remove(&key); 285 | } 286 | _ => (), 287 | }, 288 | _ => unreachable!("Sorting failure"), 289 | } 290 | } 291 | 292 | for insertion in insertions.iter() { 293 | match insertion { 294 | UnorderedMapLikeChange::InsertMany(key, value, count) => { 295 | match list_hash.get_mut(&key) { 296 | Some(val) => { 297 | val.1 += count; 298 | } 299 | None => { 300 | list_hash.insert(key, (value, *count)); 301 | } 302 | } 303 | } 304 | UnorderedMapLikeChange::InsertSingle(key, value) => match list_hash.get_mut(&key) { 305 | Some(val) => { 306 | val.1 += 1; 307 | } 308 | None => { 309 | list_hash.insert(key, (value, 1)); 310 | } 311 | }, 312 | _ => { 313 | #[cfg(all(debug_assertions, feature = "debug_asserts"))] 314 | panic!("Sorting failure") 315 | } 316 | } 317 | } 318 | 319 | Box::new( 320 | list_hash 321 | .into_iter() 322 | .flat_map(|(k, (v, count))| std::iter::repeat_n((k.clone(), v.clone()), count)) 323 | .collect::>() 324 | .into_iter(), 325 | ) 326 | } 327 | 328 | #[cfg(feature = "nanoserde")] 329 | mod nanoserde_impls { 330 | use super::{ 331 | DeBin, SerBin, UnorderedMapLikeChange, UnorderedMapLikeDiff, UnorderedMapLikeDiffInternal, 332 | }; 333 | 334 | impl SerBin for UnorderedMapLikeChange 335 | where 336 | K: SerBin + PartialEq + Clone + DeBin, 337 | V: SerBin + PartialEq + Clone + DeBin, 338 | { 339 | fn ser_bin(&self, output: &mut Vec) { 340 | match self { 341 | Self::InsertMany(k, v, c) => { 342 | 0_u8.ser_bin(output); 343 | k.ser_bin(output); 344 | v.ser_bin(output); 345 | c.ser_bin(output); 346 | } 347 | Self::RemoveMany(k, c) => { 348 | 1_u8.ser_bin(output); 349 | k.ser_bin(output); 350 | c.ser_bin(output); 351 | } 352 | Self::InsertSingle(k, v) => { 353 | 2_u8.ser_bin(output); 354 | k.ser_bin(output); 355 | v.ser_bin(output); 356 | } 357 | Self::RemoveSingle(k) => { 358 | 3_u8.ser_bin(output); 359 | k.ser_bin(output); 360 | } 361 | } 362 | } 363 | } 364 | 365 | impl SerBin for &UnorderedMapLikeChange<&K, &V> 366 | where 367 | K: SerBin + PartialEq + Clone + DeBin, 368 | V: SerBin + PartialEq + Clone + DeBin, 369 | { 370 | fn ser_bin(&self, output: &mut Vec) { 371 | match *self { 372 | UnorderedMapLikeChange::InsertMany(k, v, c) => { 373 | 0_u8.ser_bin(output); 374 | k.ser_bin(output); 375 | v.ser_bin(output); 376 | c.ser_bin(output); 377 | } 378 | UnorderedMapLikeChange::RemoveMany(k, c) => { 379 | 1_u8.ser_bin(output); 380 | k.ser_bin(output); 381 | c.ser_bin(output); 382 | } 383 | UnorderedMapLikeChange::InsertSingle(k, v) => { 384 | 2_u8.ser_bin(output); 385 | k.ser_bin(output); 386 | v.ser_bin(output); 387 | } 388 | UnorderedMapLikeChange::RemoveSingle(k) => { 389 | 3_u8.ser_bin(output); 390 | k.ser_bin(output); 391 | } 392 | } 393 | } 394 | } 395 | 396 | impl SerBin for UnorderedMapLikeDiff 397 | where 398 | K: SerBin + PartialEq + Clone + DeBin, 399 | V: SerBin + PartialEq + Clone + DeBin, 400 | { 401 | fn ser_bin(&self, output: &mut Vec) { 402 | match &self.0 { 403 | UnorderedMapLikeDiffInternal::Replace(val) => { 404 | 0_u8.ser_bin(output); 405 | val.len().ser_bin(output); 406 | for (key, value) in val { 407 | key.ser_bin(output); 408 | value.ser_bin(output); 409 | } 410 | } 411 | UnorderedMapLikeDiffInternal::Modify(val) => { 412 | 1_u8.ser_bin(output); 413 | val.len().ser_bin(output); 414 | for change_spec in val { 415 | change_spec.ser_bin(output); 416 | } 417 | } 418 | } 419 | } 420 | } 421 | 422 | impl SerBin for &UnorderedMapLikeDiff<&K, &V> 423 | where 424 | K: SerBin + PartialEq + Clone + DeBin, 425 | V: SerBin + PartialEq + Clone + DeBin, 426 | { 427 | fn ser_bin(&self, output: &mut Vec) { 428 | match &self.0 { 429 | UnorderedMapLikeDiffInternal::Replace(val) => { 430 | 0_u8.ser_bin(output); 431 | val.len().ser_bin(output); 432 | for (key, value) in val { 433 | key.ser_bin(output); 434 | value.ser_bin(output); 435 | } 436 | } 437 | UnorderedMapLikeDiffInternal::Modify(val) => { 438 | 1_u8.ser_bin(output); 439 | val.len().ser_bin(output); 440 | for change_spec in val { 441 | change_spec.ser_bin(output); 442 | } 443 | } 444 | } 445 | } 446 | } 447 | 448 | impl DeBin for UnorderedMapLikeChange 449 | where 450 | K: SerBin + PartialEq + Clone + DeBin, 451 | V: SerBin + PartialEq + Clone + DeBin, 452 | { 453 | fn de_bin( 454 | offset: &mut usize, 455 | bytes: &[u8], 456 | ) -> Result, nanoserde::DeBinErr> { 457 | let id: u8 = DeBin::de_bin(offset, bytes)?; 458 | core::result::Result::Ok(match id { 459 | 0_u8 => UnorderedMapLikeChange::InsertMany( 460 | DeBin::de_bin(offset, bytes)?, 461 | DeBin::de_bin(offset, bytes)?, 462 | DeBin::de_bin(offset, bytes)?, 463 | ), 464 | 1_u8 => UnorderedMapLikeChange::RemoveMany( 465 | DeBin::de_bin(offset, bytes)?, 466 | DeBin::de_bin(offset, bytes)?, 467 | ), 468 | 2_u8 => UnorderedMapLikeChange::InsertSingle( 469 | DeBin::de_bin(offset, bytes)?, 470 | DeBin::de_bin(offset, bytes)?, 471 | ), 472 | 3_u8 => UnorderedMapLikeChange::RemoveSingle(DeBin::de_bin(offset, bytes)?), 473 | _ => { 474 | return core::result::Result::Err(nanoserde::DeBinErr { 475 | o: *offset, 476 | l: 0, 477 | s: bytes.len(), 478 | }) 479 | } 480 | }) 481 | } 482 | } 483 | 484 | impl DeBin for UnorderedMapLikeDiff 485 | where 486 | K: SerBin + PartialEq + Clone + DeBin, 487 | V: SerBin + PartialEq + Clone + DeBin, 488 | { 489 | fn de_bin( 490 | offset: &mut usize, 491 | bytes: &[u8], 492 | ) -> Result, nanoserde::DeBinErr> { 493 | let id: u8 = DeBin::de_bin(offset, bytes)?; 494 | core::result::Result::Ok(match id { 495 | 0_u8 => { 496 | let len: usize = DeBin::de_bin(offset, bytes)?; 497 | let mut contents: Vec<(K, V)> = Vec::new(); 498 | for _ in 0..len { 499 | let content = DeBin::de_bin(offset, bytes)?; 500 | contents.push(content); 501 | } 502 | UnorderedMapLikeDiff(UnorderedMapLikeDiffInternal::Replace(contents)) 503 | } 504 | 1_u8 => { 505 | let len: usize = DeBin::de_bin(offset, bytes)?; 506 | let mut contents: Vec> = Vec::new(); 507 | for _ in 0..len { 508 | let content = DeBin::de_bin(offset, bytes)?; 509 | contents.push(content); 510 | } 511 | UnorderedMapLikeDiff(UnorderedMapLikeDiffInternal::Modify(contents)) 512 | } 513 | _ => { 514 | return core::result::Result::Err(nanoserde::DeBinErr { 515 | o: *offset, 516 | l: 0, 517 | s: bytes.len(), 518 | }) 519 | } 520 | }) 521 | } 522 | } 523 | } 524 | 525 | #[cfg(test)] 526 | mod test { 527 | use std::collections::{BTreeMap, HashMap}; 528 | 529 | use super::{UnorderedMapLikeDiff, UnorderedMapLikeDiffInternal}; 530 | use crate::{Difference, StructDiff}; 531 | 532 | use crate as structdiff; 533 | 534 | #[test] 535 | fn test_key_only() { 536 | #[derive(Debug, PartialEq, Clone, Difference, Default)] 537 | #[difference(setters)] 538 | struct TestCollection { 539 | #[difference(collection_strategy = "unordered_map_like")] 540 | test1: HashMap, 541 | #[difference(collection_strategy = "unordered_map_like")] 542 | test2: BTreeMap, 543 | #[difference(collection_strategy = "unordered_map_like")] 544 | test3: HashMap, 545 | #[difference(collection_strategy = "unordered_map_like")] 546 | test4: BTreeMap, 547 | } 548 | 549 | let first = TestCollection { 550 | test1: vec![(10, 0), (15, 2), (20, 0), (25, 0), (30, 15)] 551 | .into_iter() 552 | .collect(), 553 | test2: vec![(10, 0), (15, 2), (20, 0), (25, 0)] 554 | .into_iter() 555 | .collect(), 556 | test3: vec![(10, 0), (15, 2), (20, 0), (25, 0), (30, 15)] 557 | .into_iter() 558 | .collect(), 559 | test4: vec![(10, 0), (15, 2), (20, 0), (25, 0)] 560 | .into_iter() 561 | .collect(), 562 | }; 563 | 564 | let second = TestCollection { 565 | test1: Default::default(), 566 | test2: vec![(10, 0), (15, 2), (20, 0), (25, 0), (10, 0)] 567 | .into_iter() 568 | .collect(), 569 | test3: vec![(10, 0), (15, 2), (20, 0), (25, 0)] 570 | .into_iter() 571 | .collect(), 572 | test4: vec![(10, 0), (15, 2), (20, 0), (25, 0), (15, 2)] 573 | .into_iter() 574 | .collect(), // add duplicated field 575 | }; 576 | 577 | let diffs = first.diff(&second); 578 | 579 | type TestCollectionFields = ::Diff; 580 | 581 | if let TestCollectionFields::test1(UnorderedMapLikeDiff( 582 | UnorderedMapLikeDiffInternal::Replace(val), 583 | )) = &diffs[0] 584 | { 585 | assert_eq!(val.len(), 0); 586 | } else { 587 | panic!("Collection strategy failure"); 588 | } 589 | 590 | let diffed = first.apply(diffs); 591 | 592 | use assert_unordered::assert_eq_unordered; 593 | assert_eq_unordered!(diffed.test1, second.test1); 594 | assert_eq_unordered!(diffed.test2, second.test2); 595 | assert_eq_unordered!(diffed.test3, second.test3); 596 | assert_eq_unordered!(diffed.test4, second.test4); 597 | } 598 | 599 | #[test] 600 | fn test_key_value() { 601 | #[derive(Debug, PartialEq, Clone, Difference, Default)] 602 | #[difference(setters)] 603 | struct TestCollection { 604 | #[difference( 605 | collection_strategy = "unordered_map_like", 606 | map_equality = "key_and_value" 607 | )] 608 | test1: HashMap, 609 | } 610 | 611 | let first = TestCollection { 612 | test1: vec![(10, 0), (15, 2), (20, 0), (25, 0), (30, 15)] 613 | .into_iter() 614 | .collect(), 615 | }; 616 | 617 | let second = TestCollection { 618 | test1: vec![(10, 21), (15, 2), (20, 0), (25, 0), (30, 15)] 619 | .into_iter() 620 | .collect(), 621 | }; 622 | 623 | let diffs = first.diff(&second); 624 | 625 | let diffed = first.clone().apply(diffs); 626 | 627 | use assert_unordered::assert_eq_unordered; 628 | assert_eq_unordered!(&diffed.test1, &second.test1); 629 | 630 | let diffs = first.diff_ref(&second); 631 | 632 | let diffed = first 633 | .clone() 634 | .apply(diffs.into_iter().map(Into::into).collect()); 635 | 636 | assert_eq_unordered!(diffed.test1, second.test1); 637 | } 638 | } 639 | -------------------------------------------------------------------------------- /src/collections/unordered_map_like_recursive.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "nanoserde")] 2 | use nanoserde::{DeBin, SerBin}; 3 | #[cfg(feature = "serde")] 4 | use serde::{Deserialize, Serialize}; 5 | #[cfg(feature = "debug_diffs")] 6 | use std::fmt::Debug; 7 | 8 | #[cfg(not(feature = "rustc_hash"))] 9 | type HashMap = std::collections::HashMap; 10 | #[cfg(feature = "rustc_hash")] 11 | type HashMap = 12 | std::collections::HashMap>; 13 | 14 | use std::{hash::Hash, marker::PhantomData}; 15 | 16 | use crate::StructDiff; 17 | 18 | #[cfg_attr(feature = "debug_diffs", derive(Debug))] 19 | #[derive(Clone)] 20 | #[cfg_attr(feature = "serde", derive(Serialize))] 21 | pub(crate) enum UnorderedMapLikeRecursiveChangeRef<'a, K: Clone, V: StructDiff + Clone> { 22 | Insert((&'a K, &'a V)), 23 | Remove(&'a K), 24 | Change((&'a K, Vec>)), 25 | } 26 | 27 | #[cfg_attr(feature = "debug_diffs", derive(Debug))] 28 | #[derive(Clone)] 29 | #[cfg_attr(feature = "serde", derive(Serialize))] 30 | pub(crate) enum UnorderedMapLikeRecursiveDiffInternalRef<'a, K: Clone, V: StructDiff + Clone> { 31 | Replace(Vec<(&'a K, &'a V)>), 32 | Modify(Vec>), 33 | } 34 | 35 | /// Used internally by StructDiff to track recursive changes to a map-like collection 36 | #[repr(transparent)] 37 | #[derive(Clone)] 38 | #[cfg_attr(feature = "debug_diffs", derive(Debug))] 39 | #[cfg_attr(feature = "serde", derive(Serialize))] 40 | pub struct UnorderedMapLikeRecursiveDiffRef<'a, K: Clone, V: StructDiff + Clone>( 41 | UnorderedMapLikeRecursiveDiffInternalRef<'a, K, V>, 42 | ); 43 | 44 | #[cfg_attr(feature = "debug_diffs", derive(Debug))] 45 | #[derive(Clone)] 46 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 47 | pub(crate) enum UnorderedMapLikeRecursiveChangeOwned { 48 | Insert((K, V)), 49 | Remove(K), 50 | Change((K, Vec)), 51 | } 52 | 53 | #[cfg_attr(feature = "debug_diffs", derive(Debug))] 54 | #[derive(Clone)] 55 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 56 | pub(crate) enum UnorderedMapLikeRecursiveDiffInternalOwned { 57 | Replace(Vec<(K, V)>), 58 | Modify(Vec>), 59 | } 60 | 61 | /// Used internally by StructDiff to track recursive changes to a map-like collection 62 | #[repr(transparent)] 63 | #[derive(Clone)] 64 | #[cfg_attr(feature = "debug_diffs", derive(Debug))] 65 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 66 | pub struct UnorderedMapLikeRecursiveDiffOwned( 67 | UnorderedMapLikeRecursiveDiffInternalOwned, 68 | ); 69 | 70 | impl<'a, K: Clone, V: StructDiff + Clone> From> 71 | for UnorderedMapLikeRecursiveDiffOwned 72 | { 73 | fn from(value: UnorderedMapLikeRecursiveDiffRef<'a, K, V>) -> Self { 74 | let new_inner: UnorderedMapLikeRecursiveDiffInternalOwned = match value.0 { 75 | UnorderedMapLikeRecursiveDiffInternalRef::Replace(vals) => { 76 | UnorderedMapLikeRecursiveDiffInternalOwned::Replace( 77 | vals.into_iter() 78 | .map(|(k, v)| (k.clone(), v.clone())) 79 | .collect(), 80 | ) 81 | } 82 | UnorderedMapLikeRecursiveDiffInternalRef::Modify(vals) => { 83 | let vals = vals 84 | .into_iter() 85 | .map(|change| match change { 86 | UnorderedMapLikeRecursiveChangeRef::Insert((k, v)) => { 87 | UnorderedMapLikeRecursiveChangeOwned::Insert((k.clone(), v.clone())) 88 | } 89 | UnorderedMapLikeRecursiveChangeRef::Remove(k) => { 90 | UnorderedMapLikeRecursiveChangeOwned::Remove(k.clone()) 91 | } 92 | UnorderedMapLikeRecursiveChangeRef::Change((k, diffs)) => { 93 | let diffs = diffs 94 | .into_iter() 95 | .map(|x| { 96 | let ret: V::Diff = x.into(); 97 | ret 98 | }) 99 | .collect(); 100 | UnorderedMapLikeRecursiveChangeOwned::Change((k.clone(), diffs)) 101 | } 102 | }) 103 | .collect::>>(); 104 | UnorderedMapLikeRecursiveDiffInternalOwned::Modify(vals) 105 | } 106 | }; 107 | UnorderedMapLikeRecursiveDiffOwned(new_inner) 108 | } 109 | } 110 | 111 | fn collect_into_key_eq_map< 112 | 'a, 113 | K: Hash + PartialEq + Eq + 'a, 114 | V: 'a, 115 | B: Iterator, 116 | >( 117 | list: B, 118 | ) -> HashMap<&'a K, &'a V> { 119 | let mut map: HashMap<&K, &V> = HashMap::default(); 120 | map.reserve(list.size_hint().1.unwrap_or_default()); 121 | 122 | for (key, value) in list { 123 | map.insert(key, value); 124 | } 125 | map 126 | } 127 | 128 | enum Operation 129 | where 130 | V: StructDiff, 131 | VDIFF: Into + Clone, 132 | { 133 | Insert, 134 | Remove, 135 | Change(Vec, PhantomData), 136 | } 137 | 138 | impl<'a, K: Clone, V: StructDiff + Clone> UnorderedMapLikeRecursiveChangeRef<'a, K, V> { 139 | fn new(item: (&'a K, &'a V), insert_or_remove: Operation>) -> Self 140 | where 141 | Self: 'a, 142 | { 143 | match insert_or_remove { 144 | Operation::Insert => UnorderedMapLikeRecursiveChangeRef::Insert((item.0, item.1)), 145 | Operation::Remove => UnorderedMapLikeRecursiveChangeRef::Remove(item.0), 146 | Operation::Change(diff, ..) => { 147 | UnorderedMapLikeRecursiveChangeRef::Change((item.0, diff)) 148 | } 149 | } 150 | } 151 | } 152 | 153 | pub fn unordered_hashcmp< 154 | 'a, 155 | #[cfg(feature = "nanoserde")] K: Hash + Clone + PartialEq + Eq + SerBin + DeBin + 'a, 156 | #[cfg(not(feature = "nanoserde"))] K: Hash + Clone + PartialEq + Eq + 'a, 157 | V: Clone + PartialEq + StructDiff + 'a, 158 | B: Iterator, 159 | >( 160 | previous: B, 161 | current: B, 162 | key_only: bool, 163 | ) -> Option> { 164 | let (previous, mut current) = ( 165 | collect_into_key_eq_map(previous), 166 | collect_into_key_eq_map(current), 167 | ); 168 | 169 | // TODO look at replacing remove/insert pairs with a new type of change (K1, K2, V::Diff) 170 | // for space optimization. This method is fast but may send extra data over the wire. 171 | 172 | if key_only { 173 | if (current.len() as isize) < ((previous.len() as isize) - (current.len() as isize)) { 174 | return Some(UnorderedMapLikeRecursiveDiffRef( 175 | UnorderedMapLikeRecursiveDiffInternalRef::Replace( 176 | current.into_iter().collect::>(), 177 | ), 178 | )); 179 | } 180 | 181 | let mut ret: Vec> = 182 | Vec::with_capacity((previous.len() + current.len()) >> 1); 183 | 184 | for prev_entry in previous.into_iter() { 185 | if current.remove_entry(prev_entry.0).is_none() { 186 | ret.push(UnorderedMapLikeRecursiveChangeRef::new( 187 | prev_entry, 188 | Operation::Remove, 189 | )); 190 | } 191 | } 192 | 193 | for add_entry in current.into_iter() { 194 | ret.push(UnorderedMapLikeRecursiveChangeRef::new( 195 | add_entry, 196 | Operation::Insert, 197 | )) 198 | } 199 | 200 | ret.shrink_to_fit(); 201 | 202 | match ret.is_empty() { 203 | true => None, 204 | false => Some(UnorderedMapLikeRecursiveDiffRef( 205 | UnorderedMapLikeRecursiveDiffInternalRef::Modify(ret), 206 | )), 207 | } 208 | } else { 209 | if (current.len() as isize) < ((previous.len() as isize) - (current.len() as isize)) { 210 | return Some(UnorderedMapLikeRecursiveDiffRef( 211 | UnorderedMapLikeRecursiveDiffInternalRef::Replace( 212 | current.into_iter().collect::>(), 213 | ), 214 | )); 215 | } 216 | 217 | let mut ret: Vec> = 218 | Vec::with_capacity((previous.len() + current.len()) >> 1); 219 | 220 | for prev_entry in previous.into_iter() { 221 | match current.remove_entry(prev_entry.0) { 222 | None => ret.push(UnorderedMapLikeRecursiveChangeRef::new( 223 | prev_entry, 224 | Operation::Remove, 225 | )), 226 | Some(current_entry) if prev_entry.1 != current_entry.1 => { 227 | ret.push(UnorderedMapLikeRecursiveChangeRef::new( 228 | current_entry, 229 | Operation::Change(prev_entry.1.diff_ref(current_entry.1), PhantomData), 230 | )) 231 | } 232 | _ => (), // no change 233 | } 234 | } 235 | 236 | for add_entry in current.into_iter() { 237 | ret.push(UnorderedMapLikeRecursiveChangeRef::new( 238 | add_entry, 239 | Operation::Insert, 240 | )) 241 | } 242 | 243 | ret.shrink_to_fit(); 244 | 245 | match ret.is_empty() { 246 | true => None, 247 | false => Some(UnorderedMapLikeRecursiveDiffRef( 248 | UnorderedMapLikeRecursiveDiffInternalRef::Modify(ret), 249 | )), 250 | } 251 | } 252 | } 253 | 254 | pub fn apply_unordered_hashdiffs< 255 | #[cfg(feature = "nanoserde")] K: Hash + Clone + PartialEq + Eq + SerBin + DeBin + 'static, 256 | #[cfg(not(feature = "nanoserde"))] K: Hash + Clone + PartialEq + Eq + 'static, 257 | V: Clone + StructDiff + 'static, 258 | B: IntoIterator, 259 | >( 260 | list: B, 261 | diffs: UnorderedMapLikeRecursiveDiffOwned, 262 | ) -> Box> { 263 | let diffs = match diffs { 264 | UnorderedMapLikeRecursiveDiffOwned( 265 | UnorderedMapLikeRecursiveDiffInternalOwned::Replace(replacement), 266 | ) => { 267 | return Box::new(replacement.into_iter()); 268 | } 269 | UnorderedMapLikeRecursiveDiffOwned(UnorderedMapLikeRecursiveDiffInternalOwned::Modify( 270 | diffs, 271 | )) => diffs, 272 | }; 273 | 274 | let (insertions, rem): (Vec<_>, Vec<_>) = diffs 275 | .into_iter() 276 | .partition(|x| matches!(&x, UnorderedMapLikeRecursiveChangeOwned::Insert(_))); 277 | let (removals, changes): (Vec<_>, Vec<_>) = rem 278 | .into_iter() 279 | .partition(|x| matches!(&x, UnorderedMapLikeRecursiveChangeOwned::Remove(_))); 280 | 281 | let mut list_hash = HashMap::::from_iter(list); 282 | 283 | for remove in removals { 284 | let UnorderedMapLikeRecursiveChangeOwned::Remove(key) = remove else { 285 | continue; 286 | }; 287 | list_hash.remove(&key); 288 | } 289 | 290 | for change in changes { 291 | let UnorderedMapLikeRecursiveChangeOwned::Change((key, diff)) = change else { 292 | continue; 293 | }; 294 | let Some(to_change) = list_hash.get_mut(&key) else { 295 | continue; 296 | }; 297 | to_change.apply_mut(diff); 298 | } 299 | 300 | for insert in insertions { 301 | let UnorderedMapLikeRecursiveChangeOwned::Insert((key, value)) = insert else { 302 | continue; 303 | }; 304 | list_hash.insert(key, value); 305 | } 306 | 307 | Box::new(list_hash.into_iter()) 308 | } 309 | 310 | #[cfg(feature = "nanoserde")] 311 | mod nanoserde_impls { 312 | use crate::StructDiff; 313 | 314 | use super::{ 315 | DeBin, SerBin, UnorderedMapLikeRecursiveChangeOwned, UnorderedMapLikeRecursiveChangeRef, 316 | UnorderedMapLikeRecursiveDiffInternalOwned, UnorderedMapLikeRecursiveDiffInternalRef, 317 | UnorderedMapLikeRecursiveDiffOwned, UnorderedMapLikeRecursiveDiffRef, 318 | }; 319 | 320 | impl SerBin for UnorderedMapLikeRecursiveChangeOwned 321 | where 322 | K: SerBin + PartialEq + Clone + DeBin, 323 | V: SerBin + PartialEq + Clone + DeBin + StructDiff, 324 | { 325 | fn ser_bin(&self, output: &mut Vec) { 326 | match self { 327 | Self::Insert(val) => { 328 | 0_u8.ser_bin(output); 329 | val.ser_bin(output); 330 | } 331 | Self::Remove(val) => { 332 | 1_u8.ser_bin(output); 333 | val.ser_bin(output); 334 | } 335 | Self::Change(val) => { 336 | 2_u8.ser_bin(output); 337 | val.ser_bin(output); 338 | } 339 | } 340 | } 341 | } 342 | 343 | impl SerBin for UnorderedMapLikeRecursiveChangeRef<'_, K, V> 344 | where 345 | K: SerBin + PartialEq + Clone, 346 | V: SerBin + PartialEq + Clone + StructDiff, 347 | { 348 | fn ser_bin(&self, output: &mut Vec) { 349 | match self { 350 | Self::Insert(val) => { 351 | 0_u8.ser_bin(output); 352 | val.0.ser_bin(output); 353 | val.1.ser_bin(output); 354 | } 355 | Self::Remove(val) => { 356 | 1_u8.ser_bin(output); 357 | val.ser_bin(output); 358 | } 359 | Self::Change(val) => { 360 | 2_u8.ser_bin(output); 361 | val.0.ser_bin(output); 362 | val.1.ser_bin(output); 363 | } 364 | } 365 | } 366 | } 367 | 368 | impl SerBin for UnorderedMapLikeRecursiveDiffOwned 369 | where 370 | K: SerBin + PartialEq + Clone + DeBin, 371 | V: SerBin + PartialEq + Clone + DeBin + StructDiff, 372 | { 373 | fn ser_bin(&self, output: &mut Vec) { 374 | match &self.0 { 375 | UnorderedMapLikeRecursiveDiffInternalOwned::Replace(val) => { 376 | 0_u8.ser_bin(output); 377 | val.ser_bin(output); 378 | } 379 | UnorderedMapLikeRecursiveDiffInternalOwned::Modify(val) => { 380 | 1_u8.ser_bin(output); 381 | val.ser_bin(output); 382 | } 383 | } 384 | } 385 | } 386 | 387 | impl SerBin for UnorderedMapLikeRecursiveDiffRef<'_, K, V> 388 | where 389 | K: SerBin + PartialEq + Clone, 390 | V: SerBin + PartialEq + Clone + StructDiff, 391 | { 392 | fn ser_bin(&self, output: &mut Vec) { 393 | match &self.0 { 394 | UnorderedMapLikeRecursiveDiffInternalRef::Replace(val) => { 395 | 0_u8.ser_bin(output); 396 | val.len().ser_bin(output); 397 | for (key, value) in val { 398 | key.ser_bin(output); 399 | value.ser_bin(output) 400 | } 401 | } 402 | UnorderedMapLikeRecursiveDiffInternalRef::Modify(val) => { 403 | 1_u8.ser_bin(output); 404 | val.ser_bin(output); 405 | } 406 | } 407 | } 408 | } 409 | 410 | impl SerBin for &UnorderedMapLikeRecursiveDiffRef<'_, K, V> 411 | where 412 | K: SerBin + PartialEq + Clone, 413 | V: SerBin + PartialEq + Clone + StructDiff, 414 | { 415 | #[inline(always)] 416 | fn ser_bin(&self, output: &mut Vec) { 417 | (*self).ser_bin(output) 418 | } 419 | } 420 | 421 | impl DeBin for UnorderedMapLikeRecursiveChangeOwned 422 | where 423 | K: SerBin + PartialEq + Clone + DeBin, 424 | V: SerBin + PartialEq + Clone + DeBin + StructDiff, 425 | { 426 | fn de_bin( 427 | offset: &mut usize, 428 | bytes: &[u8], 429 | ) -> Result, nanoserde::DeBinErr> { 430 | let id: u8 = DeBin::de_bin(offset, bytes)?; 431 | core::result::Result::Ok(match id { 432 | 0_u8 => UnorderedMapLikeRecursiveChangeOwned::Insert(DeBin::de_bin(offset, bytes)?), 433 | 1_u8 => UnorderedMapLikeRecursiveChangeOwned::Remove(DeBin::de_bin(offset, bytes)?), 434 | 2_u8 => UnorderedMapLikeRecursiveChangeOwned::Change(DeBin::de_bin(offset, bytes)?), 435 | _ => { 436 | return core::result::Result::Err(nanoserde::DeBinErr { 437 | o: *offset, 438 | l: 0, 439 | s: bytes.len(), 440 | }) 441 | } 442 | }) 443 | } 444 | } 445 | 446 | impl DeBin for UnorderedMapLikeRecursiveDiffOwned 447 | where 448 | K: SerBin + PartialEq + Clone + DeBin, 449 | V: SerBin + PartialEq + Clone + DeBin + StructDiff, 450 | { 451 | fn de_bin( 452 | offset: &mut usize, 453 | bytes: &[u8], 454 | ) -> Result, nanoserde::DeBinErr> { 455 | let id: u8 = DeBin::de_bin(offset, bytes)?; 456 | core::result::Result::Ok(match id { 457 | 0_u8 => UnorderedMapLikeRecursiveDiffOwned( 458 | UnorderedMapLikeRecursiveDiffInternalOwned::Replace(DeBin::de_bin( 459 | offset, bytes, 460 | )?), 461 | ), 462 | 1_u8 => UnorderedMapLikeRecursiveDiffOwned( 463 | UnorderedMapLikeRecursiveDiffInternalOwned::Modify(DeBin::de_bin( 464 | offset, bytes, 465 | )?), 466 | ), 467 | _ => { 468 | return core::result::Result::Err(nanoserde::DeBinErr { 469 | o: *offset, 470 | l: 0, 471 | s: bytes.len(), 472 | }) 473 | } 474 | }) 475 | } 476 | } 477 | } 478 | 479 | #[cfg(test)] 480 | mod test { 481 | #[cfg(feature = "nanoserde")] 482 | use nanoserde::{DeBin, SerBin}; 483 | #[cfg(feature = "serde")] 484 | use serde::{Deserialize, Serialize}; 485 | 486 | use crate::{Difference, StructDiff}; 487 | use std::collections::{BTreeMap, HashMap}; 488 | 489 | use crate as structdiff; 490 | 491 | #[test] 492 | fn test_key_only() { 493 | #[cfg_attr(feature = "nanoserde", derive(DeBin, SerBin))] 494 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 495 | #[derive(Debug, PartialEq, Clone, Difference, Default)] 496 | pub struct TestRecurse { 497 | recurse1: i32, 498 | recurse2: Option, 499 | } 500 | 501 | #[derive(Debug, PartialEq, Clone, Difference, Default)] 502 | struct TestCollection { 503 | #[difference( 504 | collection_strategy = "unordered_map_like", 505 | recurse, 506 | map_equality = "key_only" 507 | )] 508 | test1: HashMap, 509 | #[difference(collection_strategy = "unordered_map_like", map_equality = "key_only")] 510 | test2: BTreeMap, 511 | } 512 | 513 | let first = TestCollection { 514 | test1: vec![ 515 | ( 516 | 10, 517 | TestRecurse { 518 | recurse1: 0, 519 | recurse2: None, 520 | }, 521 | ), 522 | ( 523 | 15, 524 | TestRecurse { 525 | recurse1: 2, 526 | recurse2: Some("Hello".to_string()), 527 | }, 528 | ), 529 | ] 530 | .into_iter() 531 | .collect(), 532 | test2: vec![(10, 0), (15, 2), (20, 0), (25, 2)] 533 | .into_iter() 534 | .collect(), 535 | }; 536 | 537 | let second = TestCollection { 538 | test1: vec![ 539 | ( 540 | 11, 541 | TestRecurse { 542 | recurse1: 0, 543 | recurse2: Some("Hello World".to_string()), 544 | }, 545 | ), 546 | ( 547 | 15, 548 | TestRecurse { 549 | recurse1: 2, 550 | recurse2: Some("Hello World".to_string()), 551 | }, 552 | ), 553 | ] 554 | .into_iter() 555 | .collect(), 556 | test2: vec![(10, 0), (15, 2), (20, 0), (25, 0)] 557 | .into_iter() 558 | .collect(), 559 | }; 560 | 561 | let diffs = first.diff(&second); 562 | assert_eq!(diffs.len(), 2); 563 | let diffed = first.apply(diffs); 564 | 565 | use assert_unordered::assert_eq_unordered; 566 | assert_eq_unordered!( 567 | diffed.test1.keys().collect::>(), 568 | second.test1.keys().collect::>() 569 | ); 570 | assert_eq!(diffed.test1[&11], second.test1[&11]); 571 | assert_ne!(diffed.test1[&15], second.test1[&15]); 572 | assert_eq_unordered!(diffed.test2, second.test2); 573 | } 574 | 575 | #[test] 576 | fn test_key_value() { 577 | #[cfg_attr(feature = "nanoserde", derive(DeBin, SerBin))] 578 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 579 | #[derive(Debug, PartialEq, Clone, Difference, Default)] 580 | #[difference(setters)] 581 | pub struct TestRecurse { 582 | recurse1: i32, 583 | recurse2: Option, 584 | } 585 | 586 | #[derive(Debug, PartialEq, Clone, Difference, Default)] 587 | #[difference(setters)] 588 | struct TestCollection { 589 | #[difference( 590 | collection_strategy = "unordered_map_like", 591 | map_equality = "key_and_value", 592 | recurse 593 | )] 594 | test1: HashMap, 595 | #[difference( 596 | collection_strategy = "unordered_map_like", 597 | map_equality = "key_and_value" 598 | )] 599 | test2: BTreeMap, 600 | } 601 | 602 | let first = TestCollection { 603 | test1: vec![ 604 | ( 605 | 10, 606 | TestRecurse { 607 | recurse1: 0, 608 | recurse2: None, 609 | }, 610 | ), 611 | ( 612 | 15, 613 | TestRecurse { 614 | recurse1: 2, 615 | recurse2: Some("Hello".to_string()), 616 | }, 617 | ), 618 | ] 619 | .into_iter() 620 | .collect(), 621 | test2: vec![(10, 0), (15, 2), (20, 0), (25, 2)] 622 | .into_iter() 623 | .collect(), 624 | }; 625 | 626 | let second = TestCollection { 627 | test1: vec![ 628 | ( 629 | 11, 630 | TestRecurse { 631 | recurse1: 0, 632 | recurse2: Some("Hello World".to_string()), 633 | }, 634 | ), 635 | ( 636 | 15, 637 | TestRecurse { 638 | recurse1: 2, 639 | recurse2: Some("Hello World".to_string()), 640 | }, 641 | ), 642 | ] 643 | .into_iter() 644 | .collect(), 645 | test2: vec![(10, 0), (15, 2), (20, 0), (25, 0), (10, 0)] 646 | .into_iter() 647 | .collect(), 648 | }; 649 | 650 | let diffs = first.diff(&second); 651 | let diffed = first.apply(diffs); 652 | 653 | use assert_unordered::assert_eq_unordered; 654 | assert_eq_unordered!(diffed.test1, second.test1); 655 | assert_eq_unordered!(diffed.test2, second.test2); 656 | } 657 | } 658 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "nanoserde")] 2 | use nanoserde::{DeBin, SerBin}; 3 | 4 | #[cfg(feature = "serde")] 5 | use serde::{de::DeserializeOwned, Serialize}; 6 | 7 | pub use structdiff_derive::Difference; 8 | 9 | pub mod collections; 10 | 11 | #[cfg(all(feature = "nanoserde", feature = "serde", feature = "debug_diffs"))] 12 | pub(crate) mod __private { 13 | use super::*; 14 | pub trait StructDiffOwnedBound: 15 | SerBin + DeBin + Serialize + DeserializeOwned + Clone + std::fmt::Debug 16 | { 17 | } 18 | impl 19 | StructDiffOwnedBound for T 20 | { 21 | } 22 | 23 | pub trait StructDiffRefBound: SerBin + Serialize + Clone + std::fmt::Debug {} 24 | impl StructDiffRefBound for T {} 25 | } 26 | 27 | #[cfg(all(feature = "nanoserde", not(feature = "serde"), feature = "debug_diffs"))] 28 | pub(crate) mod __private { 29 | use super::*; 30 | 31 | pub trait StructDiffOwnedBound: SerBin + DeBin + Clone + std::fmt::Debug {} 32 | impl StructDiffOwnedBound for T {} 33 | 34 | pub trait StructDiffRefBound: SerBin + Clone + std::fmt::Debug {} 35 | impl StructDiffRefBound for T {} 36 | } 37 | 38 | #[cfg(all(feature = "serde", not(feature = "nanoserde"), feature = "debug_diffs"))] 39 | pub(crate) mod __private { 40 | use super::*; 41 | 42 | pub trait StructDiffOwnedBound: Serialize + DeserializeOwned + Clone + std::fmt::Debug {} 43 | impl StructDiffOwnedBound for T {} 44 | 45 | pub trait StructDiffRefBound: Serialize + Clone + std::fmt::Debug {} 46 | impl StructDiffRefBound for T {} 47 | } 48 | 49 | #[cfg(all( 50 | not(feature = "serde"), 51 | not(feature = "nanoserde"), 52 | feature = "debug_diffs" 53 | ))] 54 | pub(crate) mod __private { 55 | use super::*; 56 | 57 | pub trait StructDiffOwnedBound: Clone + std::fmt::Debug {} 58 | impl StructDiffOwnedBound for T {} 59 | 60 | pub trait StructDiffRefBound: Clone + std::fmt::Debug {} 61 | impl StructDiffRefBound for T {} 62 | } 63 | 64 | #[cfg(all(feature = "nanoserde", feature = "serde", not(feature = "debug_diffs")))] 65 | pub(crate) mod __private { 66 | use super::*; 67 | pub trait StructDiffOwnedBound: SerBin + DeBin + Serialize + DeserializeOwned + Clone {} 68 | impl StructDiffOwnedBound for T {} 69 | 70 | pub trait StructDiffRefBound: SerBin + Serialize + Clone {} 71 | impl StructDiffRefBound for T {} 72 | } 73 | 74 | #[cfg(all( 75 | feature = "nanoserde", 76 | not(feature = "serde"), 77 | not(feature = "debug_diffs") 78 | ))] 79 | pub(crate) mod __private { 80 | use super::*; 81 | 82 | pub trait StructDiffOwnedBound: SerBin + DeBin + Clone {} 83 | impl StructDiffOwnedBound for T {} 84 | 85 | pub trait StructDiffRefBound: SerBin + Clone {} 86 | impl StructDiffRefBound for T {} 87 | } 88 | 89 | #[cfg(all( 90 | feature = "serde", 91 | not(feature = "nanoserde"), 92 | not(feature = "debug_diffs") 93 | ))] 94 | pub(crate) mod __private { 95 | use super::*; 96 | 97 | pub trait StructDiffOwnedBound: Serialize + DeserializeOwned + Clone {} 98 | impl StructDiffOwnedBound for T {} 99 | 100 | pub trait StructDiffRefBound: Serialize + Clone {} 101 | impl StructDiffRefBound for T {} 102 | } 103 | 104 | #[cfg(all( 105 | not(feature = "serde"), 106 | not(feature = "nanoserde"), 107 | not(feature = "debug_diffs") 108 | ))] 109 | pub(crate) mod __private { 110 | 111 | pub trait StructDiffOwnedBound: Clone {} 112 | impl StructDiffOwnedBound for T {} 113 | 114 | pub trait StructDiffRefBound: Clone {} 115 | impl StructDiffRefBound for T {} 116 | } 117 | 118 | pub trait StructDiff { 119 | /// A generated type used to represent the difference 120 | /// between two instances of a struct which implements 121 | /// the StructDiff trait. 122 | type Diff: __private::StructDiffOwnedBound; 123 | 124 | /// A generated type used to represent the difference 125 | /// between two instances of a struct which implements 126 | /// the StructDiff trait (using references). 127 | type DiffRef<'target>: __private::StructDiffRefBound + Into 128 | where 129 | Self: 'target; 130 | 131 | /// Generate a diff between two instances of a struct. 132 | /// This diff may be serialized if one of the serialization 133 | /// features is enabled. 134 | /// 135 | /// ``` 136 | /// use structdiff::{Difference, StructDiff}; 137 | /// 138 | /// #[derive(Debug, PartialEq, Clone, Difference)] 139 | /// struct Example { 140 | /// field1: f64, 141 | /// } 142 | /// 143 | /// let first = Example { 144 | /// field1: 0.0, 145 | /// }; 146 | /// 147 | /// let second = Example { 148 | /// field1: 3.14, 149 | /// }; 150 | /// 151 | /// let diffs: Vec<::Diff> = first.diff(&second); 152 | /// 153 | /// let diffed = first.apply(diffs); 154 | /// assert_eq!(diffed, second); 155 | /// ``` 156 | fn diff(&self, updated: &Self) -> Vec; 157 | 158 | /// Generate a diff between two instances of a struct, for 159 | /// use in passing to serializer. Much more efficient for 160 | /// structs with large fields where the diff will not be stored. 161 | /// 162 | /// ``` 163 | /// use structdiff::{Difference, StructDiff}; 164 | /// 165 | /// #[derive(Debug, PartialEq, Clone, Difference)] 166 | /// struct Example { 167 | /// field1: f64, 168 | /// } 169 | /// 170 | /// let first = Example { 171 | /// field1: 0.0, 172 | /// }; 173 | /// 174 | /// let second = Example { 175 | /// field1: 3.14, 176 | /// }; 177 | /// 178 | /// let diffs: Vec<::DiffRef<'_>> = first.diff_ref(&second); 179 | /// 180 | /// let diffed = first.clone().apply(diffs.into_iter().map(Into::into).collect()); 181 | /// assert_eq!(diffed, second); 182 | /// ``` 183 | fn diff_ref<'target>(&'target self, updated: &'target Self) -> Vec>; 184 | 185 | /// Apply a single-field diff to a mutable self ref 186 | fn apply_single(&mut self, diff: Self::Diff); 187 | 188 | /// Apply a full diff to an owned self 189 | fn apply(mut self, diffs: Vec) -> Self 190 | where 191 | Self: Sized, 192 | { 193 | for diff in diffs { 194 | self.apply_single(diff); 195 | } 196 | self 197 | } 198 | 199 | /// Apply a full diff to a self ref, returning a cloned version of self 200 | /// after diff is applied 201 | fn apply_ref(&self, diffs: Vec) -> Self 202 | where 203 | Self: Clone, 204 | { 205 | self.clone().apply(diffs) 206 | } 207 | 208 | /// Apply a full diff to a mutable self ref 209 | fn apply_mut(&mut self, diffs: Vec) { 210 | for diff in diffs { 211 | self.apply_single(diff); 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /tests/derives.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports, clippy::type_complexity)] 2 | 3 | use std::{ 4 | collections::{BTreeMap, BTreeSet, HashMap, HashSet}, 5 | fmt::Debug, 6 | num::Wrapping, 7 | }; 8 | 9 | use structdiff::{Difference, StructDiff}; 10 | 11 | // Trying to come up with all the edge cases that might be relevant 12 | #[allow(dead_code)] 13 | #[cfg(not(any(feature = "serde", feature = "nanoserde")))] 14 | #[derive(Difference)] 15 | // #[difference(setters)] 16 | pub struct TestDeriveAll< 17 | 'a, 18 | 'b: 'a, 19 | A: PartialEq + 'static, 20 | const C: usize, 21 | B, 22 | D, 23 | LM: Ord = Option, 24 | const N: usize = 4, 25 | > where 26 | A: core::hash::Hash + std::cmp::Eq + Default, 27 | LM: Ord + IntoIterator, 28 | [A; N]: Default, 29 | [B; C]: Default, 30 | [i32; N]: Default, 31 | [B; N]: Default, 32 | dyn Fn(&B): PartialEq + Clone + core::fmt::Debug, 33 | (dyn core::fmt::Debug + Send + 'static): Debug, 34 | { 35 | f1: (), 36 | f2: [A; N], 37 | f3: [i32; N], 38 | f4: BTreeMap::Item>>, 39 | f5: Option<(A, Option<&'a ::Item>)>, 40 | f6: HashMap>, 41 | f7: Box<(Vec, HashSet, [i128; u8::MIN as usize])>, 42 | f8: BTreeSet>, 43 | #[difference(skip)] 44 | f9: [B; C], 45 | f10: [B; N], 46 | r#f11: Option<&'b Option>, 47 | #[difference(skip)] 48 | f12: Option>, 49 | #[difference(skip)] 50 | f13: Vec !>, 51 | #[difference(skip)] 52 | f14: Vec Box i32>>>, 53 | #[difference(skip)] 54 | f15: Vec, 55 | } 56 | -------------------------------------------------------------------------------- /tests/enums.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | #[allow(unused_imports)] 4 | use std::{ 5 | collections::{BTreeMap, BTreeSet, HashMap}, 6 | fmt::Debug, 7 | num::Wrapping, 8 | }; 9 | use structdiff::{Difference, StructDiff}; 10 | 11 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 12 | #[derive(Debug, PartialEq, Clone, Difference, Default)] 13 | #[difference(setters)] 14 | pub struct Test { 15 | pub test1: i32, 16 | pub test2: String, 17 | pub test3: Vec, 18 | pub test4: f32, 19 | pub test5: Option, 20 | } 21 | 22 | #[derive(Debug, PartialEq, Clone, Difference)] 23 | #[difference(setters)] 24 | pub struct TestSkip 25 | where 26 | A: PartialEq, 27 | { 28 | pub test1: A, 29 | pub test2: String, 30 | #[difference(skip)] 31 | pub test3skip: Vec, 32 | pub test4: f32, 33 | } 34 | 35 | #[allow(unused)] 36 | #[cfg(not(any(feature = "serde", feature = "nanoserde")))] 37 | #[derive(PartialEq, Difference, Clone, Debug)] 38 | // #[derive(PartialEq, Clone, Debug)] 39 | pub enum TestDeriveAllEnum< 40 | 'a, 41 | 'b: 'a, 42 | A: PartialEq + 'static, 43 | const C: usize, 44 | B: PartialEq, 45 | D, 46 | LM: Ord = Option, 47 | const N: usize = 4, 48 | > where 49 | A: core::hash::Hash + std::cmp::Eq + Default, 50 | LM: Ord + IntoIterator, 51 | [A; N]: Default, 52 | [B; C]: Default, 53 | [i32; N]: Default, 54 | [B; N]: Default, 55 | dyn Fn(&B): PartialEq + Clone + core::fmt::Debug, 56 | (dyn std::cmp::PartialEq + Send + 'static): Debug + Clone + PartialEq, 57 | { 58 | F1(()), 59 | F2([A; N]), 60 | F3([i32; N]), 61 | F4(BTreeMap::Item>>), 62 | F5(Option<(A, Option<&'a ::Item>)>), 63 | F6(HashMap>), 64 | F8(BTreeSet>, BTreeSet>), 65 | F9 {}, 66 | F10 { subfield1: u64, subfield2: Test }, 67 | r#F11(Option<&'b Option>), 68 | F12(TestSkip, TestSkip), 69 | F13((TestSkip, TestSkip)), 70 | } 71 | -------------------------------------------------------------------------------- /tests/expose.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use assert_unordered::{assert_eq_unordered, assert_eq_unordered_sort}; 4 | 5 | use std::f64::consts::PI; 6 | use std::hash::Hash; 7 | use std::{ 8 | collections::{BTreeMap, BTreeSet, HashMap, HashSet, LinkedList}, 9 | fmt::Debug, 10 | num::Wrapping, 11 | }; 12 | use structdiff::{Difference, StructDiff}; 13 | 14 | #[cfg(feature = "serde")] 15 | use serde::{Deserialize, Serialize}; 16 | 17 | #[cfg(feature = "nanoserde")] 18 | use nanoserde::{DeBin, SerBin}; 19 | 20 | #[test] 21 | fn test_expose() { 22 | #[derive(Debug, PartialEq, Clone, Difference)] 23 | #[difference(expose)] 24 | struct Example { 25 | field1: f64, 26 | } 27 | 28 | let first = Example { field1: 0.0 }; 29 | 30 | let second = Example { field1: PI }; 31 | 32 | for diff in first.diff(&second) { 33 | match diff { 34 | ExampleStructDiffEnum::field1(v) => { 35 | dbg!(&v); 36 | } 37 | } 38 | } 39 | 40 | for diff in first.diff_ref(&second) { 41 | match diff { 42 | ExampleStructDiffEnumRef::field1(v) => { 43 | dbg!(&v); 44 | } 45 | } 46 | } 47 | } 48 | 49 | #[test] 50 | fn test_expose_rename() { 51 | #[derive(Debug, PartialEq, Clone, Difference)] 52 | #[difference(expose = "Cheese")] 53 | struct Example { 54 | field1: f64, 55 | } 56 | 57 | let first = Example { field1: 0.0 }; 58 | 59 | let second = Example { field1: PI }; 60 | 61 | for diff in first.diff(&second) { 62 | match diff { 63 | Cheese::field1(_v) => {} 64 | } 65 | } 66 | 67 | for diff in first.diff_ref(&second) { 68 | match diff { 69 | CheeseRef::field1(_v) => {} 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/integration.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | mod derives; 4 | mod enums; 5 | mod expose; 6 | mod types; 7 | use assert_unordered::{assert_eq_unordered, assert_eq_unordered_sort}; 8 | pub use types::{RandValue, Test, TestEnum, TestSkip}; 9 | 10 | use std::f32::consts::PI; 11 | use std::hash::Hash; 12 | use std::{ 13 | collections::{BTreeMap, BTreeSet, HashMap, HashSet, LinkedList}, 14 | fmt::Debug, 15 | num::Wrapping, 16 | }; 17 | use structdiff::{Difference, StructDiff}; 18 | 19 | #[cfg(feature = "serde")] 20 | use serde::{Deserialize, Serialize}; 21 | 22 | #[cfg(feature = "nanoserde")] 23 | use nanoserde::{DeBin, SerBin}; 24 | 25 | macro_rules! nanoserde_ref_test { 26 | ($first:ident, $second:ident) => { 27 | #[cfg(feature = "nanoserde")] 28 | assert_eq!( 29 | nanoserde::SerBin::serialize_bin(&(&$first).diff(&$second)), 30 | nanoserde::SerBin::serialize_bin(&(&$first).diff_ref(&$second)) 31 | ) 32 | }; 33 | } 34 | 35 | #[test] 36 | /// This should match the code used in README.md 37 | fn test_example() { 38 | #[derive(Debug, PartialEq, Clone, Difference)] 39 | struct Example { 40 | field1: f64, 41 | #[difference(skip)] 42 | field2: Vec, 43 | #[difference(collection_strategy = "unordered_array_like")] 44 | field3: BTreeSet, 45 | } 46 | 47 | let first = Example { 48 | field1: 0.0, 49 | field2: vec![], 50 | field3: vec![1, 2, 3].into_iter().collect(), 51 | }; 52 | 53 | let second = Example { 54 | field1: PI as f64, 55 | field2: vec![1], 56 | field3: vec![2, 3, 4].into_iter().collect(), 57 | }; 58 | 59 | let diffs = first.diff(&second); 60 | // diffs is now a Vec of differences, with length 61 | // equal to number of changed/unskipped fields 62 | assert_eq!(diffs.len(), 2); 63 | 64 | let diffed = first.apply(diffs); 65 | // diffed is now equal to second, except for skipped field 66 | assert_eq!(diffed.field1, second.field1); 67 | assert_eq!(&diffed.field3, &second.field3); 68 | assert_ne!(diffed, second); 69 | } 70 | 71 | #[test] 72 | fn test_derive() { 73 | let first: Test = Test { 74 | test1: 0, 75 | test2: String::new(), 76 | test3: Vec::new(), 77 | test4: 0.0, 78 | test5: None, 79 | }; 80 | 81 | let second = Test { 82 | test1: first.test1, 83 | test2: String::from("Hello Diff"), 84 | test3: vec![1], 85 | test4: PI, 86 | test5: Some(12), 87 | }; 88 | 89 | let diffs = first.diff(&second); 90 | let diffed = first.clone().apply(diffs); 91 | 92 | assert_eq!(&diffed, &second); 93 | nanoserde_ref_test!(first, second); 94 | } 95 | 96 | #[test] 97 | fn test_derive_with_skip() { 98 | let first: TestSkip = TestSkip { 99 | test1: 0, 100 | test2: String::new(), 101 | test3skip: Vec::new(), 102 | test4: 0.0, 103 | }; 104 | 105 | let second: TestSkip = TestSkip { 106 | test1: first.test1, 107 | test2: String::from("Hello Diff"), 108 | test3skip: vec![1], 109 | test4: PI, 110 | }; 111 | 112 | let diffs = first.diff(&second); 113 | 114 | #[cfg(feature = "serde")] 115 | { 116 | let ser_diff = bincode::serialize(&diffs).unwrap(); 117 | let deser_diff = bincode::deserialize(&ser_diff).unwrap(); 118 | let diffed_serde = first.clone().apply(deser_diff); 119 | 120 | assert_eq!(diffed_serde.test1, second.test1); 121 | assert_eq!(diffed_serde.test2, second.test2); 122 | assert_ne!(diffed_serde.test3skip, second.test3skip); 123 | assert_eq!(diffed_serde.test4, second.test4); 124 | } 125 | 126 | #[cfg(feature = "nanoserde")] 127 | { 128 | let ser = SerBin::serialize_bin(&diffs); 129 | let diffed_serde = first.clone().apply(DeBin::deserialize_bin(&ser).unwrap()); 130 | 131 | assert_eq!(diffed_serde.test1, second.test1); 132 | assert_eq!(diffed_serde.test2, second.test2); 133 | assert_ne!(diffed_serde.test3skip, second.test3skip); 134 | assert_eq!(diffed_serde.test4, second.test4); 135 | } 136 | 137 | let diffed = first.clone().apply(diffs); 138 | 139 | //check that all except the skipped are changed 140 | assert_eq!(diffed.test1, second.test1); 141 | assert_eq!(diffed.test2, second.test2); 142 | assert_ne!(diffed.test3skip, second.test3skip); 143 | assert_eq!(diffed.test4, second.test4); 144 | 145 | nanoserde_ref_test!(first, second); 146 | } 147 | 148 | #[derive(Debug, PartialEq, Clone, Difference)] 149 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 150 | #[cfg_attr(feature = "nanoserde", derive(SerBin, DeBin))] 151 | #[difference(setters)] 152 | struct TestGenerics { 153 | test1: A, 154 | test2: B, 155 | test3: C, 156 | test4: HashMap, 157 | } 158 | 159 | #[test] 160 | fn test_generics() { 161 | type TestType = TestGenerics, String>; 162 | let first: TestType = TestGenerics { 163 | test1: 0, 164 | test2: 42, 165 | test3: Some(true), 166 | test4: [(String::from("test123"), 1)].into_iter().collect(), 167 | }; 168 | 169 | let second: TestType = TestGenerics { 170 | test1: 0, 171 | test2: 42, 172 | test3: None, 173 | test4: [(String::from("test1234"), 2)].into_iter().collect(), 174 | }; 175 | 176 | let diffs = first.diff(&second); 177 | 178 | #[cfg(feature = "serde")] 179 | { 180 | let ser_diff = bincode::serialize(&diffs).unwrap(); 181 | let deser_diff = bincode::deserialize(&ser_diff).unwrap(); 182 | let diffed_serde = first.clone().apply(deser_diff); 183 | 184 | assert_eq!(diffed_serde, second); 185 | } 186 | 187 | #[cfg(feature = "nanoserde")] 188 | { 189 | let ser = SerBin::serialize_bin(&diffs); 190 | let diffed_serde = first.clone().apply(DeBin::deserialize_bin(&ser).unwrap()); 191 | 192 | assert_eq!(&diffed_serde, &second); 193 | } 194 | 195 | let diffed = first.clone().apply(diffs); 196 | 197 | //check that all except the skipped are changed 198 | assert_eq!(diffed.test1, second.test1); 199 | assert_eq!(diffed.test2, second.test2); 200 | assert_eq!(diffed.test3, second.test3); 201 | assert_eq!(diffed.test4, second.test4); 202 | 203 | nanoserde_ref_test!(first, second) 204 | } 205 | 206 | #[derive(Debug, PartialEq, Clone, Difference)] 207 | #[difference(setters)] 208 | struct TestGenericsSkip { 209 | test1: A, 210 | test2: B, 211 | test3: C, 212 | #[difference(skip)] 213 | test4: HashMap, 214 | test5: HashMap, 215 | } 216 | 217 | #[test] 218 | fn test_generics_skip() { 219 | let first: TestGenericsSkip, String> = TestGenericsSkip { 220 | test1: 0, 221 | test2: 42, 222 | test3: Some(true), 223 | test4: [(String::from("test123"), 1)].into_iter().collect(), 224 | test5: [(String::from("test123"), 1)].into_iter().collect(), 225 | }; 226 | 227 | let second: TestGenericsSkip, String> = TestGenericsSkip { 228 | test1: 0, 229 | test2: 42, 230 | test3: None, 231 | test4: [(String::from("test1234"), 2)].into_iter().collect(), 232 | test5: [(String::from("test1234"), 2)].into_iter().collect(), 233 | }; 234 | 235 | let diffs = first.diff(&second); 236 | 237 | #[cfg(feature = "serde")] 238 | { 239 | let ser_diff = bincode::serialize(&diffs).unwrap(); 240 | let deser_diff = bincode::deserialize(&ser_diff).unwrap(); 241 | let diffed_serde = first.clone().apply(deser_diff); 242 | 243 | assert_eq!(diffed_serde.test1, second.test1); 244 | assert_eq!(diffed_serde.test2, second.test2); 245 | assert_eq!(diffed_serde.test3, second.test3); 246 | assert_ne!(diffed_serde.test4, second.test4); 247 | assert_eq!(diffed_serde.test5, second.test5); 248 | } 249 | 250 | #[cfg(feature = "nanoserde")] 251 | { 252 | let ser = SerBin::serialize_bin(&diffs); 253 | let diffed_serde = first.clone().apply(DeBin::deserialize_bin(&ser).unwrap()); 254 | 255 | assert_eq!(diffed_serde.test1, second.test1); 256 | assert_eq!(diffed_serde.test2, second.test2); 257 | assert_eq!(diffed_serde.test3, second.test3); 258 | assert_ne!(diffed_serde.test4, second.test4); 259 | assert_eq!(diffed_serde.test5, second.test5); 260 | } 261 | 262 | let diffed = first.clone().apply(diffs); 263 | 264 | //check that all except the skipped are changed 265 | assert_eq!(diffed.test1, second.test1); 266 | assert_eq!(diffed.test2, second.test2); 267 | assert_eq!(diffed.test3, second.test3); 268 | assert_ne!(diffed.test4, second.test4); 269 | assert_eq!(diffed.test5, second.test5); 270 | 271 | nanoserde_ref_test!(first, second); 272 | } 273 | 274 | #[test] 275 | fn test_enums() { 276 | let mut follower = TestEnum::next(); 277 | let mut leader: TestEnum; 278 | for _ in 0..100 { 279 | leader = TestEnum::next(); 280 | let diff = follower.diff(&leader); 281 | follower.apply_mut(diff); 282 | assert_eq!(&leader, &follower); 283 | nanoserde_ref_test!(leader, follower); 284 | } 285 | } 286 | 287 | mod derive_inner { 288 | use std::f32::consts::PI; 289 | 290 | use super::{StructDiff, Test}; 291 | //tests that the associated type does not need to be exported manually 292 | 293 | #[test] 294 | fn test_derive_inner() { 295 | let first: Test = Test { 296 | test1: 0, 297 | test2: String::new(), 298 | test3: Vec::new(), 299 | test4: 0.0, 300 | test5: None, 301 | }; 302 | 303 | let second = Test { 304 | test1: first.test1, 305 | test2: String::from("Hello Diff"), 306 | test3: vec![1], 307 | test4: PI, 308 | test5: Some(13), 309 | }; 310 | 311 | let diffs = first.diff(&second); 312 | let diffed = first.clone().apply(diffs); 313 | 314 | assert_eq!(diffed, second); 315 | nanoserde_ref_test!(first, second); 316 | } 317 | } 318 | 319 | #[test] 320 | fn test_recurse() { 321 | #[derive(Debug, PartialEq, Clone, Difference)] 322 | #[difference(setters)] 323 | struct TestRecurse { 324 | test1: i32, 325 | #[difference(recurse)] 326 | test2: Test, 327 | #[difference(recurse)] 328 | test3: Option, 329 | #[difference(recurse)] 330 | test4: Option, 331 | #[difference(recurse)] 332 | test5: Option, 333 | } 334 | 335 | let first = TestRecurse { 336 | test1: 0, 337 | test2: Test { 338 | test1: 0, 339 | test2: String::new(), 340 | test3: Vec::new(), 341 | test4: 0.0, 342 | test5: Some(14), 343 | }, 344 | test3: None, 345 | test4: Some(Test::default()), 346 | test5: Some(Test { 347 | test1: 0, 348 | test2: String::new(), 349 | test3: Vec::new(), 350 | test4: 0.0, 351 | test5: Some(14), 352 | }), 353 | }; 354 | 355 | let second = TestRecurse { 356 | test1: 1, 357 | test2: Test { 358 | test1: 2, 359 | test2: String::new(), 360 | test3: Vec::new(), 361 | test4: PI, 362 | test5: None, 363 | }, 364 | test3: Some(Test::default()), 365 | test4: Some(Test { 366 | test1: 0, 367 | test2: String::new(), 368 | test3: Vec::new(), 369 | test4: 0.0, 370 | test5: Some(14), 371 | }), 372 | test5: None, 373 | }; 374 | 375 | let diffs = first.diff(&second); 376 | assert_eq!(diffs.len(), 5); 377 | 378 | type TestRecurseFields = ::Diff; 379 | 380 | if let TestRecurseFields::test2(val) = &diffs[1] { 381 | assert_eq!(val.len(), 3); 382 | } else { 383 | panic!("Recursion failure"); 384 | } 385 | 386 | #[cfg(feature = "serde")] 387 | { 388 | let ser_diff = bincode::serialize(&diffs).unwrap(); 389 | let deser_diff = bincode::deserialize(&ser_diff).unwrap(); 390 | let diffed_serde = first.clone().apply(deser_diff); 391 | 392 | assert_eq!(diffed_serde, second); 393 | } 394 | 395 | #[cfg(feature = "nanoserde")] 396 | { 397 | let ser = SerBin::serialize_bin(&diffs); 398 | let diffed_serde = first.clone().apply(DeBin::deserialize_bin(&ser).unwrap()); 399 | 400 | assert_eq!(&diffed_serde, &second); 401 | } 402 | 403 | let diffed = first.clone().apply(diffs); 404 | 405 | assert_eq!(diffed, second); 406 | nanoserde_ref_test!(first, second); 407 | } 408 | 409 | #[test] 410 | fn test_collection_strategies() { 411 | #[derive(Debug, PartialEq, Clone, Difference, Default)] 412 | struct TestCollection { 413 | #[difference(collection_strategy = "unordered_array_like")] 414 | test1: Vec, 415 | #[difference(collection_strategy = "unordered_array_like")] 416 | test2: HashSet, 417 | #[difference(collection_strategy = "unordered_array_like")] 418 | test3: LinkedList, 419 | } 420 | 421 | let first = TestCollection { 422 | test1: vec![10, 15, 20, 25, 30], 423 | test3: vec![10, 15, 17].into_iter().collect(), 424 | ..Default::default() 425 | }; 426 | 427 | let second = TestCollection { 428 | test1: Vec::default(), 429 | test2: vec![10].into_iter().collect(), 430 | test3: vec![10, 15, 17, 19].into_iter().collect(), 431 | }; 432 | 433 | let diffs = first.diff(&second); 434 | 435 | #[cfg(feature = "serde")] 436 | { 437 | let ser_diff = bincode::serialize(&diffs).unwrap(); 438 | let deser_diff = bincode::deserialize(&ser_diff).unwrap(); 439 | let diffed_serde = first.clone().apply(deser_diff); 440 | 441 | use assert_unordered::assert_eq_unordered; 442 | assert_eq_unordered!(&diffed_serde.test1, &second.test1); 443 | assert_eq_unordered!(&diffed_serde.test2, &second.test2); 444 | assert_eq_unordered!(&diffed_serde.test3, &second.test3); 445 | } 446 | 447 | #[cfg(feature = "nanoserde")] 448 | { 449 | let ser = SerBin::serialize_bin(&diffs); 450 | let diffed_nserde = first.clone().apply(DeBin::deserialize_bin(&ser).unwrap()); 451 | 452 | use assert_unordered::assert_eq_unordered; 453 | assert_eq_unordered!(&diffed_nserde.test1, &second.test1); 454 | assert_eq_unordered!(&diffed_nserde.test2, &second.test2); 455 | assert_eq_unordered!(&diffed_nserde.test3, &second.test3); 456 | } 457 | 458 | let diffed = first.clone().apply(diffs); 459 | 460 | use assert_unordered::assert_eq_unordered; 461 | assert_eq_unordered!(&diffed.test1, &second.test1); 462 | assert_eq_unordered!(&diffed.test2, &second.test2); 463 | assert_eq_unordered!(&diffed.test3, &second.test3); 464 | 465 | nanoserde_ref_test!(first, second); 466 | } 467 | 468 | #[test] 469 | fn test_key_value() { 470 | #[derive(Debug, PartialEq, Clone, Difference, Default)] 471 | struct TestCollection { 472 | #[difference( 473 | collection_strategy = "unordered_map_like", 474 | map_equality = "key_and_value" 475 | )] 476 | test1: HashMap, 477 | } 478 | 479 | let first = TestCollection { 480 | test1: vec![(10, 0), (15, 2), (20, 0), (25, 0), (30, 15)] 481 | .into_iter() 482 | .collect(), 483 | }; 484 | 485 | let second = TestCollection { 486 | test1: vec![(10, 21), (15, 2), (20, 0), (25, 0), (30, 15)] 487 | .into_iter() 488 | .collect(), 489 | }; 490 | 491 | let diffs = first.diff(&second); 492 | 493 | #[cfg(feature = "serde")] 494 | { 495 | let ser_diff = bincode::serialize(&diffs).unwrap(); 496 | let deser_diff = bincode::deserialize(&ser_diff).unwrap(); 497 | let diffed_serde = first.clone().apply(deser_diff); 498 | 499 | use assert_unordered::assert_eq_unordered; 500 | assert_eq_unordered!(&diffed_serde.test1, &second.test1); 501 | } 502 | 503 | #[cfg(feature = "nanoserde")] 504 | { 505 | let ser = SerBin::serialize_bin(&diffs); 506 | let diffed_serde = first.clone().apply(DeBin::deserialize_bin(&ser).unwrap()); 507 | 508 | use assert_unordered::assert_eq_unordered; 509 | assert_eq_unordered!(&diffed_serde.test1, &second.test1); 510 | } 511 | 512 | let diffed = first.clone().apply(diffs); 513 | 514 | use assert_unordered::assert_eq_unordered; 515 | assert_eq_unordered!(&diffed.test1, &second.test1); 516 | 517 | nanoserde_ref_test!(first, second); 518 | } 519 | 520 | #[cfg(feature = "generated_setters")] 521 | #[test] 522 | fn test_setters() { 523 | use types::TestSetters; 524 | let mut base = TestSetters::default(); 525 | let mut end = TestSetters::default(); 526 | let mut partial_diffs = vec![]; 527 | let mut full_diffs = vec![]; 528 | 529 | for _ in 0..100 { 530 | end = TestSetters::next(); 531 | let base_clone = base.clone(); 532 | partial_diffs.extend(base.testing123(end.f0.clone())); 533 | partial_diffs.extend(base.set_f1_with_diff(end.f1.clone())); 534 | partial_diffs.extend(base.set_f2_with_diff(end.f2.clone())); 535 | partial_diffs.extend(base.set_f3_with_diff(end.f3.clone())); 536 | partial_diffs.extend(base.set_f4_with_diff(end.f4.clone())); 537 | partial_diffs.extend(base.set_f5_with_diff(end.f5.clone())); 538 | partial_diffs.extend(base.set_f6_with_diff(end.f6.clone())); 539 | let tmp = base_clone.apply_ref(partial_diffs.clone()); 540 | assert_eq!(&tmp.f0, &end.f0); 541 | assert_eq!(&tmp.f1, &end.f1); 542 | assert_eq!(&tmp.f2, &end.f2); 543 | assert_eq!(&tmp.f3, &end.f3); 544 | assert_eq_unordered!(&tmp.f4, &end.f4); 545 | assert_eq_unordered!(&tmp.f5, &end.f5); 546 | assert_eq_unordered!(&tmp.f6, &end.f6); 547 | 548 | assert_eq!(&base.f0, &end.f0); 549 | assert_eq!(&base.f1, &end.f1); 550 | assert_eq!(&base.f2, &end.f2); 551 | assert_eq!(&base.f3, &end.f3); 552 | assert_eq_unordered!(&base.f4, &end.f4); 553 | assert_eq_unordered!(&base.f5, &end.f5); 554 | assert_eq_unordered!(&base.f6, &end.f6); 555 | full_diffs.extend(std::mem::take(&mut partial_diffs)); 556 | } 557 | 558 | let modified = TestSetters::default().apply(full_diffs); 559 | assert_eq!(modified.f0, end.f0); 560 | assert_eq!(modified.f1, end.f1); 561 | assert_eq!(modified.f2, end.f2); 562 | assert_eq!(modified.f3, end.f3); 563 | assert_eq_unordered_sort!(modified.f4, end.f4); 564 | assert_eq_unordered!(modified.f5, end.f5); 565 | assert_eq_unordered!(modified.f6, end.f6); 566 | } 567 | -------------------------------------------------------------------------------- /tests/types.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use generators::{fill, rand_bool, rand_string}; 4 | use nanorand::{Rng, WyRand}; 5 | #[cfg(feature = "nanoserde")] 6 | use nanoserde::{DeBin, SerBin}; 7 | #[cfg(feature = "serde")] 8 | use serde::{Deserialize, Serialize}; 9 | use structdiff::{Difference, StructDiff}; 10 | 11 | pub trait RandValue 12 | where 13 | Self: Sized, 14 | { 15 | fn next() -> Self { 16 | let mut rng = WyRand::new(); 17 | Self::next_seeded(&mut rng) 18 | } 19 | 20 | fn next_seeded(rng: &mut WyRand) -> Self; 21 | } 22 | 23 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 24 | #[cfg_attr(feature = "nanoserde", derive(SerBin, DeBin))] 25 | #[derive(Debug, PartialEq, Clone, Difference, Default)] 26 | #[difference(setters)] 27 | pub struct Test { 28 | pub test1: i32, 29 | pub test2: String, 30 | pub test3: Vec, 31 | pub test4: f32, 32 | pub test5: Option, 33 | } 34 | 35 | #[derive(Debug, PartialEq, Clone, Difference)] 36 | #[cfg_attr(feature = "nanoserde", derive(SerBin, DeBin))] 37 | #[difference(setters)] 38 | pub struct TestSkip 39 | where 40 | A: PartialEq, 41 | { 42 | pub test1: A, 43 | pub test2: String, 44 | #[difference(skip)] 45 | pub test3skip: Vec, 46 | pub test4: f32, 47 | } 48 | 49 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 50 | #[cfg_attr(feature = "nanoserde", derive(SerBin, DeBin))] 51 | #[derive(Debug, PartialEq, Clone, Difference, Default)] 52 | pub enum TestEnum { 53 | #[default] 54 | F0, 55 | F1(bool), 56 | F2(String), 57 | F3 { 58 | field1: String, 59 | field2: bool, 60 | }, 61 | F4(Test), 62 | } 63 | 64 | impl RandValue for Test { 65 | fn next_seeded(rng: &mut WyRand) -> Self { 66 | Test { 67 | test1: rng.generate(), 68 | test2: rand_string(rng), 69 | test3: fill(rng), 70 | test4: match f32::from_bits(rng.generate::()) { 71 | val if val.is_nan() => 0.0, 72 | val => val, 73 | }, 74 | test5: match rng.generate::() { 75 | true => Some(rng.generate()), 76 | false => None, 77 | }, 78 | } 79 | } 80 | } 81 | 82 | impl RandValue for TestEnum { 83 | fn next_seeded(rng: &mut WyRand) -> Self { 84 | match rng.generate_range(0..5) { 85 | 0 => Self::F0, 86 | 1 => Self::F1(rand_bool(rng)), 87 | 2 => Self::F2(rand_string(rng)), 88 | 3 => Self::F3 { 89 | field1: rand_string(rng), 90 | field2: rand_bool(rng), 91 | }, 92 | _ => Self::F4(Test::next()), 93 | } 94 | } 95 | } 96 | 97 | #[derive(Difference, Default, PartialEq, Debug, Clone)] 98 | #[difference(setters)] 99 | pub struct TestSetters { 100 | #[difference(setter_name = "testing123", recurse)] 101 | pub f0: Test, 102 | pub f1: Test, 103 | pub f2: TestEnum, 104 | #[difference(recurse)] 105 | pub f3: Option, 106 | #[difference(collection_strategy = "unordered_array_like")] 107 | pub f4: Vec, 108 | #[difference(collection_strategy = "unordered_map_like", map_equality = "key_only")] 109 | pub f5: BTreeMap, 110 | #[difference( 111 | collection_strategy = "unordered_map_like", 112 | map_equality = "key_and_value" 113 | )] 114 | pub f6: BTreeMap, 115 | } 116 | 117 | impl RandValue for TestSetters { 118 | fn next_seeded(rng: &mut WyRand) -> Self { 119 | TestSetters { 120 | f0: Test::next(), 121 | f1: Test::next(), 122 | f2: TestEnum::next(), 123 | f3: if rng.generate::() { 124 | Some(Test::next_seeded(rng)) 125 | } else { 126 | None 127 | }, 128 | f4: generators::fill(rng), 129 | f5: generators::fill::>(rng) 130 | .into_iter() 131 | .map(|x| (x, Test::next_seeded(rng))) 132 | .take(10) 133 | .collect(), 134 | f6: generators::fill::>(rng) 135 | .into_iter() 136 | .map(|x| (x, Test::next_seeded(rng))) 137 | .take(10) 138 | .collect(), 139 | } 140 | } 141 | } 142 | 143 | mod generators { 144 | use nanorand::{Rng, WyRand}; 145 | 146 | pub(super) fn rand_bool(rng: &mut WyRand) -> bool { 147 | let base = rng.generate::() as usize; 148 | base % 2 == 0 149 | } 150 | 151 | pub(super) fn rand_string(rng: &mut WyRand) -> String { 152 | let base = vec![(); rng.generate::() as usize]; 153 | base.into_iter() 154 | .map(|_| rng.generate::() as u32) 155 | .filter_map(char::from_u32) 156 | .collect::() 157 | } 158 | 159 | pub(super) fn fill(rng: &mut WyRand) -> T 160 | where 161 | V: nanorand::RandomGen, 162 | T: FromIterator, 163 | { 164 | let base = vec![(); rng.generate::() as usize]; 165 | base.into_iter().map(|_| rng.generate::()).collect::() 166 | } 167 | } 168 | --------------------------------------------------------------------------------