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