Skip to main content

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#![no_std]
77
78#[cfg(use_alloc)]
79extern crate alloc;
80
81#[cfg(use_alloc)]
82#[doc(hidden)]
83pub use alloc::vec as __vec;
84
85#[cfg(feature = "catalyst")]
86#[doc(hidden)]
87pub use struct_patch_derive::Catalyst;
88#[doc(hidden)]
89pub use struct_patch_derive::Filler;
90#[doc(hidden)]
91pub use struct_patch_derive::Patch;
92#[cfg(feature = "substrate")]
93#[doc(hidden)]
94pub use struct_patch_derive::Substrate;
95pub mod r#box;
96#[cfg(feature = "box")]
97#[doc(hidden)]
98pub use alloc::boxed::Box as __Box;
99pub mod option;
100pub mod traits;
101pub use traits::*;
102
103#[cfg(test)]
104mod tests {
105    extern crate alloc;
106    use alloc::string::String;
107    use serde::Deserialize;
108    #[cfg(feature = "merge")]
109    use struct_patch::Merge;
110    use struct_patch::Patch;
111    #[cfg(feature = "status")]
112    use struct_patch::Status;
113
114    use crate as struct_patch;
115
116    #[test]
117    fn test_basic() {
118        #[derive(Patch, Debug, PartialEq)]
119        struct Item {
120            field: u32,
121            other: String,
122        }
123
124        let mut item = Item {
125            field: 1,
126            other: String::from("hello"),
127        };
128        let patch = ItemPatch {
129            field: None,
130            other: Some(String::from("bye")),
131        };
132
133        item.apply(patch);
134        assert_eq!(
135            item,
136            Item {
137                field: 1,
138                other: String::from("bye")
139            }
140        );
141    }
142
143    #[test]
144    #[cfg(feature = "status")]
145    fn test_empty() {
146        #[derive(Patch)]
147        #[patch(attribute(derive(Debug, PartialEq)))]
148        struct Item {
149            data: u32,
150        }
151
152        let patch = ItemPatch { data: None };
153        let other_patch = Item::new_empty_patch();
154        assert!(patch.is_empty());
155        assert_eq!(patch, other_patch);
156        let patch = ItemPatch { data: Some(0) };
157        assert!(!patch.is_empty());
158    }
159
160    #[test]
161    fn test_derive() {
162        #[allow(dead_code)]
163        #[derive(Patch)]
164        #[patch(attribute(derive(Copy, Clone, PartialEq, Debug)))]
165        struct Item;
166
167        let patch = ItemPatch {};
168        let other_patch = patch;
169        assert_eq!(patch, other_patch);
170    }
171
172    #[test]
173    fn test_name() {
174        #[derive(Patch)]
175        #[patch(name = "PatchItem")]
176        struct Item;
177
178        let patch = PatchItem {};
179        let mut item = Item;
180        item.apply(patch);
181    }
182
183    #[test]
184    fn test_nullable() {
185        #[derive(Patch, Debug, PartialEq)]
186        struct Item {
187            field: Option<u32>,
188            other: Option<String>,
189        }
190
191        let mut item = Item {
192            field: Some(1),
193            other: Some(String::from("hello")),
194        };
195        let patch = ItemPatch {
196            field: None,
197            other: Some(None),
198        };
199
200        item.apply(patch);
201        assert_eq!(
202            item,
203            Item {
204                field: Some(1),
205                other: None
206            }
207        );
208    }
209
210    #[test]
211    fn test_skip() {
212        #[derive(Patch, PartialEq, Debug)]
213        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
214        struct Item {
215            #[patch(skip)]
216            id: u32,
217            data: u32,
218        }
219
220        let mut item = Item { id: 1, data: 2 };
221        let data = r#"{ "id": 10, "data": 15 }"#; // Note: serde ignores unknown fields by default.
222        let patch: ItemPatch = serde_json::from_str(data).unwrap();
223        assert_eq!(patch, ItemPatch { data: Some(15) });
224
225        item.apply(patch);
226        assert_eq!(item, Item { id: 1, data: 15 });
227    }
228
229    #[test]
230    fn test_nested() {
231        #[derive(PartialEq, Debug, Default, Patch, Deserialize)]
232        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
233        struct B {
234            c: u32,
235            d: u32,
236        }
237
238        #[derive(PartialEq, Debug, Patch, Deserialize)]
239        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
240        struct A {
241            #[patch(name = "BPatch")]
242            b: B,
243        }
244        let mut b = B::default();
245        let b_patch: BPatch = serde_json::from_str(r#"{ "d": 1 }"#).unwrap();
246        b.apply(b_patch);
247        assert_eq!(b, B { c: 0, d: 1 });
248
249        let mut a = A { b };
250        let data = r#"{ "b": { "c": 1 } }"#;
251        let patch: APatch = serde_json::from_str(data).unwrap();
252        // assert_eq!(
253        //     patch,
254        //     APatch {
255        //         b: Some(B { id: 1 })
256        //     }
257        // );
258        a.apply(patch);
259        assert_eq!(
260            a,
261            A {
262                b: B { c: 1, d: 1 }
263            }
264        );
265    }
266
267    #[test]
268    fn test_generic() {
269        #[derive(Patch)]
270        struct Item<T>
271        where
272            T: PartialEq,
273        {
274            pub field: T,
275        }
276
277        let patch = ItemPatch {
278            field: Some(String::from("hello")),
279        };
280        let mut item = Item {
281            field: String::new(),
282        };
283        item.apply(patch);
284        assert_eq!(item.field, "hello");
285    }
286
287    #[test]
288    fn test_named_generic() {
289        #[derive(Patch)]
290        #[patch(name = "PatchItem")]
291        struct Item<T>
292        where
293            T: PartialEq,
294        {
295            pub field: T,
296        }
297
298        let patch = PatchItem {
299            field: Some(String::from("hello")),
300        };
301        let mut item = Item {
302            field: String::new(),
303        };
304        item.apply(patch);
305    }
306
307    #[test]
308    fn test_nested_generic() {
309        #[derive(PartialEq, Debug, Default, Patch, Deserialize)]
310        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
311        struct B<T>
312        where
313            T: PartialEq,
314        {
315            c: T,
316            d: T,
317        }
318
319        #[derive(PartialEq, Debug, Patch, Deserialize)]
320        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
321        struct A {
322            #[patch(name = "BPatch<u32>")]
323            b: B<u32>,
324        }
325
326        let mut b = B::default();
327        let b_patch: BPatch<u32> = serde_json::from_str(r#"{ "d": 1 }"#).unwrap();
328        b.apply(b_patch);
329        assert_eq!(b, B { c: 0, d: 1 });
330
331        let mut a = A { b };
332        let data = r#"{ "b": { "c": 1 } }"#;
333        let patch: APatch = serde_json::from_str(data).unwrap();
334
335        a.apply(patch);
336        assert_eq!(
337            a,
338            A {
339                b: B { c: 1, d: 1 }
340            }
341        );
342    }
343
344    #[cfg(feature = "op")]
345    #[test]
346    fn test_shl() {
347        #[derive(Patch, Debug, PartialEq)]
348        struct Item {
349            field: u32,
350            other: String,
351        }
352
353        let item = Item {
354            field: 1,
355            other: String::from("hello"),
356        };
357        let patch = ItemPatch {
358            field: None,
359            other: Some(String::from("bye")),
360        };
361
362        assert_eq!(
363            item << patch,
364            Item {
365                field: 1,
366                other: String::from("bye")
367            }
368        );
369    }
370
371    #[cfg(all(feature = "op", feature = "merge"))]
372    #[test]
373    fn test_shl_on_patch() {
374        #[derive(Patch, Debug, PartialEq)]
375        struct Item {
376            field: u32,
377            other: String,
378        }
379
380        let mut item = Item {
381            field: 1,
382            other: String::from("hello"),
383        };
384        let patch = ItemPatch {
385            field: None,
386            other: Some(String::from("bye")),
387        };
388        let patch2 = ItemPatch {
389            field: Some(2),
390            other: None,
391        };
392
393        let new_patch = patch << patch2;
394
395        item.apply(new_patch);
396        assert_eq!(
397            item,
398            Item {
399                field: 2,
400                other: String::from("bye")
401            }
402        );
403    }
404
405    #[cfg(feature = "op")]
406    #[test]
407    fn test_add_patches() {
408        #[derive(Patch)]
409        #[patch(attribute(derive(Debug, PartialEq)))]
410        struct Item {
411            field: u32,
412            other: String,
413        }
414
415        let patch = ItemPatch {
416            field: Some(1),
417            other: None,
418        };
419        let patch2 = ItemPatch {
420            field: None,
421            other: Some(String::from("hello")),
422        };
423        let overall_patch = patch + patch2;
424        assert_eq!(
425            overall_patch,
426            ItemPatch {
427                field: Some(1),
428                other: Some(String::from("hello")),
429            }
430        );
431    }
432
433    #[cfg(feature = "op")]
434    #[test]
435    #[should_panic]
436    fn test_add_conflict_patches_panic() {
437        #[derive(Patch, Debug, PartialEq)]
438        struct Item {
439            field: u32,
440        }
441
442        let patch = ItemPatch { field: Some(1) };
443        let patch2 = ItemPatch { field: Some(2) };
444        let _overall_patch = patch + patch2;
445    }
446
447    #[cfg(feature = "merge")]
448    #[test]
449    fn test_merge() {
450        #[allow(dead_code)]
451        #[derive(Patch)]
452        #[patch(attribute(derive(PartialEq, Debug)))]
453        struct Item {
454            a: u32,
455            b: u32,
456            c: u32,
457            d: u32,
458        }
459
460        let patch = ItemPatch {
461            a: None,
462            b: Some(2),
463            c: Some(0),
464            d: None,
465        };
466        let patch2 = ItemPatch {
467            a: Some(1),
468            b: None,
469            c: Some(3),
470            d: None,
471        };
472
473        let merged_patch = patch.merge(patch2);
474        assert_eq!(
475            merged_patch,
476            ItemPatch {
477                a: Some(1),
478                b: Some(2),
479                c: Some(3),
480                d: None,
481            }
482        );
483    }
484
485    #[cfg(feature = "merge")]
486    #[test]
487    fn test_merge_nested() {
488        #[allow(dead_code)]
489        #[derive(Patch, PartialEq, Debug)]
490        #[patch(attribute(derive(PartialEq, Debug, Clone)))]
491        struct B {
492            c: u32,
493            d: u32,
494            e: u32,
495            f: u32,
496        }
497
498        #[allow(dead_code)]
499        #[derive(Patch)]
500        #[patch(attribute(derive(PartialEq, Debug)))]
501        struct A {
502            a: u32,
503            #[patch(name = "BPatch")]
504            b: B,
505        }
506
507        let patches = alloc::vec![
508            APatch {
509                a: Some(1),
510                b: Some(BPatch {
511                    c: None,
512                    d: Some(2),
513                    e: Some(0),
514                    f: None,
515                }),
516            },
517            APatch {
518                a: Some(0),
519                b: Some(BPatch {
520                    c: Some(1),
521                    d: None,
522                    e: Some(3),
523                    f: None,
524                }),
525            },
526        ];
527
528        let merged_patch = patches.into_iter().reduce(Merge::merge).unwrap();
529
530        assert_eq!(
531            merged_patch,
532            APatch {
533                a: Some(0),
534                b: Some(BPatch {
535                    c: Some(1),
536                    d: Some(2),
537                    e: Some(3),
538                    f: None,
539                }),
540            }
541        );
542    }
543}