struct_patch/
lib.rs

1//! This crate provides the [`Patch`] and [`Filler`] traits and accompanying derive macro.
2//!
3//! Deriving [`Patch`] on a struct will generate a struct similar to the original one, but with all fields wrapped in an `Option`.
4//! An instance of such a patch struct can be applied onto the original struct, replacing values only if they are set to `Some`, leaving them unchanged otherwise.
5//!
6//! The following code shows how `struct-patch` can be used together with `serde` to patch structs with JSON objects.
7//! ```rust
8//! use struct_patch::Patch;
9//! use serde::{Deserialize, Serialize};
10//!
11//! #[derive(Default, Debug, PartialEq, Patch)]
12//! #[patch(attribute(derive(Debug, Default, Deserialize, Serialize)))]
13//! struct Item {
14//!     field_bool: bool,
15//!     field_int: usize,
16//!     field_string: String,
17//! }
18//!
19//! fn patch_json() {
20//!     let mut item = Item {
21//!         field_bool: true,
22//!         field_int: 42,
23//!         field_string: String::from("hello"),
24//!     };
25//!
26//!     let data = r#"{
27//!         "field_int": 7
28//!     }"#;
29//!
30//!     let patch: ItemPatch = serde_json::from_str(data).unwrap();
31//!
32//!     item.apply(patch);
33//!
34//!     assert_eq!(
35//!         item,
36//!         Item {
37//!             field_bool: true,
38//!             field_int: 7,
39//!             field_string: String::from("hello")
40//!         }
41//!     );
42//! }
43//! ```
44//!
45//! More details on how to use the the derive macro, including what attributes are available, are
46//! available under [`Patch`]
47//!
48//! Deriving [`Filler`] on a struct will generate a struct similar to the original one with the
49//! field with `Option`, `BTreeMap`, `BTreeSet`, `BinaryHeap`,`HashMap`, `HashSet`, `LinkedList`,
50//! `VecDeque `or `Vec`.
51//! Any struct implement `Default`, `Extend`, `IntoIterator`, `is_empty` can be used with
52//! `#[filler(extenable)]`.
53//! Unlike [`Patch`], the [`Filler`] only work on the empty fields of instance.
54//!
55//! ```rust
56//! use struct_patch::Filler;
57//!
58//! #[derive(Filler)]
59//! struct Item {
60//!     field_int: usize,
61//!     maybe_field_int: Option<usize>,
62//! }
63//! let mut item = Item {
64//!     field_int: 0,
65//!     maybe_field_int: None,
66//! };
67//!
68//! let filler_1 = ItemFiller{ maybe_field_int: Some(7), };
69//! item.apply(filler_1);
70//! assert_eq!(item.maybe_field_int, Some(7));
71//!
72//! let filler_2 = ItemFiller{ maybe_field_int: Some(100), };
73//! item.apply(filler_2);
74//! assert_eq!(item.maybe_field_int, Some(7));
75//! ```
76#![cfg_attr(not(any(test, feature = "box", feature = "option")), no_std)]
77
78#[doc(hidden)]
79pub use struct_patch_derive::Filler;
80#[doc(hidden)]
81pub use struct_patch_derive::Patch;
82#[cfg(any(feature = "box", feature = "option"))]
83pub mod std;
84pub mod traits;
85pub use traits::*;
86
87#[cfg(test)]
88mod tests {
89    use serde::Deserialize;
90    #[cfg(feature = "merge")]
91    use struct_patch::Merge;
92    use struct_patch::Patch;
93    #[cfg(feature = "status")]
94    use struct_patch::Status;
95
96    use crate as struct_patch;
97
98    #[test]
99    fn test_basic() {
100        #[derive(Patch, Debug, PartialEq)]
101        struct Item {
102            field: u32,
103            other: String,
104        }
105
106        let mut item = Item {
107            field: 1,
108            other: String::from("hello"),
109        };
110        let patch = ItemPatch {
111            field: None,
112            other: Some(String::from("bye")),
113        };
114
115        item.apply(patch);
116        assert_eq!(
117            item,
118            Item {
119                field: 1,
120                other: String::from("bye")
121            }
122        );
123    }
124
125    #[test]
126    #[cfg(feature = "status")]
127    fn test_empty() {
128        #[derive(Patch)]
129        #[patch(attribute(derive(Debug, PartialEq)))]
130        struct Item {
131            data: u32,
132        }
133
134        let patch = ItemPatch { data: None };
135        let other_patch = Item::new_empty_patch();
136        assert!(patch.is_empty());
137        assert_eq!(patch, other_patch);
138        let patch = ItemPatch { data: Some(0) };
139        assert!(!patch.is_empty());
140    }
141
142    #[test]
143    fn test_derive() {
144        #[allow(dead_code)]
145        #[derive(Patch)]
146        #[patch(attribute(derive(Copy, Clone, PartialEq, Debug)))]
147        struct Item;
148
149        let patch = ItemPatch {};
150        let other_patch = patch;
151        assert_eq!(patch, other_patch);
152    }
153
154    #[test]
155    fn test_name() {
156        #[derive(Patch)]
157        #[patch(name = "PatchItem")]
158        struct Item;
159
160        let patch = PatchItem {};
161        let mut item = Item;
162        item.apply(patch);
163    }
164
165    #[test]
166    fn test_nullable() {
167        #[derive(Patch, Debug, PartialEq)]
168        struct Item {
169            field: Option<u32>,
170            other: Option<String>,
171        }
172
173        let mut item = Item {
174            field: Some(1),
175            other: Some(String::from("hello")),
176        };
177        let patch = ItemPatch {
178            field: None,
179            other: Some(None),
180        };
181
182        item.apply(patch);
183        assert_eq!(
184            item,
185            Item {
186                field: Some(1),
187                other: None
188            }
189        );
190    }
191
192    #[test]
193    fn test_skip() {
194        #[derive(Patch, PartialEq, Debug)]
195        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
196        struct Item {
197            #[patch(skip)]
198            id: u32,
199            data: u32,
200        }
201
202        let mut item = Item { id: 1, data: 2 };
203        let data = r#"{ "id": 10, "data": 15 }"#; // Note: serde ignores unknown fields by default.
204        let patch: ItemPatch = serde_json::from_str(data).unwrap();
205        assert_eq!(patch, ItemPatch { data: Some(15) });
206
207        item.apply(patch);
208        assert_eq!(item, Item { id: 1, data: 15 });
209    }
210
211    #[test]
212    fn test_nested() {
213        #[derive(PartialEq, Debug, Patch, Deserialize)]
214        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
215        struct B {
216            c: u32,
217            d: u32,
218        }
219
220        #[derive(PartialEq, Debug, Patch, Deserialize)]
221        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
222        struct A {
223            #[patch(name = "BPatch")]
224            b: B,
225        }
226
227        let mut a = A {
228            b: B { c: 0, d: 0 },
229        };
230        let data = r#"{ "b": { "c": 1 } }"#;
231        let patch: APatch = serde_json::from_str(data).unwrap();
232        // assert_eq!(
233        //     patch,
234        //     APatch {
235        //         b: Some(B { id: 1 })
236        //     }
237        // );
238        a.apply(patch);
239        assert_eq!(
240            a,
241            A {
242                b: B { c: 1, d: 0 }
243            }
244        );
245    }
246
247    #[test]
248    fn test_generic() {
249        #[derive(Patch)]
250        struct Item<T>
251        where
252            T: PartialEq,
253        {
254            pub field: T,
255        }
256
257        let patch = ItemPatch {
258            field: Some(String::from("hello")),
259        };
260        let mut item = Item {
261            field: String::new(),
262        };
263        item.apply(patch);
264        assert_eq!(item.field, "hello");
265    }
266
267    #[test]
268    fn test_named_generic() {
269        #[derive(Patch)]
270        #[patch(name = "PatchItem")]
271        struct Item<T>
272        where
273            T: PartialEq,
274        {
275            pub field: T,
276        }
277
278        let patch = PatchItem {
279            field: Some(String::from("hello")),
280        };
281        let mut item = Item {
282            field: String::new(),
283        };
284        item.apply(patch);
285    }
286
287    #[test]
288    fn test_nested_generic() {
289        #[derive(PartialEq, Debug, Patch, Deserialize)]
290        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
291        struct B<T>
292        where
293            T: PartialEq,
294        {
295            c: T,
296            d: T,
297        }
298
299        #[derive(PartialEq, Debug, Patch, Deserialize)]
300        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
301        struct A {
302            #[patch(name = "BPatch<u32>")]
303            b: B<u32>,
304        }
305
306        let mut a = A {
307            b: B { c: 0, d: 0 },
308        };
309        let data = r#"{ "b": { "c": 1 } }"#;
310        let patch: APatch = serde_json::from_str(data).unwrap();
311
312        a.apply(patch);
313        assert_eq!(
314            a,
315            A {
316                b: B { c: 1, d: 0 }
317            }
318        );
319    }
320
321    #[cfg(feature = "op")]
322    #[test]
323    fn test_shl() {
324        #[derive(Patch, Debug, PartialEq)]
325        struct Item {
326            field: u32,
327            other: String,
328        }
329
330        let item = Item {
331            field: 1,
332            other: String::from("hello"),
333        };
334        let patch = ItemPatch {
335            field: None,
336            other: Some(String::from("bye")),
337        };
338
339        assert_eq!(
340            item << patch,
341            Item {
342                field: 1,
343                other: String::from("bye")
344            }
345        );
346    }
347
348    #[cfg(all(feature = "op", feature = "merge"))]
349    #[test]
350    fn test_shl_on_patch() {
351        #[derive(Patch, Debug, PartialEq)]
352        struct Item {
353            field: u32,
354            other: String,
355        }
356
357        let mut item = Item {
358            field: 1,
359            other: String::from("hello"),
360        };
361        let patch = ItemPatch {
362            field: None,
363            other: Some(String::from("bye")),
364        };
365        let patch2 = ItemPatch {
366            field: Some(2),
367            other: None,
368        };
369
370        let new_patch = patch << patch2;
371
372        item.apply(new_patch);
373        assert_eq!(
374            item,
375            Item {
376                field: 2,
377                other: String::from("bye")
378            }
379        );
380    }
381
382    #[cfg(feature = "op")]
383    #[test]
384    fn test_add_patches() {
385        #[derive(Patch)]
386        #[patch(attribute(derive(Debug, PartialEq)))]
387        struct Item {
388            field: u32,
389            other: String,
390        }
391
392        let patch = ItemPatch {
393            field: Some(1),
394            other: None,
395        };
396        let patch2 = ItemPatch {
397            field: None,
398            other: Some(String::from("hello")),
399        };
400        let overall_patch = patch + patch2;
401        assert_eq!(
402            overall_patch,
403            ItemPatch {
404                field: Some(1),
405                other: Some(String::from("hello")),
406            }
407        );
408    }
409
410    #[cfg(feature = "op")]
411    #[test]
412    #[should_panic]
413    fn test_add_conflict_patches_panic() {
414        #[derive(Patch, Debug, PartialEq)]
415        struct Item {
416            field: u32,
417        }
418
419        let patch = ItemPatch { field: Some(1) };
420        let patch2 = ItemPatch { field: Some(2) };
421        let _overall_patch = patch + patch2;
422    }
423
424    #[cfg(feature = "merge")]
425    #[test]
426    fn test_merge() {
427        #[allow(dead_code)]
428        #[derive(Patch)]
429        #[patch(attribute(derive(PartialEq, Debug)))]
430        struct Item {
431            a: u32,
432            b: u32,
433            c: u32,
434            d: u32,
435        }
436
437        let patch = ItemPatch {
438            a: None,
439            b: Some(2),
440            c: Some(0),
441            d: None,
442        };
443        let patch2 = ItemPatch {
444            a: Some(1),
445            b: None,
446            c: Some(3),
447            d: None,
448        };
449
450        let merged_patch = patch.merge(patch2);
451        assert_eq!(
452            merged_patch,
453            ItemPatch {
454                a: Some(1),
455                b: Some(2),
456                c: Some(3),
457                d: None,
458            }
459        );
460    }
461
462    #[cfg(feature = "merge")]
463    #[test]
464    fn test_merge_nested() {
465        #[allow(dead_code)]
466        #[derive(Patch, PartialEq, Debug)]
467        #[patch(attribute(derive(PartialEq, Debug, Clone)))]
468        struct B {
469            c: u32,
470            d: u32,
471            e: u32,
472            f: u32,
473        }
474
475        #[allow(dead_code)]
476        #[derive(Patch)]
477        #[patch(attribute(derive(PartialEq, Debug)))]
478        struct A {
479            a: u32,
480            #[patch(name = "BPatch")]
481            b: B,
482        }
483
484        let patches = vec![
485            APatch {
486                a: Some(1),
487                b: Some(BPatch {
488                    c: None,
489                    d: Some(2),
490                    e: Some(0),
491                    f: None,
492                }),
493            },
494            APatch {
495                a: Some(0),
496                b: Some(BPatch {
497                    c: Some(1),
498                    d: None,
499                    e: Some(3),
500                    f: None,
501                }),
502            },
503        ];
504
505        let merged_patch = patches.into_iter().reduce(Merge::merge).unwrap();
506
507        assert_eq!(
508            merged_patch,
509            APatch {
510                a: Some(0),
511                b: Some(BPatch {
512                    c: Some(1),
513                    d: Some(2),
514                    e: Some(3),
515                    f: None,
516                }),
517            }
518        );
519    }
520}