gltf_derive/
lib.rs

1// Adapted from `validator_derive` (https://github.com/Keats/validator).
2//
3// See LICENSE for details.
4
5#![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}