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#![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, Default, 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        let mut b = B::default();
227        let b_patch: BPatch = serde_json::from_str(r#"{ "d": 1 }"#).unwrap();
228        b.apply(b_patch);
229        assert_eq!(b, B { c: 0, d: 1 });
230
231        let mut a = A { b };
232        let data = r#"{ "b": { "c": 1 } }"#;
233        let patch: APatch = serde_json::from_str(data).unwrap();
234        // assert_eq!(
235        //     patch,
236        //     APatch {
237        //         b: Some(B { id: 1 })
238        //     }
239        // );
240        a.apply(patch);
241        assert_eq!(
242            a,
243            A {
244                b: B { c: 1, d: 1 }
245            }
246        );
247    }
248
249    #[test]
250    fn test_generic() {
251        #[derive(Patch)]
252        struct Item<T>
253        where
254            T: PartialEq,
255        {
256            pub field: T,
257        }
258
259        let patch = ItemPatch {
260            field: Some(String::from("hello")),
261        };
262        let mut item = Item {
263            field: String::new(),
264        };
265        item.apply(patch);
266        assert_eq!(item.field, "hello");
267    }
268
269    #[test]
270    fn test_named_generic() {
271        #[derive(Patch)]
272        #[patch(name = "PatchItem")]
273        struct Item<T>
274        where
275            T: PartialEq,
276        {
277            pub field: T,
278        }
279
280        let patch = PatchItem {
281            field: Some(String::from("hello")),
282        };
283        let mut item = Item {
284            field: String::new(),
285        };
286        item.apply(patch);
287    }
288
289    #[test]
290    fn test_nested_generic() {
291        #[derive(PartialEq, Debug, Default, Patch, Deserialize)]
292        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
293        struct B<T>
294        where
295            T: PartialEq,
296        {
297            c: T,
298            d: T,
299        }
300
301        #[derive(PartialEq, Debug, Patch, Deserialize)]
302        #[patch(attribute(derive(PartialEq, Debug, Deserialize)))]
303        struct A {
304            #[patch(name = "BPatch<u32>")]
305            b: B<u32>,
306        }
307
308        let mut b = B::default();
309        let b_patch: BPatch<u32> = serde_json::from_str(r#"{ "d": 1 }"#).unwrap();
310        b.apply(b_patch);
311        assert_eq!(b, B { c: 0, d: 1 });
312
313        let mut a = A { b };
314        let data = r#"{ "b": { "c": 1 } }"#;
315        let patch: APatch = serde_json::from_str(data).unwrap();
316
317        a.apply(patch);
318        assert_eq!(
319            a,
320            A {
321                b: B { c: 1, d: 1 }
322            }
323        );
324    }
325
326    #[cfg(feature = "op")]
327    #[test]
328    fn test_shl() {
329        #[derive(Patch, Debug, PartialEq)]
330        struct Item {
331            field: u32,
332            other: String,
333        }
334
335        let item = Item {
336            field: 1,
337            other: String::from("hello"),
338        };
339        let patch = ItemPatch {
340            field: None,
341            other: Some(String::from("bye")),
342        };
343
344        assert_eq!(
345            item << patch,
346            Item {
347                field: 1,
348                other: String::from("bye")
349            }
350        );
351    }
352
353    #[cfg(all(feature = "op", feature = "merge"))]
354    #[test]
355    fn test_shl_on_patch() {
356        #[derive(Patch, Debug, PartialEq)]
357        struct Item {
358            field: u32,
359            other: String,
360        }
361
362        let mut item = Item {
363            field: 1,
364            other: String::from("hello"),
365        };
366        let patch = ItemPatch {
367            field: None,
368            other: Some(String::from("bye")),
369        };
370        let patch2 = ItemPatch {
371            field: Some(2),
372            other: None,
373        };
374
375        let new_patch = patch << patch2;
376
377        item.apply(new_patch);
378        assert_eq!(
379            item,
380            Item {
381                field: 2,
382                other: String::from("bye")
383            }
384        );
385    }
386
387    #[cfg(feature = "op")]
388    #[test]
389    fn test_add_patches() {
390        #[derive(Patch)]
391        #[patch(attribute(derive(Debug, PartialEq)))]
392        struct Item {
393            field: u32,
394            other: String,
395        }
396
397        let patch = ItemPatch {
398            field: Some(1),
399            other: None,
400        };
401        let patch2 = ItemPatch {
402            field: None,
403            other: Some(String::from("hello")),
404        };
405        let overall_patch = patch + patch2;
406        assert_eq!(
407            overall_patch,
408            ItemPatch {
409                field: Some(1),
410                other: Some(String::from("hello")),
411            }
412        );
413    }
414
415    #[cfg(feature = "op")]
416    #[test]
417    #[should_panic]
418    fn test_add_conflict_patches_panic() {
419        #[derive(Patch, Debug, PartialEq)]
420        struct Item {
421            field: u32,
422        }
423
424        let patch = ItemPatch { field: Some(1) };
425        let patch2 = ItemPatch { field: Some(2) };
426        let _overall_patch = patch + patch2;
427    }
428
429    #[cfg(feature = "merge")]
430    #[test]
431    fn test_merge() {
432        #[allow(dead_code)]
433        #[derive(Patch)]
434        #[patch(attribute(derive(PartialEq, Debug)))]
435        struct Item {
436            a: u32,
437            b: u32,
438            c: u32,
439            d: u32,
440        }
441
442        let patch = ItemPatch {
443            a: None,
444            b: Some(2),
445            c: Some(0),
446            d: None,
447        };
448        let patch2 = ItemPatch {
449            a: Some(1),
450            b: None,
451            c: Some(3),
452            d: None,
453        };
454
455        let merged_patch = patch.merge(patch2);
456        assert_eq!(
457            merged_patch,
458            ItemPatch {
459                a: Some(1),
460                b: Some(2),
461                c: Some(3),
462                d: None,
463            }
464        );
465    }
466
467    #[cfg(feature = "merge")]
468    #[test]
469    fn test_merge_nested() {
470        #[allow(dead_code)]
471        #[derive(Patch, PartialEq, Debug)]
472        #[patch(attribute(derive(PartialEq, Debug, Clone)))]
473        struct B {
474            c: u32,
475            d: u32,
476            e: u32,
477            f: u32,
478        }
479
480        #[allow(dead_code)]
481        #[derive(Patch)]
482        #[patch(attribute(derive(PartialEq, Debug)))]
483        struct A {
484            a: u32,
485            #[patch(name = "BPatch")]
486            b: B,
487        }
488
489        let patches = vec![
490            APatch {
491                a: Some(1),
492                b: Some(BPatch {
493                    c: None,
494                    d: Some(2),
495                    e: Some(0),
496                    f: None,
497                }),
498            },
499            APatch {
500                a: Some(0),
501                b: Some(BPatch {
502                    c: Some(1),
503                    d: None,
504                    e: Some(3),
505                    f: None,
506                }),
507            },
508        ];
509
510        let merged_patch = patches.into_iter().reduce(Merge::merge).unwrap();
511
512        assert_eq!(
513            merged_patch,
514            APatch {
515                a: Some(0),
516                b: Some(BPatch {
517                    c: Some(1),
518                    d: Some(2),
519                    e: Some(3),
520                    f: None,
521                }),
522            }
523        );
524    }
525}