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