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