darling_core/options/
shape.rs1use proc_macro2::{Span, TokenStream};
5use quote::{quote, ToTokens, TokenStreamExt};
6use syn::{parse_quote, Meta};
7
8use crate::ast::NestedMeta;
9use crate::{Error, FromMeta, Result};
10
11#[derive(Debug, Clone)]
19pub struct DeriveInputShapeSet {
20 enum_values: DataShape,
21 struct_values: DataShape,
22 any: bool,
23}
24
25impl DeriveInputShapeSet {
26 fn validator_fn_ident(&self) -> syn::Ident {
27 syn::Ident::new("__validate_body", Span::call_site())
28 }
29
30 pub fn validator_path(&self) -> syn::Path {
31 if self.any {
32 parse_quote!(::darling::export::Ok)
33 } else {
34 self.validator_fn_ident().into()
35 }
36 }
37}
38
39impl Default for DeriveInputShapeSet {
40 fn default() -> Self {
41 DeriveInputShapeSet {
42 enum_values: DataShape::new("enum_"),
43 struct_values: DataShape::new("struct_"),
44 any: Default::default(),
45 }
46 }
47}
48
49impl FromMeta for DeriveInputShapeSet {
50 fn from_list(items: &[NestedMeta]) -> Result<Self> {
51 let mut new = DeriveInputShapeSet::default();
52 for item in items {
53 if let NestedMeta::Meta(Meta::Path(ref path)) = *item {
54 let ident = &path.segments.first().unwrap().ident;
55 let word = ident.to_string();
56 if word == "any" {
57 new.any = true;
58 } else if word.starts_with("enum_") {
59 new.enum_values
60 .set_word(&word)
61 .map_err(|e| e.with_span(&ident))?;
62 } else if word.starts_with("struct_") {
63 new.struct_values
64 .set_word(&word)
65 .map_err(|e| e.with_span(&ident))?;
66 } else {
67 return Err(Error::unknown_value(&word).with_span(&ident));
68 }
69 } else {
70 return Err(Error::unsupported_format("non-word").with_span(item));
71 }
72 }
73
74 Ok(new)
75 }
76}
77
78impl ToTokens for DeriveInputShapeSet {
80 fn to_tokens(&self, tokens: &mut TokenStream) {
81 if self.any {
82 return;
83 }
84
85 let fn_body = {
86 let en = &self.enum_values;
87 let st = &self.struct_values;
88
89 quote! {
90 {
91 let struct_check = #st;
92 let enum_check = #en;
93
94 match *__body {
95 ::darling::export::syn::Data::Enum(ref data) => {
96 if enum_check.is_empty() {
97 return ::darling::export::Err(
98 ::darling::Error::unsupported_shape_with_expected("enum", &format!("struct with {}", struct_check))
99 );
100 }
101
102 let mut variant_errors = ::darling::Error::accumulator();
103 for variant in &data.variants {
104 variant_errors.handle(enum_check.check(variant));
105 }
106
107 variant_errors.finish_with(__body)
108 }
109 ::darling::export::syn::Data::Struct(ref struct_data) => {
110 if struct_check.is_empty() {
111 return ::darling::export::Err(
112 ::darling::Error::unsupported_shape_with_expected("struct", &format!("enum with {}", enum_check))
113 );
114 }
115
116 struct_check.check(struct_data).and(::darling::export::Ok(__body))
117 }
118 ::darling::export::syn::Data::Union(_) => {
119 let expected = if enum_check.is_empty() {
120 format!("struct with {}", struct_check)
121 } else if struct_check.is_empty() {
122 format!("enum with {}", enum_check)
123 } else {
124 format!("struct with {} or enum with {}", struct_check, enum_check)
125 };
126 ::darling::export::Err(::darling::Error::unsupported_shape_with_expected("union", &expected))
127 },
128 }
129 }
130 }
131 };
132
133 let fn_ident = self.validator_fn_ident();
134
135 tokens.append_all(quote! {
136 fn #fn_ident(__body: &::darling::export::syn::Data) -> ::darling::Result<&::darling::export::syn::Data> {
137 #fn_body
138 }
139 });
140 }
141}
142
143#[derive(Debug, Clone, Default, PartialEq, Eq)]
146pub struct DataShape {
147 prefix: &'static str,
149 newtype: bool,
150 named: bool,
151 tuple: bool,
152 unit: bool,
153 any: bool,
154}
155
156impl DataShape {
157 fn new(prefix: &'static str) -> Self {
158 DataShape {
159 prefix,
160 ..Default::default()
161 }
162 }
163
164 fn set_word(&mut self, word: &str) -> Result<()> {
165 match word.trim_start_matches(self.prefix) {
166 "newtype" => {
167 self.newtype = true;
168 Ok(())
169 }
170 "named" => {
171 self.named = true;
172 Ok(())
173 }
174 "tuple" => {
175 self.tuple = true;
176 Ok(())
177 }
178 "unit" => {
179 self.unit = true;
180 Ok(())
181 }
182 "any" => {
183 self.any = true;
184 Ok(())
185 }
186 _ => Err(Error::unknown_value(word)),
187 }
188 }
189}
190
191impl FromMeta for DataShape {
192 fn from_list(items: &[NestedMeta]) -> Result<Self> {
193 let mut errors = Error::accumulator();
194 let mut new = DataShape::default();
195
196 for item in items {
197 if let NestedMeta::Meta(Meta::Path(ref path)) = *item {
198 errors.handle(new.set_word(&path.segments.first().unwrap().ident.to_string()));
199 } else {
200 errors.push(Error::unsupported_format("non-word").with_span(item));
201 }
202 }
203
204 errors.finish_with(new)
205 }
206}
207
208impl ToTokens for DataShape {
209 fn to_tokens(&self, tokens: &mut TokenStream) {
210 let Self {
211 any,
212 named,
213 tuple,
214 unit,
215 newtype,
216 ..
217 } = *self;
218
219 let shape_path: syn::Path = parse_quote!(::darling::util::Shape);
220
221 let mut shapes = vec![];
222 if any || named {
223 shapes.push(quote!(#shape_path::Named));
224 }
225
226 if any || tuple {
227 shapes.push(quote!(#shape_path::Tuple));
228 }
229
230 if any || newtype {
231 shapes.push(quote!(#shape_path::Newtype));
232 }
233
234 if any || unit {
235 shapes.push(quote!(#shape_path::Unit));
236 }
237
238 tokens.append_all(quote! {
239 ::darling::util::ShapeSet::new(vec![#(#shapes),*])
240 });
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use proc_macro2::TokenStream;
247 use quote::quote;
248 use syn::parse_quote;
249
250 use super::DeriveInputShapeSet;
251 use crate::FromMeta;
252
253 fn pm(tokens: TokenStream) -> ::std::result::Result<syn::Meta, String> {
255 let attribute: syn::Attribute = parse_quote!(#[#tokens]);
256 Ok(attribute.meta)
257 }
258
259 fn fm<T: FromMeta>(tokens: TokenStream) -> T {
260 FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input"))
261 .expect("Tests should pass valid input")
262 }
263
264 #[test]
265 fn supports_any() {
266 let decl = fm::<DeriveInputShapeSet>(quote!(ignore(any)));
267 assert!(decl.any);
268 }
269
270 #[test]
271 fn supports_struct() {
272 let decl = fm::<DeriveInputShapeSet>(quote!(ignore(struct_any, struct_newtype)));
273 assert!(decl.struct_values.any);
274 assert!(decl.struct_values.newtype);
275 }
276
277 #[test]
278 fn supports_mixed() {
279 let decl =
280 fm::<DeriveInputShapeSet>(quote!(ignore(struct_newtype, enum_newtype, enum_tuple)));
281 assert!(decl.struct_values.newtype);
282 assert!(decl.enum_values.newtype);
283 assert!(decl.enum_values.tuple);
284 assert!(!decl.struct_values.any);
285 }
286}