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::Word(ref ident)) = *item {
44 let word = ident.to_string();
45 let word = word.as_str();
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::Word(ref ident)) = *item {
170 if let Err(e) = new.set_word(&ident.to_string()) {
171 errors.push(e.with_span(ident));
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