1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{FnArg, ItemTrait, ReturnType, TraitItem, parse_macro_input};
4
5#[proc_macro_attribute]
6pub fn service(_attr: TokenStream, input: TokenStream) -> TokenStream {
7 let input = parse_macro_input!(input as ItemTrait);
8
9 let trait_ident = &input.ident;
10 let visibility = input.vis;
11 let trait_name = trait_ident.to_string();
12
13 let mut send_bounds = vec![];
14 let mut invoke_branchs = vec![];
15 let mut client_methods = vec![];
16
17 let krate = get_crate_name();
18
19 let input_items = input.items;
20 for item in &input_items {
21 if let TraitItem::Fn(method) = item
22 && method.sig.inputs.len() == 3
23 && method.sig.asyncness.is_some()
24 && let Some(receiver) = method.sig.receiver()
25 && let FnArg::Typed(req_type) = &method.sig.inputs[2]
26 && let ReturnType::Type(_, rsp_type) = &method.sig.output
27 {
28 let method_ident = &method.sig.ident;
29 if *method_ident == "ruapc_export" || *method_ident == "ruapc_request" {
30 panic!("the function cannot be named `ruapc_export` or `ruapc_request`!");
31 }
32 let method_name = format!("{trait_name}/{method_ident}");
33
34 let req_type = req_type.ty.clone();
35 let output = &method.sig.output;
36 client_methods.push(quote! {
37 async fn #method_ident(#receiver, ctx: &#krate::Context, req: #req_type) #output {
38 self.ruapc_request(ctx, req, #method_name).await
39 }
40 });
41
42 send_bounds.push(quote! { Self::#method_ident(..): Send, });
43 invoke_branchs.push(quote! {
44 let this = self.clone();
45 let method = #krate::Method::new(
46 ::schemars::schema_for!(#req_type),
47 ::schemars::schema_for!(#rsp_type),
48 Box::new(move |mut ctx, msg| {
49 let this = this.clone();
50 ::tokio::spawn(async move {
51 let meta = msg.meta.clone();
52 match msg.deserialize() {
53 Ok(req) => {
54 let result = this.#method_ident(&ctx, &req).await;
55 ctx.send_rsp(meta, result).await;
56 }
57 Err(err) => {
58 ctx.send_err_rsp(meta, err).await;
59 }
60 }
61 });
62 Ok(())
63 }),
64 );
65 map.insert(#method_name.into(), method);
66 });
67 } else {
68 panic!(
69 "the function should be in the form `async fn func(&self, ctx: &Context, req: &Req) -> Result<Rsp>`."
70 );
71 }
72 }
73
74 quote! {
75 #visibility trait #trait_ident {
76 const NAME: &'static str = #trait_name;
77
78 #(#input_items)*
79
80 fn ruapc_export(
81 self: ::std::sync::Arc<Self>,
82 ) -> ::std::collections::HashMap<String, #krate::Method>
83 where
84 Self: 'static + Send + Sync,
85 #(#send_bounds)*
86 {
87 let mut map = ::std::collections::HashMap::new();
88 #(#invoke_branchs)*
89 map
90 }
91 }
92
93 impl #trait_ident for #krate::Client {
94 #(#client_methods)*
95 }
96 }
97 .into()
98}
99
100pub(crate) fn get_crate_name() -> proc_macro2::TokenStream {
101 match proc_macro_crate::crate_name("ruapc") {
102 Ok(proc_macro_crate::FoundCrate::Name(name)) => {
103 let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
104 quote! { ::#ident }
105 }
106 _ => quote! { crate },
107 }
108}