napi_ext_macros 0.3.0

Extends napi-rs with the ability to run local futures
Documentation
use proc_macro::TokenStream;
use quote::quote;
use quote::ToTokens;
use quote::TokenStreamExt;
use syn::Ident;
use syn::ItemFn;

#[proc_macro_attribute]
pub fn napi_async(
  _args: TokenStream,
  input: TokenStream,
) -> TokenStream {
  convert(input.into())
    .unwrap_or_else(|err| err.into_compile_error())
    .into()
}

fn convert(input: proc_macro2::TokenStream) -> Result<proc_macro2::TokenStream, syn::Error> {
  let mut func = syn::parse2::<ItemFn>(input)?;

  if func.sig.asyncness.is_none() {
    return Err(syn::Error::new_spanned(func, "Failed to do the thing"));
  }

  let raw_inputs = &func.sig.inputs;
  let mut input_names = proc_macro2::TokenStream::new();

  for input in raw_inputs.iter() {
    match &input {
      syn::FnArg::Receiver(_r) => continue,
      syn::FnArg::Typed(t) => {
        input_names.append_all(t.pat.to_token_stream());
        input_names.append_all(quote! {,});
      }
    }
  }

  let ident = func.sig.ident.clone();
  func.sig.ident = Ident::new(&format!("async_local_{}", ident.to_string()), ident.span());
  let new_ident = &func.sig.ident;

  let ret = match &func.sig.output {
    syn::ReturnType::Default => quote! {napi::Result<napi::JsUndefined>},
    syn::ReturnType::Type(_, v) => quote!(#v),
  };

  Ok(quote! {
    #func

    #[napi_derive::napi]
    fn #ident(#raw_inputs) -> napi::Result<JsObject> {
      let fut = #new_ident(#input_names);
      env.spawn_local_promise(move |env| async move {
        unsafe {
          let env_raw = env.raw();
          <#ret as napi::bindgen_prelude::ToNapiValue>::
            to_napi_value(env_raw, fut.await)
            .and_then(|v| JsUnknown::from_napi_value(env_raw, v))
        }
      })
    }
  })
}