1 //! Types for "shape" validation. This allows types deriving `FromDeriveInput` etc. to declare
2 //! that they only work on - for example - structs with named fields, or newtype enum variants.
3 
4 use proc_macro2::TokenStream;
5 use quote::{ToTokens, TokenStreamExt};
6 use syn::{Meta, NestedMeta};
7 
8 use {Error, FromMeta, Result};
9 
10 /// Receiver struct for shape validation. Shape validation allows a deriving type
11 /// to declare that it only accepts - for example - named structs, or newtype enum
12 /// variants.
13 ///
14 /// # Usage
15 /// Because `Shape` implements `FromMeta`, the name of the field where it appears is
16 /// controlled by the struct that declares `Shape` as a member. That field name is
17 /// shown as `ignore` below.
18 ///
19 /// ```rust,ignore
20 /// #[ignore(any, struct_named, enum_newtype)]
21 /// ```
22 #[derive(Debug, Clone)]
23 pub struct Shape {
24     enum_values: DataShape,
25     struct_values: DataShape,
26     any: bool,
27 }
28 
29 impl Default for Shape {
default() -> Self30     fn default() -> Self {
31         Shape {
32             enum_values: DataShape::new("enum_"),
33             struct_values: DataShape::new("struct_"),
34             any: Default::default(),
35         }
36     }
37 }
38 
39 impl FromMeta for Shape {
from_list(items: &[NestedMeta]) -> Result<Self>40     fn from_list(items: &[NestedMeta]) -> Result<Self> {
41         let mut new = Shape::default();
42         for item in items {
43             if let NestedMeta::Meta(Meta::Path(ref path)) = *item {
44                 let ident = &path.segments.first().unwrap().ident;
45                 let word = ident.to_string();
46                 if word == "any" {
47                     new.any = true;
48                 } else if word.starts_with("enum_") {
49                     new.enum_values
50                         .set_word(&word)
51                         .map_err(|e| e.with_span(&ident))?;
52                 } else if word.starts_with("struct_") {
53                     new.struct_values
54                         .set_word(&word)
55                         .map_err(|e| e.with_span(&ident))?;
56                 } else {
57                     return Err(Error::unknown_value(&word).with_span(&ident));
58                 }
59             } else {
60                 return Err(Error::unsupported_format("non-word").with_span(item));
61             }
62         }
63 
64         Ok(new)
65     }
66 }
67 
68 impl ToTokens for Shape {
to_tokens(&self, tokens: &mut TokenStream)69     fn to_tokens(&self, tokens: &mut TokenStream) {
70         let fn_body = if self.any {
71             quote!(::darling::export::Ok(()))
72         } else {
73             let en = &self.enum_values;
74             let st = &self.struct_values;
75             quote! {
76                 match *__body {
77                     ::syn::Data::Enum(ref data) => {
78                         fn validate_variant(data: &::syn::Fields) -> ::darling::Result<()> {
79                             #en
80                         }
81 
82                         for variant in &data.variants {
83                             validate_variant(&variant.fields)?;
84                         }
85 
86                         Ok(())
87                     }
88                     ::syn::Data::Struct(ref struct_data) => {
89                         let data = &struct_data.fields;
90                         #st
91                     }
92                     ::syn::Data::Union(_) => unreachable!(),
93                 }
94             }
95         };
96 
97         tokens.append_all(quote! {
98             #[allow(unused_variables)]
99             fn __validate_body(__body: &::syn::Data) -> ::darling::Result<()> {
100                 #fn_body
101             }
102         });
103     }
104 }
105 
106 /// Receiver for shape information within a struct or enum context. See `Shape` for more information
107 /// on valid uses of shape validation.
108 #[derive(Debug, Clone, Default, PartialEq, Eq)]
109 pub struct DataShape {
110     /// The kind of shape being described. This can be `struct_` or `enum_`.
111     prefix: &'static str,
112     newtype: bool,
113     named: bool,
114     tuple: bool,
115     unit: bool,
116     any: bool,
117     /// Control whether the emitted code should be inside a function or not.
118     /// This is `true` when creating a `Shape` for `FromDeriveInput`, but false
119     /// when deriving `FromVariant`.
120     embedded: bool,
121 }
122 
123 impl DataShape {
new(prefix: &'static str) -> Self124     fn new(prefix: &'static str) -> Self {
125         DataShape {
126             prefix,
127             embedded: true,
128             ..Default::default()
129         }
130     }
131 
supports_none(&self) -> bool132     fn supports_none(&self) -> bool {
133         !(self.any || self.newtype || self.named || self.tuple || self.unit)
134     }
135 
set_word(&mut self, word: &str) -> Result<()>136     fn set_word(&mut self, word: &str) -> Result<()> {
137         match word.trim_left_matches(self.prefix) {
138             "newtype" => {
139                 self.newtype = true;
140                 Ok(())
141             }
142             "named" => {
143                 self.named = true;
144                 Ok(())
145             }
146             "tuple" => {
147                 self.tuple = true;
148                 Ok(())
149             }
150             "unit" => {
151                 self.unit = true;
152                 Ok(())
153             }
154             "any" => {
155                 self.any = true;
156                 Ok(())
157             }
158             _ => Err(Error::unknown_value(word)),
159         }
160     }
161 }
162 
163 impl FromMeta for DataShape {
from_list(items: &[NestedMeta]) -> Result<Self>164     fn from_list(items: &[NestedMeta]) -> Result<Self> {
165         let mut errors = Vec::new();
166         let mut new = DataShape::default();
167 
168         for item in items {
169             if let NestedMeta::Meta(Meta::Path(ref path)) = *item {
170                 if let Err(e) = new.set_word(&path.segments.first().unwrap().ident.to_string()) {
171                     errors.push(e.with_span(&path));
172                 }
173             } else {
174                 errors.push(Error::unsupported_format("non-word").with_span(item));
175             }
176         }
177 
178         if !errors.is_empty() {
179             Err(Error::multiple(errors))
180         } else {
181             Ok(new)
182         }
183     }
184 }
185 
186 impl ToTokens for DataShape {
to_tokens(&self, tokens: &mut TokenStream)187     fn to_tokens(&self, tokens: &mut TokenStream) {
188         let body = if self.any {
189             quote!(::darling::export::Ok(()))
190         } else if self.supports_none() {
191             let ty = self.prefix.trim_right_matches('_');
192             quote!(::darling::export::Err(::darling::Error::unsupported_shape(#ty)))
193         } else {
194             let unit = match_arm("unit", self.unit);
195             let newtype = match_arm("newtype", self.newtype);
196             let named = match_arm("named", self.named);
197             let tuple = match_arm("tuple", self.tuple);
198             quote! {
199                 match *data {
200                     ::syn::Fields::Unit => #unit,
201                     ::syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => #newtype,
202                     ::syn::Fields::Unnamed(_) => #tuple,
203                     ::syn::Fields::Named(_) => #named,
204                 }
205             }
206         };
207 
208         if self.embedded {
209             body.to_tokens(tokens);
210         } else {
211             tokens.append_all(quote! {
212                 fn __validate_data(data: &::syn::Fields) -> ::darling::Result<()> {
213                     #body
214                 }
215             });
216         }
217     }
218 }
219 
match_arm(name: &'static str, is_supported: bool) -> TokenStream220 fn match_arm(name: &'static str, is_supported: bool) -> TokenStream {
221     if is_supported {
222         quote!(::darling::export::Ok(()))
223     } else {
224         quote!(::darling::export::Err(::darling::Error::unsupported_shape(#name)))
225     }
226 }
227 
228 #[cfg(test)]
229 mod tests {
230     use proc_macro2::TokenStream;
231     use syn;
232 
233     use super::Shape;
234     use FromMeta;
235 
236     /// parse a string as a syn::Meta instance.
pm(tokens: TokenStream) -> ::std::result::Result<syn::Meta, String>237     fn pm(tokens: TokenStream) -> ::std::result::Result<syn::Meta, String> {
238         let attribute: syn::Attribute = parse_quote!(#[#tokens]);
239         attribute.parse_meta().or(Err("Unable to parse".into()))
240     }
241 
fm<T: FromMeta>(tokens: TokenStream) -> T242     fn fm<T: FromMeta>(tokens: TokenStream) -> T {
243         FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input"))
244             .expect("Tests should pass valid input")
245     }
246 
247     #[test]
supports_any()248     fn supports_any() {
249         let decl = fm::<Shape>(quote!(ignore(any)));
250         assert_eq!(decl.any, true);
251     }
252 
253     #[test]
supports_struct()254     fn supports_struct() {
255         let decl = fm::<Shape>(quote!(ignore(struct_any, struct_newtype)));
256         assert_eq!(decl.struct_values.any, true);
257         assert_eq!(decl.struct_values.newtype, true);
258     }
259 
260     #[test]
supports_mixed()261     fn supports_mixed() {
262         let decl = fm::<Shape>(quote!(ignore(struct_newtype, enum_newtype, enum_tuple)));
263         assert_eq!(decl.struct_values.newtype, true);
264         assert_eq!(decl.enum_values.newtype, true);
265         assert_eq!(decl.enum_values.tuple, true);
266         assert_eq!(decl.struct_values.any, false);
267     }
268 }
269