1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::{format_ident, quote, quote_spanned, ToTokens};
4use syn::{
5 braced,
6 ext::IdentExt,
7 parenthesized,
8 parse::{Parse, ParseStream},
9 parse_macro_input, parse_quote,
10 punctuated::Punctuated,
11 spanned::Spanned,
12 Attribute, FnArg, Ident, Lifetime, Pat, PatType, Path, ReturnType, Token, Type, Visibility,
13};
14
15macro_rules! extend_errors {
16 ($errors: ident, $e: expr) => {
17 match $errors {
18 Ok(_) => $errors = Err($e),
19 Err(ref mut errors) => errors.extend($e),
20 }
21 };
22}
23
24fn stream_item_type(ty: &Type) -> Option<&Type> {
30 let Type::ImplTrait(impl_trait) = ty else {
31 return None;
32 };
33 for bound in &impl_trait.bounds {
34 let syn::TypeParamBound::Trait(trait_bound) = bound else {
35 continue;
36 };
37 let last_segment = trait_bound.path.segments.last()?;
38 if last_segment.ident != "Stream" {
39 continue;
40 }
41 let syn::PathArguments::AngleBracketed(arguments) = &last_segment.arguments else {
42 continue;
43 };
44 for argument in &arguments.args {
45 if let syn::GenericArgument::AssocType(associated) = argument {
46 if associated.ident == "Item" {
47 return Some(&associated.ty);
48 }
49 }
50 }
51 }
52 None
53}
54
55fn type_arguments<'a>(ty: &'a Type, wrapper: &str) -> Option<Vec<&'a Type>> {
57 let Type::Path(type_path) = ty else {
58 return None;
59 };
60 let last_segment = type_path.path.segments.last()?;
61 if last_segment.ident != wrapper {
62 return None;
63 }
64 let syn::PathArguments::AngleBracketed(arguments) = &last_segment.arguments else {
65 return None;
66 };
67 arguments
68 .args
69 .iter()
70 .map(|argument| match argument {
71 syn::GenericArgument::Type(ty) => Some(ty),
72 _ => None,
73 })
74 .collect()
75}
76
77fn option_inner_type(ty: &Type) -> Option<&Type> {
79 match type_arguments(ty, "Option")?.as_slice() {
80 [inner] => Some(inner),
81 _ => None,
82 }
83}
84
85fn result_inner_types(ty: &Type) -> Option<(&Type, &Type)> {
87 match type_arguments(ty, "Result")?.as_slice() {
88 [ok, err] => Some((ok, err)),
89 _ => None,
90 }
91}
92
93fn js_inner_type(ty: &Type) -> Option<(&Type, bool)> {
95 for (wrapper, transfer) in [("Post", false), ("Transfer", true)] {
96 if let Some([inner]) = type_arguments(ty, wrapper).as_deref() {
97 return Some((inner, transfer));
98 }
99 }
100 None
101}
102
103fn is_borrowed_serde_ref(ty: &Type) -> bool {
106 let Type::Reference(reference) = ty else {
107 return false;
108 };
109 match &*reference.elem {
110 Type::Path(path) => path.path.is_ident("str"),
111 Type::Slice(slice) => matches!(&*slice.elem, Type::Path(path) if path.path.is_ident("u8")),
112 _ => false,
113 }
114}
115
116fn is_cfg_attr(attr: &Attribute) -> bool {
120 attr.path().is_ident("cfg") || attr.path().is_ident("cfg_attr")
121}
122
123fn cfg_predicates(attrs: &[Attribute]) -> Vec<TokenStream2> {
126 attrs
127 .iter()
128 .filter(|attr| attr.path().is_ident("cfg"))
129 .filter_map(|attr| attr.parse_args::<TokenStream2>().ok())
130 .collect()
131}
132
133fn emit_encode(
143 ty: &Type,
144 value: TokenStream2,
145 post_args: &TokenStream2,
146 transfer_args: &TokenStream2,
147) -> TokenStream2 {
148 if let Some(inner) = option_inner_type(ty) {
149 let inner_encode = emit_encode(inner, quote!(__inner), post_args, transfer_args);
150 quote_spanned! {ty.span()=>
151 match &#value {
152 ::core::option::Option::Some(__inner) =>
153 web_rpc::codec::WireArg::Some(::std::boxed::Box::new(#inner_encode)),
154 ::core::option::Option::None =>
155 web_rpc::codec::WireArg::None,
156 }
157 }
158 } else if let Some((ok, err)) = result_inner_types(ty) {
159 let ok_encode = emit_encode(ok, quote!(__inner), post_args, transfer_args);
160 let err_encode = emit_encode(err, quote!(__inner), post_args, transfer_args);
161 quote_spanned! {ty.span()=>
162 match &#value {
163 ::core::result::Result::Ok(__inner) =>
164 web_rpc::codec::WireArg::Ok(::std::boxed::Box::new(#ok_encode)),
165 ::core::result::Result::Err(__inner) =>
166 web_rpc::codec::WireArg::Err(::std::boxed::Box::new(#err_encode)),
167 }
168 }
169 } else if let Some((_, transfer)) = js_inner_type(ty) {
170 let push_transfer = transfer.then(|| {
171 quote! { (#transfer_args).push(web_rpc::wrap::js_value(&#value.0)); }
172 });
173 quote_spanned! {ty.span()=>
174 {
175 (#post_args).push(web_rpc::wrap::js_value(&#value.0));
176 #push_transfer
177 web_rpc::codec::WireArg::Js
178 }
179 }
180 } else {
181 quote_spanned! {ty.span()=>
182 web_rpc::codec::WireArg::Bytes(
183 web_rpc::postcard::to_allocvec(&#value).unwrap()
184 )
185 }
186 }
187}
188
189fn emit_decode(ty: &Type, wire: TokenStream2, js_values: &TokenStream2) -> TokenStream2 {
192 if let Some(inner) = option_inner_type(ty) {
193 let inner_decode = emit_decode(inner, quote!(*__inner), js_values);
194 quote_spanned! {ty.span()=>
195 match #wire {
196 web_rpc::codec::WireArg::Some(__inner) =>
197 ::core::option::Option::Some(#inner_decode),
198 web_rpc::codec::WireArg::None =>
199 ::core::option::Option::None,
200 _ => panic!("web_rpc: wire/type mismatch, expected Some or None"),
201 }
202 }
203 } else if let Some((ok, err)) = result_inner_types(ty) {
204 let ok_decode = emit_decode(ok, quote!(*__inner), js_values);
205 let err_decode = emit_decode(err, quote!(*__inner), js_values);
206 quote_spanned! {ty.span()=>
207 match #wire {
208 web_rpc::codec::WireArg::Ok(__inner) =>
209 ::core::result::Result::Ok(#ok_decode),
210 web_rpc::codec::WireArg::Err(__inner) =>
211 ::core::result::Result::Err(#err_decode),
212 _ => panic!("web_rpc: wire/type mismatch, expected Ok or Err"),
213 }
214 }
215 } else if let Some((inner, _)) = js_inner_type(ty) {
216 quote_spanned! {ty.span()=>
217 match #wire {
218 web_rpc::codec::WireArg::Js => <#ty>::new(
219 web_rpc::wasm_bindgen::JsCast::dyn_into::<#inner>((#js_values).shift()).unwrap()
220 ),
221 _ => panic!("web_rpc: wire/type mismatch, expected a Javascript value"),
222 }
223 }
224 } else {
225 quote_spanned! {ty.span()=>
226 match #wire {
227 web_rpc::codec::WireArg::Bytes(__bytes) =>
228 web_rpc::postcard::from_bytes::<#ty>(&__bytes).unwrap(),
229 _ => panic!("web_rpc: wire/type mismatch, expected postcard bytes"),
230 }
231 }
232 }
233}
234
235fn emit_desc(ty: &Type) -> TokenStream2 {
240 if let Some(inner) = option_inner_type(ty) {
241 let inner_desc = emit_desc(inner);
242 quote_spanned!(ty.span()=> &web_rpc::describe::Desc::Option(#inner_desc))
243 } else if let Some((ok, err)) = result_inner_types(ty) {
244 let ok_desc = emit_desc(ok);
245 let err_desc = emit_desc(err);
246 quote_spanned!(ty.span()=> &web_rpc::describe::Desc::Result(#ok_desc, #err_desc))
247 } else if let Some((inner, transfer)) = js_inner_type(ty) {
248 quote_spanned! {inner.span()=>
249 &web_rpc::describe::Desc::Js {
250 name: <#inner as web_rpc::describe::JsName>::NAME,
251 transfer: #transfer,
252 }
253 }
254 } else if is_borrowed_serde_ref(ty) {
255 let schema_ty: Type = match ty {
258 Type::Reference(reference) => match &*reference.elem {
259 Type::Path(path) if path.path.is_ident("str") => {
260 parse_quote!(::std::string::String)
261 }
262 other => other.clone(),
263 },
264 other => other.clone(),
265 };
266 quote_spanned! {ty.span()=>
267 &web_rpc::describe::Desc::Inline(
268 <#schema_ty as web_rpc::postcard_schema::Schema>::SCHEMA
269 )
270 }
271 } else {
272 quote_spanned! {ty.span()=>
273 &web_rpc::describe::Desc::Postcard(
274 <#ty as web_rpc::postcard_schema::Schema>::SCHEMA
275 )
276 }
277 }
278}
279
280struct Service {
285 attrs: Vec<Attribute>,
286 vis: Visibility,
287 ident: Ident,
288 methods: Vec<RpcMethod>,
289}
290
291enum MethodOutput {
293 Notify,
294 Value(Type),
295 Stream(Type),
296}
297
298struct RpcMethod {
299 is_async: Option<Token![async]>,
300 attrs: Vec<Attribute>,
301 receiver: syn::Receiver,
302 ident: Ident,
303 args: Vec<PatType>,
304 output: MethodOutput,
305}
306
307impl RpcMethod {
308 fn argument_idents(&self) -> impl Iterator<Item = &Ident> {
311 self.args.iter().map(|argument| match &*argument.pat {
312 Pat::Ident(pattern) => &pattern.ident,
313 _ => unreachable!("argument patterns are rejected while parsing"),
314 })
315 }
316
317 fn cfg_attrs(&self) -> impl Iterator<Item = &Attribute> {
320 self.attrs.iter().filter(|attr| is_cfg_attr(attr))
321 }
322
323 fn variant_ident(&self) -> Ident {
325 Ident::new(
326 &snake_to_camel(&self.ident.unraw().to_string()),
327 self.ident.span(),
328 )
329 }
330
331 fn wire_name(&self) -> String {
333 to_lower_camel(&self.ident.unraw().to_string())
334 }
335
336 fn return_tokens(&self) -> TokenStream2 {
338 match &self.output {
339 MethodOutput::Notify => quote!(),
340 MethodOutput::Value(ty) => quote!(-> #ty),
341 MethodOutput::Stream(item) => {
342 quote!(-> impl web_rpc::futures_core::Stream<Item = #item>)
343 }
344 }
345 }
346}
347
348struct ServiceGenerator<'a> {
353 trait_ident: &'a Ident,
354 service_ident: Ident,
355 client_ident: Ident,
356 request_ident: Ident,
357 response_ident: Ident,
358 description_ident: Ident,
359 vis: &'a Visibility,
360 attrs: &'a [Attribute],
361 methods: &'a [RpcMethod],
362}
363
364impl ServiceGenerator<'_> {
365 fn enum_request(&self) -> TokenStream2 {
366 let Self {
367 vis,
368 request_ident,
369 methods,
370 ..
371 } = self;
372 let variants = methods.iter().map(|method| {
373 let cfg_attrs = method.cfg_attrs();
374 let variant_ident = method.variant_ident();
375 let fields = method.args.iter().map(|argument| {
376 let pat = &argument.pat;
377 if is_borrowed_serde_ref(&argument.ty) {
378 let Type::Reference(reference) = &*argument.ty else {
380 unreachable!("is_borrowed_serde_ref guarantees a reference")
381 };
382 let mut reference = reference.clone();
383 reference.lifetime = Some(Lifetime::new("'a", reference.and_token.span()));
384 quote_spanned! {argument.ty.span()=> #pat: #reference }
385 } else {
386 quote_spanned! {argument.ty.span()=> #pat: web_rpc::codec::WireArg }
387 }
388 });
389 quote! {
390 #(#cfg_attrs)*
391 #variant_ident { #( #fields ),* }
392 }
393 });
394 quote! {
398 #[derive(web_rpc::serde::Serialize, web_rpc::serde::Deserialize)]
399 #vis enum #request_ident<'a> {
400 #( #variants, )*
401 #[doc(hidden)]
402 __WebRpcPhantom(::std::marker::PhantomData<&'a ()>),
403 }
404 }
405 }
406
407 fn enum_response(&self) -> TokenStream2 {
408 let Self {
409 vis,
410 response_ident,
411 methods,
412 ..
413 } = self;
414 let variants = methods.iter().map(|method| {
417 let cfg_attrs = method.cfg_attrs();
418 let variant_ident = method.variant_ident();
419 quote! {
420 #(#cfg_attrs)*
421 #variant_ident ( web_rpc::codec::WireArg )
422 }
423 });
424 quote! {
425 #[derive(web_rpc::serde::Serialize, web_rpc::serde::Deserialize)]
426 #vis enum #response_ident {
427 #( #variants ),*
428 }
429 }
430 }
431
432 fn const_description(&self) -> TokenStream2 {
435 let Self {
436 vis,
437 attrs,
438 trait_ident,
439 description_ident,
440 methods,
441 ..
442 } = self;
443
444 let method_const_idents = (0..methods.len())
445 .map(|index| format_ident!("__WEB_RPC_{}_M{}", screaming_snake(trait_ident), index))
446 .collect::<Vec<_>>();
447
448 let method_consts =
449 methods
450 .iter()
451 .zip(&method_const_idents)
452 .map(|(method, const_ident)| {
453 let args = method.args.iter().zip(method.argument_idents()).map(
454 |(argument, ident)| {
455 let name = to_lower_camel(&ident.unraw().to_string());
456 let desc = emit_desc(&argument.ty);
457 quote! { web_rpc::describe::Arg { name: #name, desc: #desc } }
458 },
459 );
460 let ret = match &method.output {
461 MethodOutput::Notify => quote!(web_rpc::describe::Return::Notify),
462 MethodOutput::Value(ty) => {
463 let desc = emit_desc(ty);
464 quote!(web_rpc::describe::Return::Value(#desc))
465 }
466 MethodOutput::Stream(item) => {
467 let desc = emit_desc(item);
468 quote!(web_rpc::describe::Return::Stream(#desc))
469 }
470 };
471 let wire_name = method.wire_name();
472 let predicates = cfg_predicates(&method.attrs);
473 let (enabled, disabled) = if predicates.is_empty() {
474 (quote!(), quote!(#[cfg(any())]))
475 } else {
476 (
477 quote!(#[cfg(all(#( #predicates ),*))]),
478 quote!(#[cfg(not(all(#( #predicates ),*)))]),
479 )
480 };
481 quote! {
482 #enabled
483 #[doc(hidden)]
484 const #const_ident: &'static [web_rpc::describe::Method] =
485 &[web_rpc::describe::Method {
486 name: #wire_name,
487 args: &[ #( #args ),* ],
488 ret: #ret,
489 }];
490 #disabled
491 #[doc(hidden)]
492 const #const_ident: &'static [web_rpc::describe::Method] = &[];
493 }
494 });
495
496 let trait_name = trait_ident.to_string();
497 let trait_cfgs = attrs
498 .iter()
499 .filter(|attr| is_cfg_attr(attr))
500 .collect::<Vec<_>>();
501 quote! {
502 #( #method_consts )*
503 #( #trait_cfgs )*
504 #[doc(hidden)]
505 #[allow(non_upper_case_globals)]
506 #vis const #description_ident: &'static web_rpc::describe::Service =
507 &web_rpc::describe::Service {
508 name: #trait_name,
509 methods: &[ #( #method_const_idents ),* ],
510 };
511 }
512 }
513
514 fn trait_service(&self) -> TokenStream2 {
515 let Self {
516 attrs,
517 methods,
518 vis,
519 trait_ident,
520 ..
521 } = self;
522
523 let declarations = methods.iter().map(|method| {
524 let RpcMethod {
525 attrs,
526 args,
527 receiver,
528 ident,
529 is_async,
530 ..
531 } = method;
532 let output = method.return_tokens();
533 quote_spanned! {ident.span()=>
534 #( #attrs )*
535 #is_async fn #ident(#receiver, #( #args ),*) #output;
536 }
537 });
538
539 let forwards = methods
540 .iter()
541 .map(|method| {
542 let RpcMethod {
543 attrs,
544 args,
545 receiver,
546 ident,
547 is_async,
548 ..
549 } = method;
550 let output = method.return_tokens();
551 let do_await = is_async.map(|token| quote_spanned!(token.span=> .await));
552 let argument_idents = method.argument_idents();
553 quote_spanned! {ident.span()=>
554 #( #attrs )*
555 #is_async fn #ident(#receiver, #( #args ),*) #output {
556 T::#ident(self, #( #argument_idents ),*)#do_await
557 }
558 }
559 })
560 .collect::<Vec<_>>();
561
562 quote! {
563 #( #attrs )*
564 #[allow(async_fn_in_trait)]
565 #vis trait #trait_ident {
566 #( #declarations )*
567 }
568
569 impl<T> #trait_ident for ::std::sync::Arc<T> where T: #trait_ident {
570 #( #forwards )*
571 }
572 impl<T> #trait_ident for ::std::boxed::Box<T> where T: #trait_ident {
573 #( #forwards )*
574 }
575 impl<T> #trait_ident for ::std::rc::Rc<T> where T: #trait_ident {
576 #( #forwards )*
577 }
578 }
579 }
580
581 fn struct_client(&self) -> TokenStream2 {
582 let Self {
583 vis,
584 client_ident,
585 request_ident,
586 response_ident,
587 methods,
588 ..
589 } = self;
590
591 let rpc_fns = methods.iter().map(|method| {
592 let RpcMethod {
593 attrs, args, ident, ..
594 } = method;
595 let variant_ident = method.variant_ident();
596
597 let mut encodings = Vec::new();
600 let mut request_fields = Vec::new();
601 for (argument, argument_ident) in args.iter().zip(method.argument_idents()) {
602 if is_borrowed_serde_ref(&argument.ty) {
603 request_fields.push(quote! { #argument_ident });
604 } else {
605 let wire_ident = format_ident!("__wire_{}", argument_ident);
606 let encode = emit_encode(
607 &argument.ty,
608 quote!(#argument_ident),
609 "e!(&__post_args),
610 "e!(&__transfer_args),
611 );
612 encodings.push(quote! { let #wire_ident = #encode; });
613 request_fields.push(quote! { #argument_ident: #wire_ident });
614 }
615 }
616
617 let send = quote! {
618 let __post_args = web_rpc::js_sys::Array::new();
619 let __transfer_args = web_rpc::js_sys::Array::new();
620 #( #encodings )*
621 let __request = #request_ident::#variant_ident { #( #request_fields ),* };
622 let __sequence = self.state.send(&__request, &__post_args, &__transfer_args);
623 };
624
625 let unpack = |ty: &Type| {
626 let decode = emit_decode(ty, quote!(__wire), "e!(&__js_values));
627 quote! {
628 |__response: #response_ident, __js_values: web_rpc::js_sys::Array| {
629 let #response_ident::#variant_ident(__wire) = __response else {
630 panic!("web_rpc: received a response for another method")
631 };
632 #decode
633 }
634 }
635 };
636
637 let (return_type, body) = match &method.output {
638 MethodOutput::Notify => (quote!(()), quote! { #send }),
639 MethodOutput::Value(ty) => {
640 let unpack = unpack(ty);
641 (
642 quote!(web_rpc::client::RequestFuture<#ty>),
643 quote! {
644 #send
645 self.state.request(__sequence, #unpack)
646 },
647 )
648 }
649 MethodOutput::Stream(item) => {
650 let unpack = unpack(item);
651 (
652 quote!(web_rpc::client::StreamReceiver<#item>),
653 quote! {
654 #send
655 self.state.stream(__sequence, #unpack)
656 },
657 )
658 }
659 };
660
661 quote! {
662 #( #attrs )*
663 #vis fn #ident(&self, #( #args ),*) -> #return_type {
664 #body
665 }
666 }
667 });
668
669 quote! {
670 #[derive(::core::clone::Clone)]
671 #vis struct #client_ident {
672 state: web_rpc::client::State<#response_ident>,
673 }
674 impl ::std::fmt::Debug for #client_ident {
675 fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
676 formatter.debug_struct(::std::stringify!(#client_ident)).finish()
677 }
678 }
679 impl web_rpc::client::Client for #client_ident {
680 type Response = #response_ident;
681 }
682 impl ::std::convert::From<web_rpc::client::State<#response_ident>> for #client_ident {
683 fn from(state: web_rpc::client::State<#response_ident>) -> Self {
684 Self { state }
685 }
686 }
687 impl #client_ident {
688 #( #rpc_fns )*
689 }
690 }
691 }
692
693 fn struct_server(&self) -> TokenStream2 {
694 let Self {
695 vis,
696 trait_ident,
697 service_ident,
698 request_ident,
699 response_ident,
700 methods,
701 ..
702 } = self;
703
704 let handlers = methods.iter().map(|method| {
705 let RpcMethod {
706 is_async,
707 ident,
708 args,
709 ..
710 } = method;
711 let cfg_attrs = method.cfg_attrs();
712 let variant_ident = method.variant_ident();
713
714 let mut destructure_fields = Vec::new();
717 let mut decodings = Vec::new();
718 for (argument, argument_ident) in args.iter().zip(method.argument_idents()) {
719 if is_borrowed_serde_ref(&argument.ty) {
720 destructure_fields.push(quote! { #argument_ident });
721 } else {
722 let wire_ident = format_ident!("__wire_{}", argument_ident);
723 let decode =
724 emit_decode(&argument.ty, quote!(#wire_ident), "e!(&__js_args));
725 destructure_fields.push(quote! { #argument_ident: #wire_ident });
726 decodings.push(quote! { let #argument_ident = #decode; });
727 }
728 }
729 let argument_idents = method.argument_idents().collect::<Vec<_>>();
730 let call = quote! { self.implementation.#ident(#( #argument_idents ),*) };
731
732 let encode_outgoing = |ty: &Type, value: TokenStream2| {
733 let encode = emit_encode(
734 ty,
735 value,
736 "e!(&__post_args),
737 "e!(&__transfer_args),
738 );
739 quote! {
740 let __post_args = web_rpc::js_sys::Array::new();
741 let __transfer_args = web_rpc::js_sys::Array::new();
742 let __wire = #encode;
743 (#response_ident::#variant_ident(__wire), __post_args, __transfer_args)
744 }
745 };
746
747 let body = match (&method.output, is_async) {
748 (MethodOutput::Notify, None) => quote! {
749 #call;
750 web_rpc::service::ExecuteResult::Response(None)
751 },
752 (MethodOutput::Notify, Some(_)) => quote! {
753 #call.await;
754 web_rpc::service::ExecuteResult::Response(None)
755 },
756 (MethodOutput::Value(ty), None) => {
757 let outgoing = encode_outgoing(ty, quote!(__response));
758 quote! {
759 let __response = #call;
760 web_rpc::service::ExecuteResult::Response(Some({ #outgoing }))
761 }
762 }
763 (MethodOutput::Value(ty), Some(_)) => {
764 let outgoing = encode_outgoing(ty, quote!(__response));
765 quote! {
766 let mut __task = ::std::pin::pin!(web_rpc::futures_util::FutureExt::fuse(#call));
767 web_rpc::service::ExecuteResult::Response(
768 web_rpc::futures_util::select! {
769 _ = __abort_rx => None,
770 __response = __task => Some({ #outgoing }),
771 }
772 )
773 }
774 }
775 (MethodOutput::Stream(item), is_async) => {
776 let outgoing = encode_outgoing(item, quote!(__item));
777 let forward = quote! {
778 let mut __items = ::std::pin::pin!(__items);
779 let mut __forward = ::std::pin::pin!(web_rpc::futures_util::FutureExt::fuse(async {
780 while let Some(__item) = web_rpc::futures_util::StreamExt::next(&mut __items).await {
781 let __outgoing = { #outgoing };
782 if __stream_tx.unbounded_send((__sequence, Some(__outgoing))).is_err() {
783 break;
784 }
785 }
786 }));
787 web_rpc::futures_util::select! {
788 _ = __abort_rx => {},
789 _ = __forward => {},
790 }
791 let _ = __stream_tx.unbounded_send((__sequence, None));
792 web_rpc::service::ExecuteResult::StreamComplete
793 };
794 match is_async {
795 None => quote! {
796 let __items = #call;
797 #forward
798 },
799 Some(_) => quote! {
800 let mut __task = ::std::pin::pin!(web_rpc::futures_util::FutureExt::fuse(#call));
801 let __items = web_rpc::futures_util::select! {
802 _ = __abort_rx => None,
803 __items = __task => Some(__items),
804 };
805 match __items {
806 Some(__items) => { #forward }
807 None => {
808 let _ = __stream_tx.unbounded_send((__sequence, None));
809 web_rpc::service::ExecuteResult::StreamComplete
810 }
811 }
812 },
813 }
814 }
815 };
816
817 quote! {
818 #( #cfg_attrs )*
819 #request_ident::#variant_ident { #( #destructure_fields ),* } => {
820 #( #decodings )*
821 #body
822 }
823 }
824 });
825
826 quote! {
827 #vis struct #service_ident<T> {
828 implementation: T
829 }
830 impl<T: #trait_ident> web_rpc::service::Service for #service_ident<T> {
831 type Response = #response_ident;
832 #[allow(unused_mut, unused_variables)]
833 async fn execute(
834 &self,
835 __sequence: u32,
836 mut __abort_rx: web_rpc::futures_channel::oneshot::Receiver<()>,
837 __payload: ::std::vec::Vec<u8>,
838 __js_args: web_rpc::js_sys::Array,
839 __stream_tx: web_rpc::futures_channel::mpsc::UnboundedSender<
840 web_rpc::service::StreamMessage<Self::Response>
841 >,
842 ) -> (u32, web_rpc::service::ExecuteResult<Self::Response>) {
843 let __request: #request_ident<'_> =
844 web_rpc::postcard::from_bytes(&__payload).unwrap();
845 let __result = match __request {
846 #( #handlers )*
847 #request_ident::__WebRpcPhantom(_) => {
848 unreachable!("web_rpc: __WebRpcPhantom variant received on wire")
849 }
850 };
851 (__sequence, __result)
852 }
853 }
854 impl<T: #trait_ident> ::std::convert::From<T> for #service_ident<T> {
855 fn from(implementation: T) -> Self {
856 Self { implementation }
857 }
858 }
859 }
860 }
861}
862
863impl ToTokens for ServiceGenerator<'_> {
864 fn to_tokens(&self, output: &mut TokenStream2) {
865 output.extend([
866 self.enum_request(),
867 self.enum_response(),
868 self.const_description(),
869 self.trait_service(),
870 self.struct_client(),
871 self.struct_server(),
872 ])
873 }
874}
875
876impl Parse for Service {
881 fn parse(input: ParseStream) -> syn::Result<Self> {
882 let attrs = input.call(Attribute::parse_outer)?;
883 let vis = input.parse()?;
884 input.parse::<Token![trait]>()?;
885 let ident: Ident = input.parse()?;
886 let content;
887 braced!(content in input);
888 let mut methods = Vec::new();
889 while !content.is_empty() {
890 methods.push(content.parse()?);
891 }
892 Ok(Self {
893 attrs,
894 vis,
895 ident,
896 methods,
897 })
898 }
899}
900
901impl Parse for RpcMethod {
902 fn parse(input: ParseStream) -> syn::Result<Self> {
903 let mut errors = Ok(());
904 let attrs = input.call(Attribute::parse_outer)?;
905
906 let is_async = input.parse::<Token![async]>().ok();
907 input.parse::<Token![fn]>()?;
908 let ident: Ident = input.parse()?;
909
910 if input.peek(Token![<]) {
912 let generics: syn::Generics = input.parse()?;
913 extend_errors!(
914 errors,
915 syn::Error::new_spanned(
916 generics,
917 "web_rpc::service trait methods may not have generic parameters; \
918 concrete types are required so the macro can route and describe each \
919 argument."
920 )
921 );
922 }
923
924 let content;
925 parenthesized!(content in input);
926 let mut receiver: Option<syn::Receiver> = None;
927 let mut args = Vec::new();
928 for argument in content.parse_terminated(FnArg::parse, Token![,])? {
929 match argument {
930 FnArg::Typed(typed) => match &*typed.pat {
931 Pat::Ident(_) => args.push(typed),
932 _ => extend_errors!(
933 errors,
934 syn::Error::new(
935 typed.pat.span(),
936 "patterns are not allowed in RPC arguments"
937 )
938 ),
939 },
940 FnArg::Receiver(ref parsed) => {
941 if parsed.reference.is_none() || parsed.mutability.is_some() {
942 extend_errors!(
943 errors,
944 syn::Error::new(
945 argument.span(),
946 "RPC methods only support `&self` as a receiver"
947 )
948 );
949 }
950 receiver = Some(parsed.clone());
951 }
952 }
953 }
954 let receiver = receiver.unwrap_or_else(|| {
955 extend_errors!(
956 errors,
957 syn::Error::new(
958 ident.span(),
959 "RPC methods must include `&self` as the first parameter"
960 )
961 );
962 parse_quote!(&self)
963 });
964 let output = match input.parse::<ReturnType>()? {
965 ReturnType::Default => MethodOutput::Notify,
966 ReturnType::Type(_, ty) => match stream_item_type(&ty) {
967 Some(item) => MethodOutput::Stream(item.clone()),
968 None => MethodOutput::Value(*ty),
969 },
970 };
971 input.parse::<Token![;]>()?;
972 errors?;
973
974 Ok(Self {
975 is_async,
976 attrs,
977 receiver,
978 ident,
979 args,
980 output,
981 })
982 }
983}
984
985#[proc_macro_attribute]
992pub fn service(_attr: TokenStream, input: TokenStream) -> TokenStream {
993 let Service {
994 ref attrs,
995 ref vis,
996 ref ident,
997 ref methods,
998 } = parse_macro_input!(input as Service);
999
1000 ServiceGenerator {
1001 trait_ident: ident,
1002 service_ident: format_ident!("{}Service", ident),
1003 client_ident: format_ident!("{}Client", ident),
1004 request_ident: format_ident!("{}Request", ident),
1005 response_ident: format_ident!("{}Response", ident),
1006 description_ident: format_ident!("{}_DESCRIPTION", screaming_snake(ident)),
1007 vis,
1008 attrs,
1009 methods,
1010 }
1011 .into_token_stream()
1012 .into()
1013}
1014
1015struct EndpointArgs {
1021 service: Option<Path>,
1022 client: Option<Path>,
1023}
1024
1025impl Parse for EndpointArgs {
1026 fn parse(input: ParseStream) -> syn::Result<Self> {
1027 let mut service = None;
1028 let mut client = None;
1029 for entry in Punctuated::<EndpointArg, Token![,]>::parse_terminated(input)? {
1030 let (slot, path, key) = match entry {
1031 EndpointArg::Service(path) => (&mut service, path, "service"),
1032 EndpointArg::Client(path) => (&mut client, path, "client"),
1033 };
1034 if slot.replace(path).is_some() {
1035 return Err(syn::Error::new(
1036 input.span(),
1037 format!("`{key}` is given more than once"),
1038 ));
1039 }
1040 }
1041 if service.is_none() && client.is_none() {
1042 return Err(syn::Error::new(
1043 proc_macro2::Span::call_site(),
1044 "a Javascript endpoint needs at least one of `service = ...` (the trait it \
1045 implements) and `client = ...` (the trait it calls)",
1046 ));
1047 }
1048 Ok(Self { service, client })
1049 }
1050}
1051
1052enum EndpointArg {
1053 Service(Path),
1054 Client(Path),
1055}
1056
1057impl Parse for EndpointArg {
1058 fn parse(input: ParseStream) -> syn::Result<Self> {
1059 let key: Ident = input.parse()?;
1060 input.parse::<Token![=]>()?;
1061 if key == "service" {
1062 Ok(EndpointArg::Service(input.parse()?))
1063 } else if key == "client" {
1064 Ok(EndpointArg::Client(input.parse()?))
1065 } else {
1066 Err(syn::Error::new(
1067 key.span(),
1068 "expected `service` or `client`",
1069 ))
1070 }
1071 }
1072}
1073
1074fn description_path(path: &Path) -> syn::Result<Path> {
1076 let span = path.span();
1077 let mut path = path.clone();
1078 let last = path
1079 .segments
1080 .last_mut()
1081 .ok_or_else(|| syn::Error::new(span, "expected a generated Service or Client"))?;
1082 let name = last.ident.to_string();
1083 let trait_name = name
1084 .strip_suffix("Service")
1085 .or_else(|| name.strip_suffix("Client"))
1086 .filter(|trait_name| !trait_name.is_empty())
1087 .ok_or_else(|| {
1088 syn::Error::new(
1089 last.ident.span(),
1090 "expected a name generated by #[web_rpc::service], which ends in `Service` or \
1091 `Client`",
1092 )
1093 })?;
1094 last.ident = format_ident!(
1095 "{}_DESCRIPTION",
1096 screaming_snake_str(trait_name),
1097 span = last.ident.span()
1098 );
1099 last.arguments = syn::PathArguments::None;
1100 Ok(path)
1101}
1102
1103#[proc_macro]
1106pub fn endpoint(input: TokenStream) -> TokenStream {
1107 let args = parse_macro_input!(input as EndpointArgs);
1108
1109 let class_path = args.client.as_ref().or(args.service.as_ref()).unwrap();
1110 let class_name = class_path.segments.last().unwrap().ident.to_string();
1111 let snake = snake_case_str(&class_name);
1112 let screaming = screaming_snake_str(&class_name);
1113
1114 let description = |path: Option<&Path>| match path.map(description_path) {
1115 Some(Ok(path)) => Ok(quote!(::core::option::Option::Some(#path))),
1116 Some(Err(error)) => Err(error.to_compile_error()),
1117 None => Ok(quote!(::core::option::Option::None)),
1118 };
1119 let service_description = match description(args.service.as_ref()) {
1120 Ok(tokens) => tokens,
1121 Err(error) => return error.into(),
1122 };
1123 let client_description = match description(args.client.as_ref()) {
1124 Ok(tokens) => tokens,
1125 Err(error) => return error.into(),
1126 };
1127
1128 let endpoint_ident = format_ident!("__WEB_RPC_ENDPOINT_{screaming}");
1129 let js_length_ident = format_ident!("__WEB_RPC_ENDPOINT_{screaming}_JS_LENGTH");
1130 let js_ident = format_ident!("__WEB_RPC_ENDPOINT_{screaming}_JS");
1131 let dts_length_ident = format_ident!("__WEB_RPC_ENDPOINT_{screaming}_DTS_LENGTH");
1132 let dts_ident = format_ident!("__WEB_RPC_ENDPOINT_{screaming}_DTS");
1133 let guard_ident = format_ident!("__web_rpc_{snake}");
1134 let js_section = format!("__web_rpc_{snake}_js");
1135 let dts_section = format!("__web_rpc_{snake}_d_ts");
1136
1137 quote! {
1138 #[doc(hidden)]
1139 const #endpoint_ident: web_rpc::js::Endpoint = web_rpc::js::Endpoint {
1140 class: #class_name,
1141 service: #service_description,
1142 client: #client_description,
1143 };
1144 #[doc(hidden)]
1145 const #js_length_ident: usize = web_rpc::js::render_js::<0>(&#endpoint_ident).length;
1146 #[doc(hidden)]
1147 #[allow(long_running_const_eval)]
1148 const #js_ident: [u8; #js_length_ident] =
1149 web_rpc::js::render_js::<#js_length_ident>(&#endpoint_ident).bytes;
1150 #[doc(hidden)]
1151 const #dts_length_ident: usize = web_rpc::js::render_dts::<0>(&#endpoint_ident).length;
1152 #[doc(hidden)]
1153 #[allow(long_running_const_eval)]
1154 const #dts_ident: [u8; #dts_length_ident] =
1155 web_rpc::js::render_dts::<#dts_length_ident>(&#endpoint_ident).bytes;
1156
1157 const _: () = {
1158 #[used]
1159 #[link_section = #js_section]
1160 static JS: [u8; #js_length_ident] = #js_ident;
1161 #[used]
1162 #[link_section = #dts_section]
1163 static D_TS: [u8; #dts_length_ident] = #dts_ident;
1164 #[no_mangle]
1167 static #guard_ident: u8 = 0;
1168 };
1169 }
1170 .into()
1171}
1172
1173fn snake_to_camel(name: &str) -> String {
1179 let mut camel = String::with_capacity(name.len());
1180 let mut capitalize_next = true;
1181 for character in name.chars() {
1182 match character {
1183 '_' => capitalize_next = true,
1184 character if capitalize_next => {
1185 camel.extend(character.to_uppercase());
1186 capitalize_next = false;
1187 }
1188 character => camel.extend(character.to_lowercase()),
1189 }
1190 }
1191 camel
1192}
1193
1194fn to_lower_camel(name: &str) -> String {
1196 let camel = snake_to_camel(name);
1197 let mut characters = camel.chars();
1198 match characters.next() {
1199 Some(first) => first.to_lowercase().chain(characters).collect(),
1200 None => camel,
1201 }
1202}
1203
1204fn screaming_snake(ident: &Ident) -> String {
1206 screaming_snake_str(&ident.to_string())
1207}
1208
1209fn screaming_snake_str(name: &str) -> String {
1210 snake_case_str(name).to_uppercase()
1211}
1212
1213fn snake_case_str(name: &str) -> String {
1215 let mut snake = String::with_capacity(name.len() + 4);
1216 for (index, character) in name.chars().enumerate() {
1217 if character.is_uppercase() && index > 0 {
1218 snake.push('_');
1219 }
1220 snake.extend(character.to_lowercase());
1221 }
1222 snake
1223}