serde_valid_derive 0.16.0-alpha

JSON Schema based validation tool using with serde.
Documentation
use crate::types::{CommaSeparatedNestedMetas, SingleIdentPath};
use proc_macro2::TokenStream;
use quote::quote;
use std::str::FromStr;

use super::{get_str, MetaListMessage, MetaNameValueMessage, MetaPathMessage};

#[derive(Debug, Default)]
pub struct CustomMessageToken {
    pub message_fn: Option<TokenStream>,
    #[cfg(feature = "fluent")]
    pub fluent_message: Option<TokenStream>,
}

impl CustomMessageToken {
    pub fn new_message_fn(message_fn: TokenStream) -> Self {
        Self {
            message_fn: Some(message_fn),
            #[cfg(feature = "fluent")]
            fluent_message: None,
        }
    }

    #[cfg(feature = "fluent")]
    pub fn new_fluent_message(fluent_message: TokenStream) -> Self {
        Self {
            message_fn: None,
            fluent_message: Some(fluent_message),
        }
    }

    pub fn into_token(self) -> TokenStream {
        let message_fn = self.message_fn.unwrap_or(quote!(
            ::serde_valid::validation::ToDefaultMessage::to_default_message
        ));

        #[cfg(feature = "fluent")]
        let fluent_message = quote!(fluent_message: None,);
        #[cfg(not(feature = "fluent"))]
        let fluent_message = quote!();

        quote!(
            ::serde_valid::validation::CustomMessage{
                message_fn: #message_fn,
                #fluent_message
            }
        )
    }
}

pub fn extract_custom_message_tokens(
    nested_meta: &syn::NestedMeta,
) -> Result<CustomMessageToken, crate::Errors> {
    match nested_meta {
        syn::NestedMeta::Meta(meta) => match meta {
            syn::Meta::List(message_fn_list) => {
                extract_custom_message_tokens_from_meta_list(message_fn_list)
            }
            syn::Meta::NameValue(name_value) => {
                extract_custom_message_tokens_from_name_value(name_value)
            }
            syn::Meta::Path(path) => {
                let path_label = SingleIdentPath::new(path).ident().to_string();
                if MetaNameValueMessage::from_str(&path_label).is_ok() {
                    Err(crate::Error::validate_meta_name_value_need_value(
                        path,
                        &path_label,
                    ))
                } else if MetaListMessage::from_str(&path_label).is_ok() {
                    Err(crate::Error::validate_meta_list_need_value(
                        path,
                        &path_label,
                    ))
                } else {
                    Err(crate::Error::validate_unknown_type(
                        path,
                        &path_label,
                        &(MetaNameValueMessage::iter().map(|x| x.name()))
                            .chain(MetaListMessage::iter().map(|x| x.name()))
                            .chain(MetaPathMessage::iter().map(|x| x.name()))
                            .collect::<Vec<_>>(),
                    ))
                }
            }
            .map_err(|error| vec![error]),
        },
        syn::NestedMeta::Lit(lit) => Err(vec![crate::Error::literal_not_support(lit)]),
    }
}

fn extract_custom_message_tokens_from_meta_list(
    syn::MetaList {
        path,
        nested: message_fn_define,
        ..
    }: &syn::MetaList,
) -> Result<CustomMessageToken, crate::Errors> {
    let path_ident = SingleIdentPath::new(path).ident();
    let path_label = path_ident.to_string();

    match MetaListMessage::from_str(&path_label) {
        Ok(MetaListMessage::MessageFn) => get_message_fn_from_nested_meta(path, message_fn_define)
            .map(CustomMessageToken::new_message_fn),
        #[cfg(feature = "fluent")]
        Ok(ref message_type @ (MetaListMessage::I18n | MetaListMessage::Fluent)) => {
            get_fluent_message_from_nested_meta(message_type, path, message_fn_define)
                .map(CustomMessageToken::new_fluent_message)
        }
        Err(unknown) => {
            let error = if MetaNameValueMessage::from_str(&path_label).is_ok() {
                crate::Error::validate_meta_list_need_value(path, &path_label)
            } else if MetaPathMessage::from_str(&path_label).is_ok() {
                crate::Error::validate_meta_path_need_value(path, &path_label)
            } else {
                crate::Error::validate_unknown_type(
                    path,
                    &unknown,
                    &MetaListMessage::iter()
                        .map(|x| x.name())
                        .collect::<Vec<_>>(),
                )
            };
            Err(vec![error])
        }
    }
}

fn extract_custom_message_tokens_from_name_value(
    syn::MetaNameValue { path, lit, .. }: &syn::MetaNameValue,
) -> Result<CustomMessageToken, crate::Errors> {
    let path_ident = SingleIdentPath::new(path).ident();
    let path_label = path_ident.to_string();

    match MetaNameValueMessage::from_str(&path_label) {
        Ok(MetaNameValueMessage::Message) => {
            get_message_fn_from_lit(lit).map(CustomMessageToken::new_message_fn)
        }
        Err(unknown) => if MetaListMessage::from_str(&path_label).is_ok() {
            Err(crate::Error::validate_meta_list_need_value(
                path,
                &path_label,
            ))
        } else if MetaPathMessage::from_str(&path_label).is_ok() {
            Err(crate::Error::validate_meta_path_need_value(
                path,
                &path_label,
            ))
        } else {
            Err(crate::Error::validate_unknown_type(
                path,
                &unknown,
                &MetaNameValueMessage::iter()
                    .map(|x| x.name())
                    .collect::<Vec<_>>(),
            ))
        }
        .map_err(|error| vec![error]),
    }
}

fn get_message_fn_from_nested_meta(
    path: &syn::Path,
    fn_define: &CommaSeparatedNestedMetas,
) -> Result<TokenStream, crate::Errors> {
    match fn_define.len() {
        0 => Err(vec![crate::Error::message_fn_need_item(path)]),
        1 => {
            let fn_name = match &fn_define[0] {
                syn::NestedMeta::Meta(syn::Meta::Path(fn_name)) => Some(quote!(#fn_name)),
                _ => None,
            };
            fn_name.ok_or_else(|| vec![crate::Error::message_fn_allow_name_path(&fn_define[0])])
        }
        _ => Err(fn_define
            .iter()
            .skip(1)
            .map(crate::Error::message_fn_tail_error)
            .collect()),
    }
}

fn get_message_fn_from_lit(lit: &syn::Lit) -> Result<TokenStream, crate::Errors> {
    get_str(lit).map(|lit_str| quote!(|_| { #lit_str.to_string() }))
}

#[cfg(feature = "fluent")]
fn get_fluent_message_from_nested_meta(
    message_type: &MetaListMessage,
    path: &syn::Path,
    fn_define: &CommaSeparatedNestedMetas,
) -> Result<TokenStream, crate::Errors> {
    match fn_define.len() {
        0 => Err(vec![crate::Error::fluent_need_item(message_type, path)]),
        1 => match &fn_define[0] {
            syn::NestedMeta::Lit(syn::Lit::Str(id)) => Ok(quote!(
                ::serde_valid::fluent::Message{
                    id: #id,
                    args: vec![]
                }
            )),
            _ => Err(vec![crate::Error::fluent_allow_key(
                message_type,
                &fn_define[0],
            )]),
        },
        _ => Err(fn_define
            .iter()
            .skip(1)
            .map(|args| crate::Error::fluent_allow_args(message_type, args))
            .collect()),
    }
}