zod-derive 0.1.0

Rust integraions with the `zod` typescript library.
Documentation
use darling::{ast::Data, FromDeriveInput, FromField};
use proc_macro_error::{abort, abort_call_site};
use quote::format_ident;
use syn::{parse_quote, Ident, ImplItem, ImplItemMethod, ItemImpl, Type};

#[derive(FromDeriveInput)]
pub struct BackendInput {
    pub ident: syn::Ident,
    pub data: Data<darling::util::Ignored, BackendField>,
}

#[derive(FromField, Clone)]
pub struct BackendField {
    pub ident: Option<syn::Ident>,
    pub ty: Type,
}

pub struct RpcInput {
    pub ident: syn::Ident,
    pub items: Vec<RpcItem>,
}

impl RpcInput {
    pub fn from_ast(ast: ItemImpl) -> Self {
        let self_ty = ast.self_ty;
        let ident: Ident = parse_quote!(#self_ty);
        Self {
            ident,
            items: ast
                .items
                .into_iter()
                .filter_map(|item| match item {
                    ImplItem::Method(method) => Some(method),
                    _ => None,
                })
                .map(RpcItem::from_ast)
                .collect(),
        }
    }

    pub(crate) fn req_ident(&self) -> Ident {
        format_ident!("{}Req", self.ident)
    }
}

pub struct RpcItem {
    pub ident: syn::Ident,
    pub arg_types: Vec<RpcArg>,
    pub kind: RpcItemKind,
    pub output: Box<Type>,
}

pub struct RpcArg {
    pub name: String,
    pub ty: Box<Type>,
}

impl RpcItem {
    pub fn from_ast(ast: ImplItemMethod) -> Self {
        let sig = ast.sig;
        let ident = sig.ident;
        let is_async = sig.asyncness.is_some();

        let kind = if is_async {
            RpcItemKind::Method
        } else {
            RpcItemKind::Stream
        };

        let output = match (&kind, sig.output) {
            (RpcItemKind::Method, syn::ReturnType::Default) => {
                parse_quote!(())
            }
            (RpcItemKind::Stream, syn::ReturnType::Default) => {
                abort!(
                    ident.span(),
                    "zod: namespace methods must be async or return a stream"
                )
            }
            (RpcItemKind::Method, syn::ReturnType::Type(_, t))
            | (RpcItemKind::Stream, syn::ReturnType::Type(_, t)) => t,
        };

        if let Some(receiver) = sig.inputs.iter().find_map(|arg| match arg {
            syn::FnArg::Receiver(inner) => Some(inner),
            _ => None,
        }) {
            match (receiver.mutability, &receiver.reference) {
                (Some(_), Some((_, None))) => {}
                (Some(_), Some((_, Some(lifetime)))) => {
                    abort! {
                    lifetime.span(),
                    "zod: namespace methods are not allowed to have lifetimes"

                    }
                }
                (None, None) => abort!(
                    receiver.self_token.span,
                    "zod: expected `&mut self` got `self`.",
                ),
                (None, Some((and, _))) => {
                    abort!(and.span, "zod: expected `&mut self` got `&self`.",)
                }
                (Some(_), None) => abort!(
                    receiver.self_token.span,
                    "zod: expected `&mut self` got `mut self`.",
                ),
            }
        } else {
            abort!(
                ident.span(),
                "zod: namespace methods must have a self argument"
            );
        }

        let arg_types = sig
            .inputs
            .iter()
            .filter_map(|arg| match arg {
                syn::FnArg::Receiver(_) => None,
                syn::FnArg::Typed(t) => Some(RpcArg {
                    ty: t.ty.clone(),
                    name: match t.pat.as_ref() {
                        syn::Pat::Ident(ident) => ident.ident.to_string(),
                        _ => abort_call_site!("Expected an ident, got {:?}", t.pat),
                    },
                }),
            })
            .collect();

        Self {
            ident,
            arg_types,
            kind,
            output,
        }
    }
}

pub enum RpcItemKind {
    Method,
    Stream,
}