validators-derive 0.26.0

The provided crate offers a procedural macro for defining validators, including optional parameters.
use quote::quote;
use regex::Regex;
use syn::{Expr, ExprLit, Lit, Meta, Token, punctuated::Punctuated};

use crate::{
    common::{rocket_options::RocketOptions, serde_options::SerdeOptions},
    panic,
};

pub(crate) struct RegexAttribute {
    pub(crate) regex:          Expr,
    #[cfg_attr(not(feature = "serde"), allow(dead_code))]
    pub(crate) serde_options:  SerdeOptions,
    #[cfg_attr(not(feature = "rocket"), allow(dead_code))]
    pub(crate) rocket_options: RocketOptions,
}

impl RegexAttribute {
    pub(crate) fn build_from_meta(meta: &Meta) -> syn::Result<Self> {
        let correct_parameters = ["regex", "serde", "rocket"];

        let mut regex = None;
        let mut serde_options = SerdeOptions::default();
        let mut rocket_options = RocketOptions::default();

        match meta {
            Meta::Path(_) => (),
            Meta::NameValue(_) => {
                return Err(panic::attribute_incorrect_format(meta.path()));
            },
            Meta::List(list) => {
                let result =
                    list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;

                let mut regex_is_set = false;
                let mut serde_options_is_set = false;
                let mut rocket_options_is_set = false;

                let mut handler = |meta: &Meta| -> syn::Result<bool> {
                    if let Some(ident) = meta.path().get_ident() {
                        match ident.to_string().as_str() {
                            "regex" => {
                                let v = meta_2_regex_expr(meta)?;

                                if regex_is_set {
                                    return Err(panic::parameter_reset(ident));
                                }

                                regex_is_set = true;

                                regex = Some(v);

                                return Ok(true);
                            },
                            "serde" => {
                                let v = SerdeOptions::from_meta(meta)?;

                                if serde_options_is_set {
                                    return Err(panic::parameter_reset(ident));
                                }

                                serde_options_is_set = true;

                                serde_options = v;

                                return Ok(true);
                            },
                            "rocket" => {
                                let v = RocketOptions::from_meta(meta)?;

                                if rocket_options_is_set {
                                    return Err(panic::parameter_reset(ident));
                                }

                                rocket_options_is_set = true;

                                rocket_options = v;

                                return Ok(true);
                            },
                            _ => (),
                        }
                    }

                    Ok(false)
                };

                for p in result {
                    if !handler(&p)? {
                        return Err(panic::parameter_incorrect_format(
                            p.path(),
                            &correct_parameters,
                        ));
                    }
                }
            },
        }

        if let Some(regex) = regex {
            Ok(Self {
                regex,
                serde_options,
                rocket_options,
            })
        } else {
            Err(syn::Error::new_spanned(meta.path(), "the `regex` parameter is not set"))
        }
    }
}

fn expr_lit_2_regex_expr(lit: &ExprLit) -> syn::Result<Expr> {
    if let Lit::Str(lit) = &lit.lit {
        let s = lit.value();

        if let Err(error) = Regex::new(s.as_str()) {
            return Err(syn::Error::new_spanned(lit, error));
        }

        return Ok(
            syn::parse2(quote! ( validators_prelude::regex::Regex::new(#s).unwrap() )).unwrap()
        );
    }

    Err(syn::Error::new_spanned(lit, "expected `\"regex\"`"))
}

fn meta_2_regex_expr(meta: &Meta) -> syn::Result<Expr> {
    match meta {
        Meta::NameValue(name_value) => {
            return if let Expr::Lit(lit) = &name_value.value {
                expr_lit_2_regex_expr(lit)
            } else {
                Ok(name_value.value.clone())
            };
        },
        Meta::List(list) => {
            let expr: Expr = list.parse_args()?;

            return if let Expr::Lit(lit) = &expr { expr_lit_2_regex_expr(lit) } else { Ok(expr) };
        },
        Meta::Path(_) => (),
    }

    let path = meta.path();

    Err(syn::Error::new_spanned(path, "expected `\"regex\"` or an expr"))
}