aragog-macros 0.8.0

Macros for Aragog Crate
Documentation
use crate::parse_operation::{OperationValue, ParseOperation};
use crate::toolbox::{expect_str_lit, expect_usize_lit, get_ident};
use proc_macro2::{Span, TokenStream};
use std::fmt::{self, Display, Formatter};
use syn::{spanned::Spanned, Ident, Path};

#[allow(dead_code)]
#[derive(Clone)]
pub enum Operation {
    MinLength {
        value: usize,
        field: String,
    },
    MaxLength {
        value: usize,
        field: String,
    },
    Length {
        value: usize,
        field: String,
    },
    MinCount {
        value: usize,
        field: String,
    },
    MaxCount {
        value: usize,
        field: String,
    },
    Count {
        value: usize,
        field: String,
    },
    Regex {
        value: OperationValue,
        field: String,
    },
    GreaterThan {
        value: OperationValue,
        field: String,
    },
    LesserThan {
        value: OperationValue,
        field: String,
    },
    GreaterOrEqual {
        value: OperationValue,
        field: String,
    },
    LesserOrEqual {
        value: OperationValue,
        field: String,
    },
    Function {
        func: String,
        field: Option<String>,
    },
    CallValidations {
        field: String,
    },
    IsSome {
        field: String,
    },
    IsNone {
        field: String,
    },
}

impl ParseOperation for Operation {
    fn parse(path: &Path, value: Option<OperationValue>, field: Option<String>) -> Option<Self> {
        let str = get_ident(path)?;
        let res = match str.as_str() {
            "min_length" => {
                let value = expect_usize_lit(&Self::expect_literal_value(path, value)?)?;
                let field = Self::expect_field(path, field)?;
                Self::MinLength { value, field }
            }
            "max_length" => {
                let value = expect_usize_lit(&Self::expect_literal_value(path, value)?)?;
                let field = Self::expect_field(path, field)?;
                Self::MaxLength { value, field }
            }
            "length" => {
                let value = expect_usize_lit(&Self::expect_literal_value(path, value)?)?;
                let field = Self::expect_field(path, field)?;
                Self::Length { value, field }
            }
            "min_count" => {
                let value = expect_usize_lit(&Self::expect_literal_value(path, value)?)?;
                let field = Self::expect_field(path, field)?;
                Self::MinCount { value, field }
            }
            "max_count" => {
                let value = expect_usize_lit(&Self::expect_literal_value(path, value)?)?;
                let field = Self::expect_field(path, field)?;
                Self::MaxCount { value, field }
            }
            "count" => {
                let value = expect_usize_lit(&Self::expect_literal_value(path, value)?)?;
                let field = Self::expect_field(path, field)?;
                Self::Count { value, field }
            }
            "regex" => {
                let value = Self::expect_value(path, value)?;
                let field = Self::expect_field(path, field)?;
                Self::Regex { value, field }
            }
            "greater_than" => {
                let value = Self::expect_value(path, value)?;
                let field = Self::expect_field(path, field)?;
                Self::GreaterThan { value, field }
            }
            "lesser_than" => {
                let value = Self::expect_value(path, value)?;
                let field = Self::expect_field(path, field)?;
                Self::LesserThan { value, field }
            }
            "greater_or_equal" => {
                let value = Self::expect_value(path, value)?;
                let field = Self::expect_field(path, field)?;
                Self::GreaterOrEqual { value, field }
            }
            "lesser_or_equal" => {
                let value = Self::expect_value(path, value)?;
                let field = Self::expect_field(path, field)?;
                Self::LesserOrEqual { value, field }
            }
            "call_validations" => {
                Self::expect_no_value(value)?;
                let field = Self::expect_field(path, field)?;
                Self::CallValidations { field }
            }
            "is_some" => {
                Self::expect_no_value(value)?;
                let field = Self::expect_field(path, field)?;
                Self::IsSome { field }
            }
            "is_none" => {
                Self::expect_no_value(value)?;
                let field = Self::expect_field(path, field)?;
                Self::IsNone { field }
            }
            "func" => {
                let lit = Self::expect_literal_value(path, value)?;
                let func = expect_str_lit(&lit)?;
                if func == "validations" {
                    emit_error!(
                        lit.span(),
                        "Please use a different method name than `validations` \
                        as it may lead to unexpected behavior"
                    );
                    return None;
                }
                Self::Function { func, field }
            }
            _ => {
                emit_error!(
                    path.span(),
                    "Can't find a valid operation for validation attribute"
                );
                return None;
            }
        };
        Some(res)
    }
}

