async-graphql-derive 7.2.1

Macros for async-graphql
Documentation
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::{Expr, Lit, Result};

#[derive(Clone)]
pub enum Number {
    F64(f64),
    I64(i64),
}

impl FromMeta for Number {
    fn from_value(value: &Lit) -> darling::Result<Self> {
        match value {
            Lit::Int(n) => Ok(Number::I64(n.base10_parse::<i64>()?)),
            Lit::Float(n) => Ok(Number::F64(n.base10_parse::<f64>()?)),
            _ => Err(darling::Error::unexpected_type("number")),
        }
    }
}

impl ToTokens for Number {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Number::F64(n) => tokens.extend(quote!(#n as f64)),
            Number::I64(n) => tokens.extend(quote!(#n as i64)),
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum UuidVersionValidation {
    None,
    Value(Lit),
}

impl FromMeta for UuidVersionValidation {
    fn from_word() -> darling::Result<Self> {
        Ok(UuidVersionValidation::None)
    }

    fn from_value(value: &Lit) -> darling::Result<Self> {
        Ok(UuidVersionValidation::Value(value.clone()))
    }
}

#[derive(FromMeta, Default, Clone)]
pub struct Validators {
    #[darling(default)]
    multiple_of: Option<Number>,
    #[darling(default)]
    min_password_strength: Option<u8>,
    #[darling(default)]
    maximum: Option<Number>,
    #[darling(default)]
    minimum: Option<Number>,
    #[darling(default)]
    max_length: Option<usize>,
    #[darling(default)]
    min_length: Option<usize>,
    #[darling(default)]
    max_items: Option<usize>,
    #[darling(default)]
    min_items: Option<usize>,
    #[darling(default)]
    chars_max_length: Option<usize>,
    #[darling(default)]
    chars_min_length: Option<usize>,
    #[darling(default)]
    email: bool,
    #[darling(default)]
    url: bool,
    #[darling(default)]
    ip: bool,
    #[darling(default)]
    regex: Option<String>,
    #[darling(default)]
    uuid: Option<UuidVersionValidation>,
    #[darling(default, multiple)]
    custom: Vec<Expr>,
    #[darling(default)]
    list: bool,
}

impl Validators {
    pub fn create_validators(
        &self,
        crate_name: &syn::Path,
        value: TokenStream,
        map_err: Option<TokenStream>,
    ) -> Result<TokenStream> {
        let mut list_validators = Vec::new();
        let mut elem_validators = Vec::new();
        let mut codes = Vec::new();

        if let Some(n) = &self.max_items {
            list_validators.push(quote! {
                #crate_name::validators::max_items(__raw_value, #n)
            });
        }

        if let Some(n) = &self.min_items {
            list_validators.push(quote! {
                #crate_name::validators::min_items(__raw_value, #n)
            });
        }

        if let Some(n) = &self.multiple_of {
            elem_validators.push(quote! {
                #crate_name::validators::multiple_of(__raw_value, #n)
            });
        }

        if let Some(n) = &self.min_password_strength {
            elem_validators.push(quote! {
                #crate_name::validators::min_password_strength(__raw_value, #n)
            });
        }

        if let Some(n) = &self.maximum {
            elem_validators.push(quote! {
                #crate_name::validators::maximum(__raw_value, #n)
            });
        }

        if let Some(n) = &self.minimum {
            elem_validators.push(quote! {
                #crate_name::validators::minimum(__raw_value, #n)
            });
        }

        if let Some(n) = &self.max_length {
            elem_validators.push(quote! {
                #crate_name::validators::max_length(__raw_value, #n)
            });
        }

        if let Some(n) = &self.min_length {
            elem_validators.push(quote! {
                #crate_name::validators::min_length(__raw_value, #n)
            });
        }

        if let Some(n) = &self.chars_max_length {
            elem_validators.push(quote! {
                #crate_name::validators::chars_max_length(__raw_value, #n)
            });
        }

        if let Some(n) = &self.chars_min_length {
            elem_validators.push(quote! {
                #crate_name::validators::chars_min_length(__raw_value, #n)
            });
        }

        if self.email {
            elem_validators.push(quote! {
                #crate_name::validators::email(__raw_value)
            });
        }

        if self.url {
            elem_validators.push(quote! {
                #crate_name::validators::url(__raw_value)
            });
        }

        if self.ip {
            elem_validators.push(quote! {
                #crate_name::validators::ip(__raw_value)
            });
        }

        if let Some(re) = &self.regex {
            elem_validators.push(quote! {
                #crate_name::validators::regex(__raw_value, #re)
            });
        }

        if let Some(version_validation) = &self.uuid {
            match version_validation {
                UuidVersionValidation::None => {
                    elem_validators.push(quote! {
                        #crate_name::validators::uuid(__raw_value, None)
                    });
                }
                UuidVersionValidation::Value(version) => {
                    elem_validators.push(quote! {
                        #crate_name::validators::uuid(__raw_value, Some(#version))
                    });
                }
            }
        }

        if !list_validators.is_empty() {
            codes.push(quote! {
                if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(#value) {
                    #(#list_validators #map_err ?;)*
                }
            });
        }

        if !elem_validators.is_empty() {
            if self.list {
                codes.push(quote! {
                    if let ::std::option::Option::Some(value) = #crate_name::InputType::as_raw_value(#value) {
                        for __item in value {
                            if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(__item) {
                                #(#elem_validators #map_err ?;)*
                            }
                        }
                    }
                });
            } else {
                codes.push(quote! {
                    if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(#value) {
                        #(#elem_validators #map_err ?;)*
                    }
                });
            }
        }

        for expr in &self.custom {
            if self.list {
                codes.push(quote! {
                    if let ::std::option::Option::Some(value) = #crate_name::InputType::as_raw_value(#value) {
                        for __item in value {
                            if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(__item) {
                                #crate_name::CustomValidator::check(&(#expr), __raw_value) #map_err ?;
                            }
                        }
                    }
                });
            } else {
                codes.push(quote! {
                    if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(#value) {
                        #crate_name::CustomValidator::check(&(#expr), __raw_value) #map_err ?;
                    }
                });
            }
        }

        if codes.is_empty() {
            return Ok(quote!());
        }

        Ok(quote!(#(#codes)*))
    }
}