use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro_error::{abort, proc_macro_error};
use quote::{format_ident, quote, ToTokens};
use syn::{
parse::Result as ParseResult, parse_macro_input, spanned::Spanned, Attribute, Fields, Ident,
Meta, NestedMeta,
};
fn attr_traits(attr: &Attribute, key: &str) -> Vec<syn::Path> {
let mut traits = Vec::new();
if attr.path.is_ident(key) {
if let Ok(Meta::List(ref ml)) = attr.parse_meta() {
for n in ml.nested.iter() {
if let NestedMeta::Meta(Meta::Path(p)) = n {
traits.push(p.clone())
}
}
}
}
traits
}
#[allow(dead_code)]
struct ReceiverDef {
attrs: Vec<Attribute>,
attrs_span: Span,
ident: Ident,
ident_span: Span,
fields: Fields,
}
impl syn::parse::Parse for ReceiverDef {
fn parse(input: syn::parse::ParseStream) -> ParseResult<Self> {
let derive_input: syn::DeriveInput = input.parse()?;
let attrs_span = derive_input.span();
let syn::DeriveInput { attrs, ident, data, .. } = derive_input;
let ident_span = ident.span();
let fields = match data {
syn::Data::Struct(data) => data.fields,
_ => {
return Err(syn::Error::new(
ident_span,
"derive macro only works for structs",
))
}
};
Ok(ReceiverDef {
attrs,
attrs_span,
ident,
ident_span,
fields,
})
}
}
#[proc_macro_error]
#[proc_macro_derive(Actor, attributes(services))]
pub fn derive_actor(input: TokenStream) -> TokenStream {
let actor_receiver = parse_macro_input!(input as ReceiverDef);
let mut traits = Vec::new();
for attr in actor_receiver.attrs.iter() {
traits.extend(attr_traits(attr, "services"));
}
if traits.is_empty() {
abort!(
actor_receiver.attrs_span,
"Missing list of traits. try `#[services(Trait1,Trait2)]`"
);
}
let actor_ident = actor_receiver.ident;
let dispatch_impl = gen_dispatch(&traits, &actor_ident);
let output = quote!(
pub const HOST_API_VERSION : u32 = 1;
#[link(wasm_import_module = "wasmbus")]
#[cfg(target_arch = "wasm32")]
extern "C" {
pub fn __guest_response(ptr: *const u8, len: usize);
pub fn __guest_error(ptr: *const u8, len: usize);
pub fn __guest_request(op_ptr: *const u8, ptr: *const u8);
}
#[no_mangle]
pub extern "C" fn __wasmbus_rpc_version() -> u32 {
HOST_API_VERSION
}
#[no_mangle]
#[cfg(target_arch = "wasm32")]
pub extern "C" fn __guest_call(op_len: i32, req_len: i32) -> i32 {
use std::slice;
let buf: Vec<u8> = Vec::with_capacity(req_len as _);
let req_ptr = buf.as_ptr();
let opbuf: Vec<u8> = Vec::with_capacity(op_len as _);
let op_ptr = opbuf.as_ptr();
let (slice, op) = unsafe {
__guest_request(op_ptr, req_ptr);
(
slice::from_raw_parts(req_ptr, req_len as _),
slice::from_raw_parts(op_ptr, op_len as _),
)
};
let method = String::from_utf8_lossy(op);
let context = wasmbus_rpc::common::Context::default();
let actor = #actor_ident ::default();
let resp = futures::executor::block_on({
wasmbus_rpc::common::MessageDispatch::dispatch(
&actor,
&context,
wasmbus_rpc::common::Message {
method: &method,
arg: std::borrow::Cow::Borrowed(slice),
},
)
});
match resp {
Ok(data) => {
unsafe {
__guest_response(data.as_ptr(), data.len() as _);
}
1
}
Err(e) => {
let errmsg = format!("Guest call failed for method {}: {}",
&method, e);
unsafe {
__guest_error(errmsg.as_ptr(), errmsg.len() as _);
}
0
}
}
}
#dispatch_impl
);
output.into()
}
#[proc_macro_derive(ActorHealthResponder)]
pub fn derive_health_responder(input: TokenStream) -> TokenStream {
let actor_receiver = parse_macro_input!(input as ReceiverDef);
let actor_ident = actor_receiver.ident;
let output = quote!(
#[async_trait]
impl Actor for #actor_ident {
async fn health_request(
&self,
ctx: &wasmbus_rpc::common::Context,
arg: &wasmbus_rpc::core::HealthCheckRequest,
) -> wasmbus_rpc::error::RpcResult<wasmbus_rpc::core::HealthCheckResponse> {
Ok(wasmbus_rpc::core::HealthCheckResponse {
healthy: true,
message: None,
})
}
}
); output.into()
}
fn gen_dispatch(traits: &[syn::Path], ident: &Ident) -> TokenStream2 {
let mut methods = Vec::new();
let mut trait_receiver_impl = Vec::new();
for path in traits.iter() {
let path_str = path.segments.to_token_stream().to_string();
let id = format_ident!("{}Receiver", &path_str);
methods.push(quote!(
#path_str => #id::dispatch(self, ctx, message).await
));
trait_receiver_impl.push(quote!(
impl #id for #ident { }
));
}
quote!(
#[async_trait]
impl wasmbus_rpc::common::MessageDispatch for #ident {
async fn dispatch(
&self,
ctx: &wasmbus_rpc::common::Context,
message: wasmbus_rpc::common::Message<'_>,
) -> std::result::Result<Vec<u8>, wasmbus_rpc::error::RpcError> {
let (trait_name, trait_method) = message
.method
.rsplit_once('.')
.unwrap_or(("_", message.method));
let message = wasmbus_rpc::common::Message {
method: trait_method,
arg: message.arg,
};
match trait_name {
#( #methods, )*
_ => Err(wasmbus_rpc::error::RpcError::MethodNotHandled(
format!("{}.{} - unknown method", trait_name,message.method)))
}
}
}
#( #trait_receiver_impl )*
)
}
fn gen_empty_dispatch(ident: &Ident) -> TokenStream2 {
quote!(
#[async_trait]
impl wasmbus_rpc::common::MessageDispatch for #ident {
async fn dispatch(&self, _ctx: &wasmbus_rpc::common::Context, message: wasmbus_rpc::common::Message<'_>) -> std::result::Result<Vec<u8>, wasmbus_rpc::error::RpcError> {
Err(wasmbus_rpc::error::RpcError::MethodNotHandled(message.method.to_string()))
}
}
)
}
#[proc_macro_error]
#[proc_macro_derive(Provider, attributes(services))]
pub fn derive_provider(input: TokenStream) -> TokenStream {
let provider_receiver = parse_macro_input!(input as ReceiverDef);
let mut traits = Vec::new();
for attr in provider_receiver.attrs.iter() {
traits.extend(attr_traits(attr, "services"));
}
let ident = provider_receiver.ident;
let output = if traits.is_empty() {
gen_empty_dispatch(&ident)
} else {
gen_dispatch(&traits, &ident)
};
output.into()
}