i18n_error_impl 0.1.0

Implementation detail of the `i18n_error` crate.
Documentation
use proc_macro2::{Ident, TokenStream as TokenStream2, TokenTree};
use quote::ToTokens;
use syn::{Data, DeriveInput, Meta, Variant};

#[derive(Clone)]
pub(crate) enum DeriveTarget {
    Struct(ErrorOfStruct),
    Enum(ErrorOfEnum),
}

#[derive(Clone)]
pub(crate) struct ErrorOfStruct {
    pub(crate) input: DeriveInput,
    pub(crate) i18n_key: TokenStream2,
}

#[derive(Clone)]
pub(crate) struct ErrorOfEnum {
    pub(crate) ident: syn::Ident,
    pub(crate) enum_variants: Vec<EnumVariant>,
}

#[derive(Clone)]
pub(crate) struct EnumVariant {
    pub(crate) variant: Variant,
    pub(crate) i18n_key_delegate: I18nKeyDelegate,
}

#[derive(Clone)]
pub(crate) enum I18nKeyDelegate {
    I18nKey(TokenStream2),
    Delegate,
}

pub(crate) fn get_language_codes(input: &DeriveInput) -> Vec<Ident> {
    let language_codes = input
        .attrs
        .iter()
        .filter_map(|attr| match attr.meta.clone() {
            Meta::List(list) => {
                if list.path.is_ident("i18n_language_codes") {
                    let language_codes = list
                        .to_token_stream()
                        .into_iter()
                        .filter_map(|tk| match tk {
                            TokenTree::Group(ref group) => {
                                let stream = group.stream();
                                let language_codes = stream
                                    .into_iter()
                                    .filter_map(|s| match s {
                                        TokenTree::Ident(ref ident) => Some(ident.clone()),
                                        _ => None,
                                    })
                                    .collect::<Vec<_>>();
                                Some(language_codes)
                            }
                            _ => None,
                        })
                        .flatten()
                        .collect::<Vec<_>>();

                    if language_codes.is_empty() {
                        return None;
                    }

                    Some(language_codes)
                } else {
                    None
                }
            }
            _ => {
                panic!("i18n_language_codes must be a list.");
            }
        })
        .flatten()
        .collect::<Vec<_>>();

    if language_codes.is_empty() {
        panic!("i18n_language_codes must be specified.");
    }

    language_codes
}

pub(crate) fn get_derive_target(input: &DeriveInput) -> DeriveTarget {
    match &input.data {
        Data::Struct(_) => {
            let token_streams = input
                .attrs
                .iter()
                .filter_map(|attr| match attr.meta.clone() {
                    Meta::List(list) => {
                        if list.path.is_ident("i18n_key") {
                            Some(list.tokens.clone())
                        } else {
                            None
                        }
                    }
                    _ => {
                        panic!("i18n_error must be a list.");
                    }
                })
                .collect::<Vec<TokenStream2>>();

            if token_streams.len() != 1 {
                panic!("i18n_error must be specified only once as Struct attribute.");
            }
            let token_stream = token_streams.first().unwrap();

            let struct_struct = ErrorOfStruct {
                input: input.clone(),
                i18n_key: token_stream.clone(),
            };

            DeriveTarget::Struct(struct_struct)
        }
        Data::Enum(data) => {
            let enum_variants: Vec<EnumVariant> = data
                .variants
                .iter()
                .map(|variant| {
                    let mut i18n_key = None;
                    let mut i18n_delegate = false;
                    variant.attrs.iter().for_each(|attr| {
                        let meta = &attr.meta.clone();
                        match meta {
                            Meta::List(list) => {
                                if list.path.is_ident("i18n_key") {
                                    i18n_key = Some(list.tokens.clone());
                                }
                            }
                            Meta::Path(path) => {
                                if path.is_ident("i18n_delegate") {
                                    i18n_delegate = true;
                                }
                            }
                            _ => {
                                panic!("i18n_key must be a list. i18n_delegate must a path.");
                            }
                        };
                    });
                    EnumVariant {
                        variant: variant.clone(),
                        i18n_key_delegate: match (i18n_key, i18n_delegate) {
                            (Some(_), true) => {
                                panic!("Cannot specify both i18n_key and i18n_delegate.")
                            }
                            (Some(key), false) => I18nKeyDelegate::I18nKey(key),
                            (None, true) => I18nKeyDelegate::Delegate,
                            (None, false) => {
                                panic!("Either i18n_key or i18n_delegate must be specified.")
                            }
                        },
                    }
                })
                .collect();

            DeriveTarget::Enum(ErrorOfEnum {
                ident: input.ident.clone(),
                enum_variants,
            })
        }
        Data::Union(_) => panic!("I18nError is only supported for Enum."),
    }
}