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