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