type-rules-derive 0.2.3

type-rules derive macro
Documentation
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::Token;

pub enum Data {
    Struct(Struct),
    Enum(Vec<Variant>),
}

pub struct Struct {
    fields: Vec<Field>,
    style: Style,
}

pub struct Field {
    member: syn::Member,
    rules: Vec<syn::Expr>,
}

pub enum Style {
    Struct,
    Tuple,
    Unit,
}

pub struct Variant {
    ident: syn::Ident,
    def: Struct,
}

struct RuleAttribute {
    expr: Punctuated<syn::Expr, Token![,]>,
}

pub fn from_ast(data: &syn::Data) -> Data {
    match data {
        syn::Data::Enum(data) => Data::Enum(enum_from_ast(&data.variants)),
        syn::Data::Struct(data) => Data::Struct(struct_from_ast(&data.fields)),
        syn::Data::Union(_) => panic!("Validator derive is not implemented for Union"),
    }
}

fn enum_from_ast(variants: &Punctuated<syn::Variant, Token![,]>) -> Vec<Variant> {
    variants
        .iter()
        .map(|var| Variant {
            ident: var.ident.clone(),
            def: struct_from_ast(&var.fields),
        })
        .collect()
}

fn struct_from_ast(fields: &syn::Fields) -> Struct {
    match fields {
        syn::Fields::Named(fields) => Struct {
            fields: fields_from_ast(&fields.named),
            style: Style::Struct,
        },
        syn::Fields::Unnamed(fields) => Struct {
            fields: fields_from_ast(&fields.unnamed),
            style: Style::Tuple,
        },
        syn::Fields::Unit => Struct {
            fields: Vec::new(),
            style: Style::Unit,
        },
    }
}

fn fields_from_ast(fields: &Punctuated<syn::Field, Token![,]>) -> Vec<Field> {
    fields
        .iter()
        .enumerate()
        .map(|(i, field)| Field {
            member: match &field.ident {
                Some(ident) => syn::Member::Named(ident.clone()),
                None => syn::Member::Unnamed(i.into()),
            },
            rules: RuleAttribute::parse_attributes(&field.attrs),
        })
        .collect()
}

impl Parse for RuleAttribute {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Self {
            expr: input.parse_terminated(syn::Expr::parse)?,
        })
    }
}

impl RuleAttribute {
    pub fn parse_attributes(attrs: &[syn::Attribute]) -> Vec<syn::Expr> {
        attrs
            .iter()
            .filter(|attr| attr.path.is_ident("rule"))
            .flat_map(Self::parse_attribute)
            .collect()
    }

    fn parse_attribute(attr: &syn::Attribute) -> Vec<syn::Expr> {
        attr.parse_args::<Self>()
            .unwrap()
            .expr
            .into_iter()
            .collect()
    }
}

impl Data {
    pub fn body(&self, ident: &syn::Ident) -> TokenStream {
        match self {
            Data::Enum(variants) => Self::enum_body(variants, ident),
            Data::Struct(data) => Self::struct_body(data),
        }
    }

    fn enum_body(variants: &[Variant], ident: &syn::Ident) -> TokenStream {
        let variants_arms = variants.iter().map(|variant| variant.match_arm(ident));
        quote! {
            match self {
                #( #variants_arms ),*
            }
        }
    }

    fn struct_body(data: &Struct) -> TokenStream {
        let fields_rules = data.fields.iter().map(Field::rules);
        quote! {
            #( #fields_rules )*
            Ok(())
        }
    }
}

impl Variant {
    fn match_arm(&self, data_ident: &syn::Ident) -> TokenStream {
        let case = self.match_arm_case(data_ident);
        let fields_rules = self.def.fields.iter().map(Field::rules_named);
        quote! {
            #case => {
                #( #fields_rules )*
                Ok(())
            }
        }
    }

    fn match_arm_case(&self, data_ident: &syn::Ident) -> TokenStream {
        let ident = &self.ident;
        let fields_names = self.def.fields.iter().map(Field::get_named_ident);
        match self.def.style {
            Style::Struct => quote! {
                #data_ident::#ident{#( #fields_names ),*}
            },
            Style::Tuple => quote! {
                #data_ident::#ident(#( #fields_names ),*)
            },
            Style::Unit => quote! {
                #data_ident::#ident
            },
        }
    }
}

impl Field {
    fn rules(&self) -> TokenStream {
        let Self { rules, member } = &self;
        quote! {
            #( type_rules::Rule::check(&#rules, &self.#member)?; )*
        }
    }

    fn rules_named(&self) -> TokenStream {
        let ident = self.get_named_ident();
        let rules = &self.rules;
        quote! {
            #( type_rules::Rule::check(&#rules, #ident)?; )*
        }
    }

    fn get_named_ident(&self) -> syn::Ident {
        match &self.member {
            syn::Member::Named(ident) => ident.clone(),
            syn::Member::Unnamed(index) => format_ident!("__field{}", index),
        }
    }
}