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