carapax-codegen 0.2.0

Procedural macroses for carapax
Documentation
use proc_macro2::Span;
use syn::{
    parse::{Parse, ParseStream},
    FnArg, GenericArgument, Ident, ItemFn, Path, PathArguments, PathSegment, Result as SynResult, ReturnType, Type,
    Visibility,
};

pub(crate) struct DialogueMeta {
    pub(super) vis: Visibility,
    pub(super) ident: Ident,
    pub(super) ident_inner: Ident,
    pub(super) handler: ItemFn,
    pub(super) state: Path,
    pub(super) context: Box<Type>,
    pub(super) input: Box<Type>,
    pub(super) error: Type,
}

impl Parse for DialogueMeta {
    fn parse(input: ParseStream) -> SynResult<Self> {
        let mut handler = input.parse::<ItemFn>()?;
        let vis = handler.vis.clone();
        let ident = handler.sig.ident.clone();
        let ident_inner = Ident::new(&format!("__carapax_{}", ident), Span::call_site());
        handler.sig.ident = ident_inner.clone();

        if handler.sig.asyncness.is_none() {
            return Err(input.error("function must be async"));
        }
        let (state_type, context_type, input_type) = {
            let mut inputs = handler.sig.inputs.iter();
            let state_type = match inputs.next() {
                Some(FnArg::Typed(typed)) => match &*typed.ty {
                    Type::Path(type_path) => type_path.path.clone(),
                    _ => return Err(input.error("state must be a path")),
                },
                _ => return Err(input.error("unable to detect dialogue state")),
            };
            let context_type = match inputs.next() {
                Some(FnArg::Typed(typed)) => match &*typed.ty {
                    Type::Reference(reference) => reference.elem.clone(),
                    _ => return Err(input.error("context must be a reference")),
                },
                _ => return Err(input.error("unable to detect handler context")),
            };
            let input_type = match inputs.next() {
                Some(FnArg::Typed(arg)) => arg.ty.clone(),
                _ => return Err(input.error("unable to detect handler input")),
            };
            (state_type, context_type, input_type)
        };
        let mut error_type = None;
        if let ReturnType::Type(_, ref path) = handler.sig.output {
            if let Type::Path(type_path) = &**path {
                if let Some(PathSegment {
                    arguments: PathArguments::AngleBracketed(bracketed),
                    ..
                }) = type_path.path.segments.last()
                {
                    if let Some(GenericArgument::Type(ty)) = bracketed.args.last() {
                        error_type = Some(ty.clone());
                    }
                }
            }
        }
        let error_type = error_type.ok_or_else(|| input.error("Unable to detect handler error type"))?;
        Ok(DialogueMeta {
            vis,
            ident,
            ident_inner,
            handler,
            state: state_type,
            context: context_type,
            input: input_type,
            error: error_type,
        })
    }
}