1 use darling::{util::Flag, FromDeriveInput, FromMeta};
2 use proc_macro2::Ident;
3 use syn::parse_quote;
4
5 #[derive(FromMeta)]
6 struct Vis {
7 public: Flag,
8 private: Flag,
9 }
10
11 #[derive(FromDeriveInput)]
12 #[darling(attributes(sample))]
13 struct Example {
14 ident: Ident,
15 label: String,
16 #[darling(flatten)]
17 visibility: Vis,
18 }
19
20 #[test]
happy_path()21 fn happy_path() {
22 let di = Example::from_derive_input(&parse_quote! {
23 #[sample(label = "Hello", public)]
24 struct Demo {}
25 });
26
27 let parsed = di.unwrap();
28 assert_eq!(parsed.ident, "Demo");
29 assert_eq!(&parsed.label, "Hello");
30 assert!(parsed.visibility.public.is_present());
31 assert!(!parsed.visibility.private.is_present());
32 }
33
34 #[test]
unknown_field_errors()35 fn unknown_field_errors() {
36 let errors = Example::from_derive_input(&parse_quote! {
37 #[sample(label = "Hello", republic)]
38 struct Demo {}
39 })
40 .map(|_| "Should have failed")
41 .unwrap_err();
42
43 assert_eq!(errors.len(), 1);
44 }
45
46 /// This test demonstrates flatten being used recursively.
47 /// Fields are expected to be consumed by the outermost matching struct.
48 #[test]
recursive_flattening()49 fn recursive_flattening() {
50 #[derive(FromMeta)]
51 struct Nested2 {
52 above: isize,
53 below: isize,
54 port: Option<isize>,
55 }
56
57 #[derive(FromMeta)]
58 struct Nested1 {
59 port: isize,
60 starboard: isize,
61 #[darling(flatten)]
62 z_axis: Nested2,
63 }
64
65 #[derive(FromMeta)]
66 struct Nested0 {
67 fore: isize,
68 aft: isize,
69 #[darling(flatten)]
70 cross_section: Nested1,
71 }
72
73 #[derive(FromDeriveInput)]
74 #[darling(attributes(boat))]
75 struct BoatPosition {
76 #[darling(flatten)]
77 pos: Nested0,
78 }
79
80 let parsed = BoatPosition::from_derive_input(&parse_quote! {
81 #[boat(fore = 1, aft = 1, port = 10, starboard = 50, above = 20, below = -3)]
82 struct Demo;
83 })
84 .unwrap();
85
86 assert_eq!(parsed.pos.fore, 1);
87 assert_eq!(parsed.pos.aft, 1);
88
89 assert_eq!(parsed.pos.cross_section.port, 10);
90 assert_eq!(parsed.pos.cross_section.starboard, 50);
91
92 assert_eq!(parsed.pos.cross_section.z_axis.above, 20);
93 assert_eq!(parsed.pos.cross_section.z_axis.below, -3);
94 // This should be `None` because the `port` field in `Nested1` consumed
95 // the field before the leftovers were passed to `Nested2::from_list`.
96 assert_eq!(parsed.pos.cross_section.z_axis.port, None);
97 }
98
99 /// This test confirms that a collection - in this case a HashMap - can
100 /// be used with `flatten`.
101 #[test]
flattening_into_hashmap()102 fn flattening_into_hashmap() {
103 #[derive(FromDeriveInput)]
104 #[darling(attributes(ca))]
105 struct Catchall {
106 hello: String,
107 volume: usize,
108 #[darling(flatten)]
109 others: std::collections::HashMap<String, String>,
110 }
111
112 let parsed = Catchall::from_derive_input(&parse_quote! {
113 #[ca(hello = "World", volume = 10, first_name = "Alice", second_name = "Bob")]
114 struct Demo;
115 })
116 .unwrap();
117
118 assert_eq!(parsed.hello, "World");
119 assert_eq!(parsed.volume, 10);
120 assert_eq!(parsed.others.len(), 2);
121 }
122
123 #[derive(FromMeta)]
124 #[allow(dead_code)]
125 struct Person {
126 first: String,
127 last: String,
128 parent: Option<Box<Person>>,
129 }
130
131 #[derive(FromDeriveInput)]
132 #[darling(attributes(v))]
133 #[allow(dead_code)]
134 struct Outer {
135 #[darling(flatten)]
136 owner: Person,
137 #[darling(default)]
138 blast: bool,
139 }
140
141 /// This test makes sure that field names from parent structs are not inappropriately
142 /// offered as alternates for unknown field errors in child structs.
143 ///
144 /// A naive implementation that tried to offer all the flattened fields for "did you mean"
145 /// could inspect all errors returned by the flattened field's `from_list` call and add the
146 /// parent's field names as alternates to all unknown field errors.
147 ///
148 /// THIS WOULD BE INCORRECT. Those unknown field errors may have already come from
149 /// child fields within the flattened struct, where the parent's field names are not valid.
150 #[test]
do_not_suggest_invalid_alts()151 fn do_not_suggest_invalid_alts() {
152 let errors = Outer::from_derive_input(&parse_quote! {
153 #[v(first = "Hello", last = "World", parent(first = "Hi", last = "Earth", blasts = "off"))]
154 struct Demo;
155 })
156 .map(|_| "Should have failed")
157 .unwrap_err()
158 .to_string();
159
160 assert!(
161 !errors.contains("`blast`"),
162 "Should not contain `blast`: {}",
163 errors
164 );
165 }
166
167 #[test]
168 #[cfg(feature = "suggestions")]
suggest_valid_parent_alts()169 fn suggest_valid_parent_alts() {
170 let errors = Outer::from_derive_input(&parse_quote! {
171 #[v(first = "Hello", bladt = false, last = "World", parent(first = "Hi", last = "Earth"))]
172 struct Demo;
173 })
174 .map(|_| "Should have failed")
175 .unwrap_err()
176 .to_string();
177 assert!(
178 errors.contains("`blast`"),
179 "Should contain `blast` as did-you-mean suggestion: {}",
180 errors
181 );
182 }
183
184 /// Make sure that flatten works with smart pointer types, e.g. `Box`.
185 ///
186 /// The generated `flatten` impl directly calls `FromMeta::from_list`
187 /// rather than calling `from_meta`, and the default impl of `from_list`
188 /// will return an unsupported format error; this test ensures that the
189 /// smart pointer type is properly forwarding the `from_list` call.
190 #[test]
flattening_to_box()191 fn flattening_to_box() {
192 #[derive(FromDeriveInput)]
193 #[darling(attributes(v))]
194 struct Example {
195 #[darling(flatten)]
196 items: Box<Vis>,
197 }
198
199 let when_omitted = Example::from_derive_input(&parse_quote! {
200 struct Demo;
201 })
202 .unwrap();
203
204 assert!(!when_omitted.items.public.is_present());
205 }
206