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