node_api_macro 0.5.0

Write Node.js native addons with Node-API in Rust.
Documentation
use quote::quote;
use syn::spanned::Spanned;

#[proc_macro]
pub fn init(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
	init_impl(input.into())
		.unwrap_or_else(|e| e.to_compile_error())
		.into()
}

fn init_impl(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2::TokenStream> {
	let input: syn::Ident = syn::parse2(input)?;
	let code = quote! {
		#[no_mangle]
		pub unsafe extern "C" fn napi_register_module_v1(env: node_api::sys::napi_env, exports: node_api::sys::napi_value) -> node_api::sys::napi_value {
			let env = node_api::Env::from_raw(env);
			let exports = node_api::Value::from_raw(env, exports);
			let result = std::panic::catch_unwind(|| #input(env, exports));
			let result = match result {
				Ok(result) => result,
				Err(panic_info) => {
					env.throw_error("A panic occurred.");
					return std::ptr::null_mut();
				},
			};
			let exports = match result {
				Ok(exports) => exports,
				Err(error) => {
					if !env.is_exception_pending() {
						env.throw_error(&format!("{}", error));
					}
					return std::ptr::null_mut();
				}
			};
			exports.raw()
		}
	};
	Ok(code)
}

#[proc_macro_attribute]
pub fn function(
	_attr: proc_macro::TokenStream,
	input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
	function_impl(input.into())
		.unwrap_or_else(|e| e.to_compile_error())
		.into()
}

fn function_impl(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2::TokenStream> {
	let input: syn::ItemFn = syn::parse2(input)?;
	let visibility = &input.vis;
	let ident = &input.sig.ident;
	let impl_inputs = input.sig.inputs.iter().skip(1);
	let impl_output = &input.sig.output;
	let impl_block = &input.block;
	let args = input
		.sig
		.inputs
		.iter()
		.skip(1)
		.map(|input| {
			let input = match input {
				syn::FnArg::Typed(arg) => arg,
				syn::FnArg::Receiver(_) => {
					return Err(syn::Error::new(
						input.span(),
						"receiver arg is not allowed here",
					))
				}
			};
			let ident = match &*input.pat {
				syn::Pat::Ident(pat_ident) => &pat_ident.ident,
				_ => return Err(syn::Error::new(input.pat.span(), "invalid pattern")),
			};
			Ok(ident)
		})
		.collect::<syn::Result<Vec<_>>>()?;
	let args_count = args.len();
	let from_node_api_statements = args
		.iter()
		.enumerate()
		.map(|(i, ident)| {
			let code = quote! {
				let #ident = argv[#i];
				let #ident = node_api::Value::from_raw(env, #ident);
				let #ident = node_api::FromNodeAPI::from_node_api(#ident)?;
			};
			Ok(code)
		})
		.collect::<syn::Result<Vec<_>>>()?;
	let code = quote! {
		#visibility unsafe extern "C" fn #ident(env: node_api::sys::napi_env, info: node_api::sys::napi_callback_info) -> node_api::sys::napi_value {
			fn function_impl<'a>(env: node_api::Env<'a>, #(#impl_inputs),*) #impl_output #impl_block
			let env = node_api::Env::from_raw(env);
			let result = std::panic::catch_unwind(|| -> node_api::Result<_> {
				let mut argc = #args_count;
				let mut argv: [node_api::sys::napi_value; #args_count] = [std::ptr::null_mut(); #args_count];
				let status = node_api::sys::napi_get_cb_info(
					env.raw(),
					info,
					&mut argc as *mut usize,
					argv.as_mut_ptr() as *mut node_api::sys::napi_value,
					std::ptr::null_mut(),
					std::ptr::null_mut()
				);
				if status != node_api::sys::napi_status::napi_ok {
					return Err(node_api::Error::from_last_node_api_error(env.raw(), status).into());
				}
				if argc != #args_count {
					return Err(node_api::Error::message("invalid number of arguments").into());
				}
				#(#from_node_api_statements)*
				let output = function_impl(env, #(#args),*).map_err(|error| node_api::Error::message(error.to_string()))?;
				let output = node_api::IntoNodeApi::into_node_api(output, env)?;
				Ok(output)
			});
			let result = match result {
				Ok(result) => result,
				Err(_) => {
					env.throw_error("A panic occurred.");
					return std::ptr::null_mut();
				},
			};
			let output = match result {
				Ok(output) => output,
				Err(error) => {
					if !env.is_exception_pending() {
						env.throw_error(&format!("{}", error));
					}
					return std::ptr::null_mut();
				}
			};
			output.raw()
		}
	};
	Ok(code)
}