range_checker_macro 0.2.2

derive-macro crate aimed to provide declarative bounds checking and filtering for structure.
Documentation
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{DeriveInput, Result};

use super::extract_attributes;

pub fn derive_range_checker(input: DeriveInput) -> Result<TokenStream> {
    let DeriveInput { ident, .. } = input;

    let mut check_list = vec![];
    let mut ident_list = vec![];
    let mut fallback_list = vec![];

    if let syn::Data::Struct(syn::DataStruct { fields, .. }) = input.data {
        for field in fields {
            let ident_item = &field.ident;

            let (range_attrs, filter_attrs, fallback_lit_attrs, fallback_closure_attrs) =
                extract_attributes(field.attrs)?;

            let mut check_statement = TokenStream::default();

            let mut range_attrs = range_attrs.iter();
            if let Some(range_first) = range_attrs.next() {
                check_statement = quote! {(#range_first).contains(&self.#ident_item)};

                for range in range_attrs {
                    check_statement.extend(quote! {|| (#range).contains(&self.#ident_item)});
                }
            }

            let mut filter_attrs = filter_attrs.iter();
            if check_statement.is_empty() {
                if let Some(filter_first) = filter_attrs.next() {
                    check_statement = quote! {(#filter_first)(&self.#ident_item)};
                };
            }
            for filter in filter_attrs {
                check_statement.extend(quote! {&& (#filter)(&self.#ident_item)})
            }

            if !check_statement.is_empty() {
                if field.ident.is_none() {
                    return Err(syn::Error::new(
                        Span::call_site(),
                        "currently only named field are supported by this derive",
                    ));
                }
                let fallback_closure = fallback_closure_attrs
                    .first()
                    .map(|closure| {
                        Some(quote! {
                            self.#ident_item = (#closure)(&self.#ident_item);
                        })
                    })
                    .unwrap_or(fallback_lit_attrs.first().map(|lit| {
                        quote! {
                            self.#ident_item = #lit;
                        }
                    }))
                    .unwrap_or(quote! {
                        return Err(());
                    });

                check_list.push(check_statement);
                ident_list.push(ident_item.clone());
                fallback_list.push(fallback_closure);
            }
        }
    } else {
        return Err(syn::Error::new(
            Span::call_site(),
            "currently only structs are supported by this derive",
        ));
    }

    Ok(quote!(
        impl ::range_checker::Check for #ident {
            fn check(&self) -> Result<(), ()> {
                #(
                    if !(#check_list) {
                        return Err(());
                    }
                )*

                Ok(())
            }

            fn check_with_fallback(&mut self) -> Result<(), ()> {
                #(
                    if !(#check_list) {
                        #fallback_list
                    }
                )*

                Ok(())
            }
        }
    ))
}