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, NestedMeta};
7
8use 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)]
18pub struct DeriveInputShapeSet {
19 enum_values: DataShape,
20 struct_values: DataShape,
21 any: bool,
22}
23
24impl 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
34impl 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
63impl 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)]
118pub 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
128impl 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
163impl 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
180impl 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)]
217mod 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