Skip to content

Commit 1591f77

Browse files
committed
Add nesting feature
1 parent a4570af commit 1591f77

File tree

5 files changed

+167
-0
lines changed

5 files changed

+167
-0
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ jobs:
2626
nix develop -c cargo run --no-default-features --example rename-patch-struct
2727
nix develop -c cargo run --no-default-features --example patch-attr
2828
nix develop -c cargo run --no-default-features --example time
29+
nix develop -c cargo run --no-default-features --features=nesting --example nesting
2930
nix develop -c cargo test --no-default-features
3031
3132
- name: Test with std features

struct-patch-derive/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ syn = { version = "2.0", features = ["parsing"] }
2222
status = []
2323
op = []
2424
merge = []
25+
nesting = []
2526

2627
[dev-dependencies]
2728
pretty_assertions_sorted = "1.2.3"

struct-patch-derive/src/patch.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const ATTRIBUTE: &str = "attribute";
1313
const SKIP: &str = "skip";
1414
const ADDABLE: &str = "addable";
1515
const ADD: &str = "add";
16+
const NESTING: &str = "nesting";
1617

1718
pub(crate) struct Patch {
1819
visibility: syn::Visibility,
@@ -38,6 +39,8 @@ struct Field {
3839
retyped: bool,
3940
#[cfg(feature = "op")]
4041
addable: Addable,
42+
#[cfg(feature = "nesting")]
43+
nesting: bool,
4144
}
4245

4346
impl Patch {
@@ -56,20 +59,60 @@ impl Patch {
5659
.iter()
5760
.map(|f| f.to_token_stream())
5861
.collect::<Result<Vec<_>>>()?;
62+
63+
#[cfg(not(feature = "nesting"))]
5964
let field_names = fields.iter().map(|f| f.ident.as_ref()).collect::<Vec<_>>();
65+
#[cfg(feature = "nesting")]
66+
let field_names = fields
67+
.iter()
68+
.filter(|f| !f.nesting)
69+
.map(|f| f.ident.as_ref()).collect::<Vec<_>>();
6070

71+
#[cfg(not(feature = "nesting"))]
6172
let renamed_field_names = fields
6273
.iter()
6374
.filter(|f| f.retyped)
6475
.map(|f| f.ident.as_ref())
6576
.collect::<Vec<_>>();
77+
#[cfg(feature = "nesting")]
78+
let renamed_field_names = fields
79+
.iter()
80+
.filter(|f| f.retyped && !f.nesting)
81+
.map(|f| f.ident.as_ref())
82+
.collect::<Vec<_>>();
6683

84+
#[cfg(not(feature = "nesting"))]
6785
let original_field_names = fields
6886
.iter()
6987
.filter(|f| !f.retyped)
7088
.map(|f| f.ident.as_ref())
7189
.collect::<Vec<_>>();
7290

91+
#[cfg(feature = "nesting")]
92+
let original_field_names = fields
93+
.iter()
94+
.filter(|f| !f.retyped && !f.nesting)
95+
.map(|f| f.ident.as_ref())
96+
.collect::<Vec<_>>();
97+
98+
#[cfg(not(feature = "nesting"))]
99+
let nesting_field_names: Vec<String> = Vec::new();
100+
#[cfg(not(feature = "nesting"))]
101+
let nesting_field_types: Vec<Type> = Vec::new();
102+
103+
#[cfg(feature = "nesting")]
104+
let nesting_field_names = fields
105+
.iter()
106+
.filter(|f| f.nesting)
107+
.map(|f| f.ident.as_ref())
108+
.collect::<Vec<_>>();
109+
#[cfg(feature = "nesting")]
110+
let nesting_field_types = fields
111+
.iter()
112+
.filter(|f| f.nesting)
113+
.map(|f| f.ty.clone())
114+
.collect::<Vec<_>>();
115+
73116
let mapped_attributes = attributes
74117
.iter()
75118
.map(|a| {
@@ -119,6 +162,9 @@ impl Patch {
119162
#(
120163
#original_field_names: other.#original_field_names.or(self.#original_field_names),
121164
)*
165+
#(
166+
#nesting_field_names: other.#nesting_field_names.apply(self.#nesting_field_names),
167+
)*
122168
}
123169
}
124170
}
@@ -252,6 +298,9 @@ impl Patch {
252298
self.#original_field_names = v;
253299
}
254300
)*
301+
#(
302+
self.#nesting_field_names.apply(patch.#nesting_field_names);
303+
)*
255304
}
256305

257306
fn into_patch(self) -> #name #generics {
@@ -262,6 +311,9 @@ impl Patch {
262311
#(
263312
#original_field_names: Some(self.#original_field_names),
264313
)*
314+
#(
315+
#nesting_field_names: self.#nesting_field_names.into_patch(),
316+
)*
265317
}
266318
}
267319

@@ -283,6 +335,9 @@ impl Patch {
283335
None
284336
},
285337
)*
338+
#(
339+
#nesting_field_names: self.#nesting_field_names.into_patch_by_diff(previous_struct.#nesting_field_names),
340+
)*
286341
}
287342
}
288343

