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
4use proc_macro2::TokenStream;
5use quote::{quote, ToTokens, TokenStreamExt};
6use syn::{parse_quote, Meta};
7
8use crate::ast::NestedMeta;
9use 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)]
19pub struct DeriveInputShapeSet {
20 enum_values: DataShape,
21 struct_values: DataShape,
22 any: bool,
23}
24
25impl Default for DeriveInputShapeSet {
26 fn default() -> Self {
27 DeriveInputShapeSet {
28 enum_values: DataShape::new(prefix:"enum_"),
29 struct_values: DataShape::new(prefix:"struct_"),
30 any: Default::default(),
31 }
32 }
33}
34
35impl FromMeta for DeriveInputShapeSet {
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
64impl ToTokens for DeriveInputShapeSet {
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)]
119pub 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
129impl DataShape {
130 fn new(prefix: &'static str) -> Self {
131 DataShape {
132 prefix,
133 ..Default::default()
134 }
135 }
136
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
164impl FromMeta for DataShape {
165 fn from_list(items: &[NestedMeta]) -> Result<Self> {
166 let mut errors: Accumulator = Error::accumulator();
167 let mut new: DataShape = DataShape::default();
168
169 for item: &NestedMeta in items {
170 if let NestedMeta::Meta(Meta::Path(ref path: &Path)) = *item {
171 errors.handle(result:new.set_word(&path.segments.first().unwrap().ident.to_string()));
172 } else {
173 errors.push(error:Error::unsupported_format("non-word").with_span(node:item));
174 }
175 }
176
177 errors.finish_with(success:new)
178 }
179}
180
181impl ToTokens for DataShape {
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)]
218mod 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.
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
232 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]
238 fn supports_any() {
239 let decl = fm::<DeriveInputShapeSet>(quote!(ignore(any)));
240 assert!(decl.any);
241 }
242
243 #[test]
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]
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