zod-derive 0.1.0

Rust integraions with the `zod` typescript library.
Documentation
use darling::ast::Fields;
use proc_macro2::TokenStream;
use syn::{parse_quote, spanned::Spanned, Ident, Path};

use crate::rpc::args::{BackendField, BackendInput};
use crate::{get_private, get_zod};
use quote::{format_ident, quote, quote_spanned};

pub fn expand(input: BackendInput, fields: Fields<BackendField>) -> TokenStream {
    let __private = get_private();
    let ident = input.ident;
    let req_ident = format_ident!("{}Req", ident);

    let backend_impl = expand_backend_impl(&ident, &req_ident, &fields);
    let req_enum = expand_req_enum(&req_ident, &fields);
    let req_enum_impl = expand_req_enum_impl(&ident, &req_ident, &fields);

    quote! {
        const _: () = {
            #backend_impl

            #req_enum

            #req_enum_impl
        };
    }
}

fn expand_backend_impl(
    ident: &Ident,
    req_ident: &Ident,
    fields: &Fields<BackendField>,
) -> TokenStream {
    let zod = get_zod();
    let __private = get_private();

    let namespaces = fields.iter().map(|f| {
        let ty = &f.ty;
        quote_spanned!(ty.span() => <#ty as #zod::Namespace>::NAME)
    });

    quote_spanned! {
        ident.span() =>
        #[#__private::async_trait::async_trait]
        impl #__private::server::Backend for #ident {
            const NS_NAMES: &'static[&'static str] = &[#(#namespaces),*];

            async fn handle_request(
                &mut self,
                req: #__private::Request,
                sender: #__private::ResponseSender,
                subscribers: &mut #__private::server::SubscriberMap,
            ) {
                match req {
                    #__private::Request::Exec { id, value } => {
                        match #__private::serde_json::from_value::<#req_ident>(value) {
                            ::std::result::Result::<_, _>::Ok(evt) => {
                                if let Some(jh) = evt.call(id, self, sender).await {
                                    subscribers.insert(id, jh);
                                }
                            }
                            ::std::result::Result::<_, _>::Err(err) => {
                                let _ = sender
                                    .unbounded_send(#__private::Response::error(id, err))
                                    .ok();
                            }
                        }
                    }
                    #__private::Request::CancelStream { id } => {
                        if let Some(jh) = subscribers.remove(&id) {
                            jh.abort();
                        }
                    }
                }
            }
        }
    }
}

pub fn expand_req_enum(ident: &Ident, fields: &Fields<BackendField>) -> TokenStream {
    let __private = get_private();
    let req_variants = fields.iter().map(|f| {
        let ty = &f.ty;
        let variant_ident = variant_ident_from_ty(ty);
        quote_spanned!(ty.span() => #variant_ident(<#ty as #__private::codegen::RpcNamespace>::Req))
    });
    quote! {

        #[derive(#__private::serde::Deserialize, Debug)]
        #[serde(tag = "namespace")]
        enum #ident {
            #(#req_variants),*
        }
    }
}

pub fn expand_req_enum_impl(
    backend_ident: &Ident,
    req_ident: &Ident,
    fields: &Fields<BackendField>,
) -> TokenStream {
    let __private = get_private();

    let req_call_dispatch = fields.iter().enumerate().map(|(index, f)| {
        let ty = &f.ty;
        let variant_ident = variant_ident_from_ty(ty);
        let field_or_index = f.ident.clone().map(|ident| quote!(#ident)).unwrap_or_else(|| {
            let index = syn::Index::from(index);
            quote!(#index)
        });

        quote_spanned!(ty.span() => #req_ident :: #variant_ident(req) => req.call(id, &mut backend.#field_or_index, sender).await)
    });

    quote_spanned! {
        req_ident.span() =>
        impl #req_ident {
            pub async fn call(
                self,
                id: usize,
                backend: &mut #backend_ident,
                sender: #__private::ResponseSender,
            ) -> ::std::option::Option<#__private::tokio::task::JoinHandle<()>> {
                match self {
                    #(#req_call_dispatch),*
                }
            }
        }
    }
}

fn variant_ident_from_ty(ty: &syn::Type) -> Ident {
    let p: Path = parse_quote!(#ty);
    p.segments.last().expect("one segment").ident.clone()
}