1#![recursion_limit = "128"]
6
7extern crate proc_macro;
8
9use proc_macro::TokenStream;
10use syn::DeriveInput;
11
12#[proc_macro_derive(Validate, attributes(gltf))]
13pub fn derive_validate(input: TokenStream) -> TokenStream {
14 expand(&syn::parse_macro_input!(input as DeriveInput)).into()
15}
16
17struct ValidateHook(pub syn::Ident);
18
19impl syn::parse::Parse for ValidateHook {
20 fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result<Self> {
21 let tag = input.parse::<syn::Ident>()?;
22 if tag == "validate_hook" {
23 let _eq = input.parse::<syn::Token![=]>()?;
24 let literal = input.parse::<syn::LitStr>()?;
25 let ident = syn::Ident::new(&literal.value(), tag.span());
26 Ok(ValidateHook(ident))
27 } else {
28 panic!("unrecognized gltf attribute");
29 }
30 }
31}
32
33fn expand(ast: &DeriveInput) -> proc_macro2::TokenStream {
34 use proc_macro2::TokenStream;
35 use quote::quote;
36
37 let mut validate_hook = quote! {};
38 for attr in &ast.attrs {
39 if attr.path().is_ident("gltf") {
40 let ValidateHook(ident) = attr
41 .parse_args::<ValidateHook>()
42 .expect("failed to parse attribute");
43 validate_hook = quote! {
44 #ident(self, _root, _path, _report);
45 };
46 }
47 }
48
49 let fields = match ast.data {
50 syn::Data::Struct(ref data_struct) => &data_struct.fields,
51 _ => panic!("#[derive(Validate)] only works on `struct`s"),
52 };
53 let ident = &ast.ident;
54 let validations: Vec<TokenStream> = fields
55 .iter()
56 .map(|f| f.ident.as_ref().unwrap())
57 .map(|ident| {
58 use inflections::Inflect;
59 let field = ident.to_string().to_camel_case();
60 quote!(
61 self.#ident.validate(
62 _root,
63 || _path().field(#field),
64 _report,
65 )
66 )
67 })
68 .collect();
69 let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
70 quote!(
71 impl #impl_generics crate::validation::Validate
72 for #ident #ty_generics #where_clause
73 {
74 fn validate<P, R>(
75 &self,
76 _root: &crate::Root,
77 _path: P,
78 _report: &mut R
79 ) where
80 P: Fn() -> crate::Path,
81 R: FnMut(&dyn Fn() -> crate::Path, crate::validation::Error),
82 {
83 #(
84 #validations;
85 )*
86
87 #validate_hook
88 }
89 }
90 )
91}