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