• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::{quote, ToTokens, TokenStreamExt};
6 use syn::{parse_quote, Meta};
7 
8 use crate::ast::NestedMeta;
9 use crate::{Error, FromMeta, Result};
10 
11 /// Receiver struct for shape validation. Shape validation allows a deriving type
12 /// to declare that it only accepts - for example - named structs, or newtype enum
13 /// variants.
14 ///
15 /// ```rust,ignore
16 /// #[ignore(any, struct_named, enum_newtype)]
17 /// ```
18 #[derive(Debug, Clone)]
19 pub struct DeriveInputShapeSet {
20     enum_values: DataShape,
21     struct_values: DataShape,
22     any: bool,
23 }
24 
25 impl Default for DeriveInputShapeSet {
default() -> Self26     fn default() -> Self {
27         DeriveInputShapeSet {
28             enum_values: DataShape::new("enum_"),
29             struct_values: DataShape::new("struct_"),
30             any: Default::default(),
31         }
32     }
33 }
34 
35 impl FromMeta for DeriveInputShapeSet {
from_list(items: &[NestedMeta]) -> Result<Self>36     fn from_list(items: &[NestedMeta]) -> Result<Self> {
37         let mut new = DeriveInputShapeSet::default();
38         for item in items {
39             if let NestedMeta::Meta(Meta::Path(ref path)) = *item {
40                 let ident = &path.segments.first().unwrap().ident;
41                 let word = ident.to_string();
42                 if word == "any" {
43                     new.any = true;
44                 } else if word.starts_with("enum_") {
45                     new.enum_values
46                         .set_word(&word)
47                         .map_err(|e| e.with_span(&ident))?;
48                 } else if word.starts_with("struct_") {
49                     new.struct_values
50                         .set_word(&word)
51                         .map_err(|e| e.with_span(&ident))?;
52                 } else {
53                     return Err(Error::unknown_value(&word).with_span(&ident));
54                 }
55             } else {
56                 return Err(Error::unsupported_format("non-word").with_span(item));
57             }
58         }
59 
60         Ok(new)
61     }
62 }
63 
64 impl ToTokens for DeriveInputShapeSet {
to_tokens(&self, tokens: &mut TokenStream)65     fn to_tokens(&self, tokens: &mut TokenStream) {
66         let fn_body = if self.any {
67             quote!(::darling::export::Ok(()))
68         } else {
69             let en = &self.enum_values;
70             let st = &self.struct_values;
71 
72             quote! {
73                 {
74                     let struct_check = #st;
75                     let enum_check = #en;
76 
77                     match *__body {
78                         ::darling::export::syn::Data::Enum(ref data) => {
79                             if enum_check.is_empty() {
80                                 return ::darling::export::Err(
81                                     ::darling::Error::unsupported_shape_with_expected("enum", &format!("struct with {}", struct_check))
82                                 );
83                             }
84 
85                             let mut variant_errors = ::darling::Error::accumulator();
86                             for variant in &data.variants {
87                                 variant_errors.handle(enum_check.check(variant));
88                             }
89 
90                             variant_errors.finish()
91                         }
92                         ::darling::export::syn::Data::Struct(ref struct_data) => {
93                             if struct_check.is_empty() {
94                                 return ::darling::export::Err(
95                                     ::darling::Error::unsupported_shape_with_expected("struct", &format!("enum with {}", enum_check))
96                                 );
97                             }
98 
99                             struct_check.check(struct_data)
100                         }
101                         ::darling::export::syn::Data::Union(_) => unreachable!(),
102                     }
103                 }
104             }
105         };
106 
107         tokens.append_all(quote! {
108             #[allow(unused_variables)]
109             fn __validate_body(__body: &::darling::export::syn::Data) -> ::darling::Result<()> {
110                 #fn_body
111             }
112         });
113     }
114 }
115 
116 /// Receiver for shape information within a struct or enum context. See `Shape` for more information
117 /// on valid uses of shape validation.
118 #[derive(Debug, Clone, Default, PartialEq, Eq)]
119 pub struct DataShape {
120     /// The kind of shape being described. This can be `struct_` or `enum_`.
121     prefix: &'static str,
122     newtype: bool,
123     named: bool,
124     tuple: bool,
125     unit: bool,
126     any: bool,
127 }
128 
129 impl DataShape {
new(prefix: &'static str) -> Self130     fn new(prefix: &'static str) -> Self {
131         DataShape {
132             prefix,
133             ..Default::default()
134         }
135     }
136 
set_word(&mut self, word: &str) -> Result<()>137     fn set_word(&mut self, word: &str) -> Result<()> {
138         match word.trim_start_matches(self.prefix) {
139             "newtype" => {
140                 self.newtype = true;
141                 Ok(())
142             }
143             "named" => {
144                 self.named = true;
145                 Ok(())
146             }
147             "tuple" => {
148                 self.tuple = true;
149                 Ok(())
150             }
151             "unit" => {
152                 self.unit = true;
153                 Ok(())
154             }
155             "any" => {
156                 self.any = true;
157                 Ok(())
158             }
159             _ => Err(Error::unknown_value(word)),
160         }
161     }
162 }
163 
164 impl FromMeta for DataShape {
from_list(items: &[NestedMeta]) -> Result<Self>165     fn from_list(items: &[NestedMeta]) -> Result<Self> {
166         let mut errors = Error::accumulator();
167         let mut new = DataShape::default();
168 
169         for item in items {
170             if let NestedMeta::Meta(Meta::Path(ref path)) = *item {
171                 errors.handle(new.set_word(&path.segments.first().unwrap().ident.to_string()));
172             } else {
173                 errors.push(Error::unsupported_format("non-word").with_span(item));
174             }
175         }
176 
177         errors.finish_with(new)
178     }
179 }
180 
181 impl ToTokens for DataShape {
to_tokens(&self, tokens: &mut TokenStream)182     fn to_tokens(&self, tokens: &mut TokenStream) {
183         let Self {
184             any,
185             named,
186             tuple,
187             unit,
188             newtype,
189             ..
190         } = *self;
191 
192         let shape_path: syn::Path = parse_quote!(::darling::util::Shape);
193 
194         let mut shapes = vec![];
195         if any || named {
196             shapes.push(quote!(#shape_path::Named));
197         }
198 
199         if any || tuple {
200             shapes.push(quote!(#shape_path::Tuple));
201         }
202 
203         if any || newtype {
204             shapes.push(quote!(#shape_path::Newtype));
205         }
206 
207         if any || unit {
208             shapes.push(quote!(#shape_path::Unit));
209         }
210 
211         tokens.append_all(quote! {
212             ::darling::util::ShapeSet::new(vec![#(#shapes),*])
213         });
214     }
215 }
216 
217 #[cfg(test)]
218 mod tests {
219     use proc_macro2::TokenStream;
220     use quote::quote;
221     use syn::parse_quote;
222 
223     use super::DeriveInputShapeSet;
224     use crate::FromMeta;
225 
226     /// parse a string as a syn::Meta instance.
pm(tokens: TokenStream) -> ::std::result::Result<syn::Meta, String>227     fn pm(tokens: TokenStream) -> ::std::result::Result<syn::Meta, String> {
228         let attribute: syn::Attribute = parse_quote!(#[#tokens]);
229         Ok(attribute.meta)
230     }
231 
fm<T: FromMeta>(tokens: TokenStream) -> T232     fn fm<T: FromMeta>(tokens: TokenStream) -> T {
233         FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input"))
234             .expect("Tests should pass valid input")
235     }
236 
237     #[test]
supports_any()238     fn supports_any() {
239         let decl = fm::<DeriveInputShapeSet>(quote!(ignore(any)));
240         assert!(decl.any);
241     }
242 
243     #[test]
supports_struct()244     fn supports_struct() {
245         let decl = fm::<DeriveInputShapeSet>(quote!(ignore(struct_any, struct_newtype)));
246         assert!(decl.struct_values.any);
247         assert!(decl.struct_values.newtype);
248     }
249 
250     #[test]
supports_mixed()251     fn supports_mixed() {
252         let decl =
253             fm::<DeriveInputShapeSet>(quote!(ignore(struct_newtype, enum_newtype, enum_tuple)));
254         assert!(decl.struct_values.newtype);
255         assert!(decl.enum_values.newtype);
256         assert!(decl.enum_values.tuple);
257         assert!(!decl.struct_values.any);
258     }
259 }
260