@@ -291,6 +346,9 @@ impl Patch {
291346
#(
292347
#field_names: None,
293348
)*
349+
#(
350+
#nesting_field_names: #nesting_field_types::new_empty_patch(),
351+
)*
294352
}
295353
}
296354
}
@@ -403,6 +461,8 @@ impl Field {
403461
ident,
404462
ty,
405463
attributes,
464+
#[cfg(feature = "nesting")]
465+
nesting,
406466
..
407467
} = self;
408468

@@ -415,14 +475,48 @@ impl Field {
415475
})
416476
.collect::<Vec<_>>();
417477
match ident {
478+
#[cfg(not(feature = "nesting"))]
418479
Some(ident) => Ok(quote! {
419480
#(#attributes)*
420481
pub #ident: Option<#ty>,
421482
}),
483+
#[cfg(feature = "nesting")]
484+
Some(ident) => {
485+
if *nesting {
486+
// TODO handle rename
487+
let patch_type = syn::Ident::new(&format!("{}Patch", &ty.to_token_stream()), Span::call_site());
488+
Ok(quote! {
489+
#(#attributes)*
490+
pub #ident: #patch_type,
491+
})
492+
} else {
493+
Ok(quote! {
494+
#(#attributes)*
495+
pub #ident: Option<#ty>,
496+
})
497+
}
498+
},
499+
#[cfg(not(feature = "nesting"))]
422500
None => Ok(quote! {
423501
#(#attributes)*
424502
pub Option<#ty>,
425503
}),
504+
#[cfg(feature = "nesting")]
505+
None => {
506+
if *nesting {
507+
// TODO handle rename
508+
let patch_type = syn::Ident::new(&format!("{}Patch", &ty.to_token_stream()), Span::call_site());
509+
Ok(quote! {
510+
#(#attributes)*
511+
pub #patch_type,
512+
})
513+
} else {
514+
Ok(quote! {
515+
#(#attributes)*
516+
pub Option<#ty>,
517+
})
518+
}
519+
},
426520
}
427521
}
428522

@@ -438,6 +532,8 @@ impl Field {
438532

439533
#[cfg(feature = "op")]
440534
let mut addable = Addable::Disable;
535+
#[cfg(feature = "nesting")]
536+
let mut nesting = false;
441537

442538
for attr in attrs {
443539
if attr.path().to_string().as_str() != PATCH {
@@ -491,6 +587,16 @@ impl Field {
491587
ADD => {
492588
return Err(syn::Error::new(ident.span(), "`add` needs `op` feature"));
493589
}
590+
#[cfg(feature = "nesting")]
591+
NESTING => {
592+
// #[patch(nesting)]
593+
nesting = true;
594+
}
595+
#[cfg(not(feature = "nesting"))]
596+
NESTING => {
597+
return Err(
598+
meta.error("#[patch(nesting)] only work with `nesting` feature"));
599+
}
494600
_ => {
495601
return Err(meta.error(format_args!(
496602
"unknown patch field attribute `{}`",
@@ -512,6 +618,8 @@ impl Field {
512618
attributes,
513619
#[cfg(feature = "op")]
514620
addable,
621+
#[cfg(feature = "nesting")]
622+
nesting,
515623
}))
516624
}
517625
}

struct-patch/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@ merge = [
3535
std = ["box", "option"]
3636
box = []
3737
option = []
38+
nesting = []
3839
none_as_default = ["option"]
3940
keep_none = ["option"]

struct-patch/examples/nesting.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use serde::Deserialize;
2+
use struct_patch::Patch;
3+
4+
#[allow(dead_code)]
5+
#[cfg(feature = "nesting")]
6+
#[derive(Clone, Debug, Default, Patch, PartialEq)]
7+
#[patch(attribute(derive(Debug, Deserialize, PartialEq)))]
8+
struct Item {
9+
field_complete: bool,
10+
field_int: usize,
11+
field_string: String,
12+
#[patch(nesting)]
13+
inner: Nesting,
14+
}
15+
16+
#[allow(dead_code)]
17+
#[derive(Clone, Debug, Default, Patch, PartialEq)]
18+
#[patch(attribute(derive(Debug, Deserialize, PartialEq)))]
19+
struct Nesting {
20+
inner_int: usize,
21+
inner_string: String,
22+
}
23+
24+
#[cfg(not(feature = "nesting"))]
25+
fn main() {}
26+
27+
#[cfg(feature = "nesting")]
28+
fn main() {
29+
let item_a = Item::default();
30+
let item_b = Item {
31+
field_int: 7,
32+
inner: Nesting {
33+
inner_int: 100,
34+
..Default::default()
35+
},
36+
..Default::default()
37+
};
38+
39+
let patch = item_b.clone().into_patch_by_diff(item_a);
40+
assert_eq!(
41+
format!("{patch:?}"),
42+
"ItemPatch { field_complete: None, field_int: Some(7), field_string: None, inner: NestingPatch { inner_int: Some(100), inner_string: None } }"
43+
);
44+
45+
let data = r#"{
46+
"field_int": 7,
47+
"inner": {
48+
"inner_int": 100
49+
}
50+
}"#;
51+
assert_eq!(patch, serde_json::from_str(data).unwrap());
52+
53+
let mut item = Item::default();
54+
item.apply(patch);
55+
assert_eq!(item, item_b);
56+
}

0 commit comments

Comments
 (0)