impl Display for Operation {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Operation::MinLength { .. } => "min_length",
                Operation::MaxLength { .. } => "max_length",
                Operation::Length { .. } => "length",
                Operation::MinCount { .. } => "min_count",
                Operation::MaxCount { .. } => "max_count",
                Operation::Count { .. } => "count",
                Operation::Regex { .. } => "regex",
                Operation::GreaterThan { .. } => "greater_than",
                Operation::LesserThan { .. } => "lesser_than",
                Operation::GreaterOrEqual { .. } => "greater_or_equal",
                Operation::LesserOrEqual { .. } => "lesser_or_equal",
                Operation::Function { .. } => "func",
                Operation::CallValidations { .. } => "call_validations",
                Operation::IsSome { .. } => "is_some",
                Operation::IsNone { .. } => "is_none",
            }
        )
    }
}

impl Operation {
    //noinspection RsTypeCheck
    fn field_token(
        field: &str,
        custom_token_stream: Option<TokenStream>,
        no_ref: bool,
    ) -> TokenStream {
        match custom_token_stream {
            None => {
                let field_ident = Ident::new(field, Span::call_site());
                let res = quote! { self.#field_ident };
                res
            }
            Some(token) => {
                if no_ref {
                    quote! { *#token }
                } else {
                    token
                }
            }
        }
    }

    //noinspection RsTypeCheck
    pub(crate) fn token_stream(self, custom_token: Option<TokenStream>) -> TokenStream {
        match self {
            Self::MinLength { value, field } => {
                let field_token = Self::field_token(&field, custom_token, false);
                quote! {
                    Self::validate_min_len(#field, &#field_token, #value, errors);
                }
            }
            Self::MaxLength { value, field } => {
                let field_token = Self::field_token(&field, custom_token, false);
                quote! {
                    Self::validate_max_len(#field, &#field_token, #value, errors);
                }
            }
            Self::Length { value, field } => {
                let field_token = Self::field_token(&field, custom_token, false);
                quote! {
                    Self::validate_len(#field, &#field_token, #value, errors);
                }
            }
            Self::MinCount { value, field } => {
                let field_token = Self::field_token(&field, custom_token, false);
                quote! {
                    Self::validate_min_count(#field, #field_token.iter(), #value, errors);
                }
            }
            Self::MaxCount { value, field } => {
                let field_token = Self::field_token(&field, custom_token, false);
                quote! {
                    Self::validate_max_count(#field, #field_token.iter(), #value, errors);
                }
            }
            Self::Count { value, field } => {
                let field_token = Self::field_token(&field, custom_token, false);
                quote! {
                    Self::validate_count(#field, #field_token.iter(), #value, errors);
                }
            }
            Self::Regex { value, field } => {
                let field_token = Self::field_token(&field, custom_token, false);
                quote! {
                    Self::validate_regex(#field, &#field_token, #value, errors);
                }
            }
            Self::GreaterThan { value, field } => {
                let field_token = Self::field_token(&field, custom_token, true);
                quote! {
                    Self::validate_greater_than(#field, #field_token, #value, errors);
                }
            }
            Self::LesserThan { value, field } => {
                let field_token = Self::field_token(&field, custom_token, true);
                quote! {
                    Self::validate_lesser_than(#field, #field_token, #value, errors);
                }
            }
            Self::GreaterOrEqual { value, field } => {
                let field_token = Self::field_token(&field, custom_token, true);
                quote! {
                    Self::validate_greater_or_equal_to(#field, #field_token, #value, errors);
                }
            }
            Self::LesserOrEqual { value, field } => {
                let field_token = Self::field_token(&field, custom_token, true);
                quote! {
                    Self::validate_lesser_or_equal_to(#field, #field_token, #value, errors);
                }
            }
            Self::CallValidations { field } => {
                let field_token = Self::field_token(&field, custom_token, false);
                quote! {
                    #field_token.validations(errors);
                }
            }
            Self::IsSome { field } => {
                let field_token = Self::field_token(&field, custom_token, false);
                quote! {
                    Self::validate_field_presence(#field, &#field_token, errors);
                }
            }
            Self::IsNone { field } => {
                let field_token = Self::field_token(&field, custom_token, false);
                quote! {
                    Self::validate_field_absence(#field, &#field_token, errors);
                }
            }
            Self::Function { func, field } => {
                let func_ident = Ident::new(&func, Span::call_site());
                field.map_or(
                    quote! {
                        self.#func_ident(errors);
                    },
                    |field| {
                        let field_token = Self::field_token(&field, custom_token, false);
                        quote! {
                            Self::#func_ident(#field, &#field_token, errors);
                        }
                    },
                )
            }
        }
    }
}