Skip to main content

frodobuf_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use proc_macro_error::{abort, proc_macro_error};
4use quote::{format_ident, quote, ToTokens};
5use syn::{
6    parse::Result as ParseResult, parse_macro_input, spanned::Spanned, Attribute, Fields, Ident,
7    Meta, NestedMeta,
8};
9
10/// extract traits from attribute
11///  `#[services(Apple,Banana)]` returns vec![ Piano, Tuba ]
12///  items in the vec are syn::Path, and may have more than one path segment,
13///    as in instruments::Piano
14///
15fn attr_traits(attr: &Attribute, key: &str) -> Vec<syn::Path> {
16    let mut traits = Vec::new();
17    if attr.path.is_ident(key) {
18        if let Ok(Meta::List(ref ml)) = attr.parse_meta() {
19            for n in ml.nested.iter() {
20                if let NestedMeta::Meta(Meta::Path(p)) = n {
21                    traits.push(p.clone())
22                }
23            }
24        }
25    }
26    traits
27}
28
29#[allow(dead_code)]
30struct ServerDef {
31    attrs: Vec<Attribute>,
32    attrs_span: Span,
33    ident: Ident,
34    ident_span: Span,
35    fields: Fields,
36}
37
38impl syn::parse::Parse for ServerDef {
39    fn parse(input: syn::parse::ParseStream) -> ParseResult<Self> {
40        let derive_input: syn::DeriveInput = input.parse()?;
41        let attrs_span = derive_input.span();
42        let syn::DeriveInput {
43            attrs, ident, data, ..
44        } = derive_input;
45        let ident_span = ident.span();
46        let fields = match data {
47            syn::Data::Struct(data) => data.fields,
48            _ => {
49                return Err(syn::Error::new(
50                    ident_span,
51                    "derive macro only works for structs",
52                ))
53            }
54        };
55        Ok(ServerDef {
56            attrs,
57            attrs_span,
58            ident,
59            ident_span,
60            fields,
61        })
62    }
63}
64
65#[proc_macro_error]
66#[proc_macro_derive(FrodobufActor, attributes(services))]
67pub fn derive_actor(input: TokenStream) -> TokenStream {
68    let actor_server = parse_macro_input!(input as ServerDef);
69
70    let mut traits = Vec::new();
71    for attr in actor_server.attrs.iter() {
72        traits.extend(attr_traits(attr, "services"));
73    }
74    if traits.is_empty() {
75        abort!(
76            actor_server.attrs_span,
77            "Missing list of traits. try `#[services(Trait1,Trait2)]`"
78        );
79    }
80    let actor_ident = actor_server.ident;
81    let dispatch_impl = gen_dispatch(&traits, &actor_ident);
82    let output = quote!(
83
84    #[link(wasm_import_module = "wapc")]
85    extern "C" {
86        pub fn __guest_response(ptr: *const u8, len: usize);
87        pub fn __guest_error(ptr: *const u8, len: usize);
88        pub fn __guest_request(op_ptr: *const u8, ptr: *const u8);
89    }
90
91    #[no_mangle]
92    pub extern "C" fn __actor_api_version() -> u32 {
93        frodobuf::FRODOBUF_API_VERSION
94    }
95
96    #[no_mangle]
97    pub extern "C" fn __guest_call(op_len: i32, req_len: i32) -> i32 {
98        use std::slice;
99
100        let buf: Vec<u8> = Vec::with_capacity(req_len as _);
101        let req_ptr = buf.as_ptr();
102
103        let opbuf: Vec<u8> = Vec::with_capacity(op_len as _);
104        let op_ptr = opbuf.as_ptr();
105
106        let (slice, op) = unsafe {
107            __guest_request(op_ptr, req_ptr);
108            (
109                slice::from_raw_parts(req_ptr, req_len as _),
110                slice::from_raw_parts(op_ptr, op_len as _),
111            )
112        };
113        let method = String::from_utf8_lossy(op);
114        let context = context::Context::default();
115        let actor = #actor_ident ::default();
116        let resp = futures::executor::block_on({
117            MessageDispatch::dispatch(
118                &actor,
119                &context,
120                Message {
121                    method: &method,
122                    arg: std::borrow::Cow::Borrowed(slice),
123                },
124            )
125        });
126        match resp {
127            Ok(Message { arg, .. }) => {
128                unsafe {
129                    __guest_response(arg.as_ptr(), arg.len() as _);
130                }
131                1
132            }
133            Err(e) => {
134                //let errmsg = format!("Guest call failed for method {} on {}: {}",
135                //        &method, #actor_ident, e);
136                let errmsg = format!("Guest call failed for method {}: {}",
137                        &method, e);
138                unsafe {
139                    __guest_error(errmsg.as_ptr(), errmsg.len() as _);
140                }
141                0
142            }
143        }
144    }
145
146       #dispatch_impl
147    ); // end quote
148
149    // struct #actor_ident { #fields }
150    output.into()
151}
152/*
153
154
155
156*/
157
158fn gen_dispatch(traits: &[syn::Path], ident: &Ident) -> TokenStream2 {
159    let mut methods = Vec::new();
160    let mut methods_legacy = Vec::new();
161    let mut trait_server_impl = Vec::new();
162    //let ident_name = ident.to_string();
163
164    for path in traits.iter() {
165        let path_str = path.segments.to_token_stream().to_string();
166        let id = format_ident!("{}Server", &path_str);
167        //let quoted_path = format!("\"{}\"", &path_str);
168        methods.push(quote!(
169            #path_str => #id::dispatch(self, ctx, &message).await
170        ));
171        methods_legacy.push(quote!(
172            match #id::dispatch(self, ctx, &message).await {
173                Err(RpcError::MethodNotHandled(_)) => {}, // continue
174                res => return res, // either Ok(_) or Err(_)
175            };
176        ));
177        trait_server_impl.push(quote!(
178            impl #id for #ident { }
179        ));
180    }
181
182    quote!(
183        #[async_trait]
184        impl MessageDispatch for #ident {
185            async fn dispatch(
186                &self,
187                ctx: &context::Context<'_>,
188                message: Message<'_>,
189            ) -> Result<Message<'static>, RpcError> {
190                let (trait_name, trait_method) = message
191                    .method
192                    .rsplit_once('.')
193                    .unwrap_or(("_", message.method));
194
195                let message = Message {
196                    method: trait_method,
197                    arg: message.arg,
198                };
199                match trait_name {
200                   #( #methods, )*
201
202                    "_" => {
203                        // legacy handlers  - compatibility with no Trait prefix
204                        #( #methods_legacy )*
205                        Err(RpcError::MethodNotHandled(message.method.to_string()))
206                    },
207                    _ => Err(RpcError::MethodNotHandled(
208                            format!("{} - unknown trait", message.method)))
209                }
210            }
211        }
212
213      #( #trait_server_impl )*
214    )
215}
216/*
217
218*/
219
220#[proc_macro_error]
221#[proc_macro_derive(FrodobufProvider, attributes(services))]
222pub fn derive_provider(input: TokenStream) -> TokenStream {
223    let provider_server = parse_macro_input!(input as ServerDef);
224
225    let mut traits = Vec::new();
226    for attr in provider_server.attrs.iter() {
227        traits.extend(attr_traits(attr, "services"));
228    }
229    if traits.is_empty() {
230        abort!(
231            provider_server.attrs_span,
232            "Missing list of traits. try `#[services(Trait1,Trait2)]`"
233        );
234    }
235    let ident = provider_server.ident;
236    //let fields = actor_server.fields;
237    let dispatch_impl = gen_dispatch(&traits, &ident);
238    let output = quote!(
239
240    impl wasmcloud_provider_core::CapabilityProvider for #ident {
241        /// This function will be called on the provider when the host runtime is ready and has
242        /// configured a dispatcher. This function is only ever
243        /// called _once_ for a capability provider, regardless of the number of actors being
244        /// managed in the host
245        fn configure_dispatch( &self, dispatcher: Box<dyn provider::Dispatcher>,
246             ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
247          let mut lock = self.dispatcher.write().unwrap();
248          *lock = Some(dispatcher);
249          Ok(())
250        }
251
252        /// Invoked when an actor has requested that a provider perform a given operation
253        fn handle_call(
254            &self,
255            actor: &str,
256            op: &str,
257            arg: &[u8],
258        ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
259            let ctx = &context::Context {
260                actor: Some(actor),
261                ..Default::default()
262            };
263            let response = futures::executor::block_on(MessageDispatch::dispatch(
264                self,
265                &ctx,
266                Message {
267                    method: op,
268                    arg: std::borrow::Cow::Borrowed(arg),
269                },
270            ))?;
271            Ok(response.arg.to_vec())
272        }
273
274        /// This function is called to let the capability provider know that it is being removed
275        /// from the host runtime. This gives the provider an opportunity to clean up any
276        /// resources and stop any running threads.
277        /// WARNING: do not do anything in this function that can
278        /// cause a panic, including attempting to write to STDOUT while the host process is terminating
279        fn stop(&self) {
280                #ident :: stop(&self);
281        }
282    }
283
284    #dispatch_impl
285
286        );
287    output.into()
288}