struct_patch/
lib.rs

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