1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::parse::Parser;
4use syn::punctuated::Punctuated;
5use syn::{
6 Expr, FnArg, GenericArgument, ItemFn, Lit, LitStr, PatType, Path, PathArguments, Token, Type,
7 parse_macro_input,
8};
9
10#[proc_macro_attribute]
11pub fn host(attr: TokenStream, item: TokenStream) -> TokenStream {
12 let parser = Punctuated::<Expr, Token![,]>::parse_terminated;
13 let args = match parser.parse(attr) {
14 Ok(args) => args,
15 Err(err) => return err.to_compile_error().into(),
16 };
17
18 let (route_lit, mode) = match parse_host_attr(args) {
19 Ok(parsed) => parsed,
20 Err(err) => return err.to_compile_error().into(),
21 };
22
23 let route = route_lit.value();
24 let Some((namespace, method)) = route.rsplit_once('.') else {
25 return syn::Error::new(
26 route_lit.span(),
27 "host route must look like \"namespace.method\"",
28 )
29 .to_compile_error()
30 .into();
31 };
32 if namespace.trim().is_empty() || method.trim().is_empty() {
33 return syn::Error::new(
34 route_lit.span(),
35 "host route must contain non-empty namespace and method",
36 )
37 .to_compile_error()
38 .into();
39 }
40 if namespace == "channel" {
41 return syn::Error::new(
42 route_lit.span(),
43 "host namespace 'channel' is reserved by the JS API; choose a different namespace",
44 )
45 .to_compile_error()
46 .into();
47 }
48
49 let input_fn = parse_macro_input!(item as ItemFn);
50 match mode {
51 HostMode::Stream => expand_stream(route_lit.clone(), namespace, method, input_fn).into(),
52 HostMode::Channel => expand_channel(route_lit.clone(), namespace, method, input_fn).into(),
53 HostMode::Unary => expand_host(route_lit.clone(), namespace, method, mode, input_fn).into(),
54 }
55}
56
57#[proc_macro]
58pub fn register_hosts(input: TokenStream) -> TokenStream {
59 let parser = Punctuated::<Path, Token![,]>::parse_terminated;
60 let paths = parse_macro_input!(input with parser);
61
62 let registrations = match paths
63 .iter()
64 .map(expand_register_host_path)
65 .collect::<syn::Result<Vec<_>>>()
66 {
67 Ok(registrations) => registrations,
68 Err(err) => return err.to_compile_error().into(),
69 };
70
71 quote!({
72 #(#registrations)*
73 })
74 .into()
75}
76
77fn parse_host_attr(args: Punctuated<Expr, Token![,]>) -> syn::Result<(LitStr, HostMode)> {
78 let Some(first) = args.first() else {
79 return Err(syn::Error::new(
80 proc_macro2::Span::call_site(),
81 "expected #[host(\"namespace.method\")]",
82 ));
83 };
84 let Expr::Lit(first_lit) = first else {
85 return Err(syn::Error::new_spanned(
86 first,
87 "expected #[host(\"namespace.method\")]",
88 ));
89 };
90 let Lit::Str(route_lit) = &first_lit.lit else {
91 return Err(syn::Error::new_spanned(
92 &first_lit.lit,
93 "expected #[host(\"namespace.method\")]",
94 ));
95 };
96
97 let mut mode = HostMode::Unary;
98 for arg in args.iter().skip(1) {
99 match arg {
100 Expr::Path(path) if path.path.is_ident("stream") => {
101 if !matches!(mode, HostMode::Unary) {
102 return Err(syn::Error::new_spanned(
103 arg,
104 "duplicate or conflicting mode flag in #[host(...)]",
105 ));
106 }
107 mode = HostMode::Stream;
108 }
109 Expr::Path(path) if path.path.is_ident("channel") => {
110 if !matches!(mode, HostMode::Unary) {
111 return Err(syn::Error::new_spanned(
112 arg,
113 "duplicate or conflicting mode flag in #[host(...)]",
114 ));
115 }
116 mode = HostMode::Channel;
117 }
118 _ => {
119 return Err(syn::Error::new_spanned(
120 arg,
121 "expected only #[host(\"namespace.method\")], #[host(\"namespace.method\", stream)], or #[host(\"namespace.method\", channel)]",
122 ));
123 }
124 }
125 }
126
127 Ok((route_lit.clone(), mode))
128}
129
130#[derive(Clone, Copy)]
131enum HostMode {
132 Unary,
133 Stream,
134 Channel,
135}
136
137fn expand_register_host_path(path: &Path) -> syn::Result<proc_macro2::TokenStream> {
138 let mut helper_path = path.clone();
139 let Some(last_segment) = helper_path.segments.last_mut() else {
140 return Err(syn::Error::new_spanned(
141 path,
142 "register_hosts! expects a handler function path",
143 ));
144 };
145
146 if !matches!(last_segment.arguments, PathArguments::None) {
147 return Err(syn::Error::new_spanned(
148 &last_segment.arguments,
149 "register_hosts! does not accept generic arguments",
150 ));
151 }
152
153 last_segment.ident = format_ident!("{}_host", last_segment.ident);
154 Ok(quote! {
155 ::lingxia::register_host_entry(#helper_path());
156 })
157}
158
159fn expand_host(
160 route_lit: LitStr,
161 namespace: &str,
162 method: &str,
163 mode: HostMode,
164 input_fn: ItemFn,
165) -> proc_macro2::TokenStream {
166 let fn_ident = input_fn.sig.ident.clone();
167 let helper_ident = format_ident!("{}_host", fn_ident);
168 let handler_ident = format_ident!("__LingxiaHostHandler_{}", fn_ident);
169 let namespace_lit = LitStr::new(namespace, route_lit.span());
170 let method_lit = LitStr::new(method, route_lit.span());
171
172 let call_plan = match HostFnPlan::from_fn(&input_fn) {
173 Ok(plan) => plan,
174 Err(err) => return err.to_compile_error(),
175 };
176
177 let call_expr = call_plan.call_expr(&fn_ident, input_fn.sig.asyncness.is_some());
178 let ctor_ident = match mode {
179 HostMode::Unary => format_ident!("new"),
180 HostMode::Stream => format_ident!("stream"),
181 HostMode::Channel => unreachable!("channel mode is handled by expand_channel"),
182 };
183 let serialize_expr = match mode {
184 HostMode::Unary => quote! {
185 ::lingxia::host::serialize_result(__lingxia_result)
186 },
187 HostMode::Stream => unreachable!("stream mode is handled by expand_stream"),
188 HostMode::Channel => unreachable!("channel mode is handled by expand_channel"),
189 };
190 quote! {
191 #input_fn
192
193 #[doc(hidden)]
194 #[allow(non_camel_case_types)]
195 pub struct #handler_ident;
196
197 impl ::lingxia::host::HostHandler for #handler_ident {
198 fn call<'a>(
199 &'a self,
200 __lingxia_lxapp: std::sync::Arc<::lingxia::LxApp>,
201 __lingxia_input: Option<String>,
202 __lingxia_cancel: ::lingxia::host::HostCancel,
203 ) -> ::lingxia::host::HostFuture<'a> {
204 Box::pin(async move {
205 let __lingxia_result = #call_expr;
206 #serialize_expr
207 })
208 }
209 }
210
211 #[doc(hidden)]
212 pub fn #helper_ident() -> ::lingxia::host::HostRegistrationEntry {
213 ::lingxia::host::HostRegistrationEntry::Handler(
214 ::lingxia::host::HostRegistration::#ctor_ident(
215 #namespace_lit,
216 #method_lit,
217 std::sync::Arc::new(#handler_ident),
218 )
219 )
220 }
221 }
222}
223
224struct HostFnPlan {
225 has_lxapp: bool,
226 input_ty: Option<Type>,
227 has_cancel: bool,
228}
229
230impl HostFnPlan {
231 fn from_fn(input_fn: &ItemFn) -> syn::Result<Self> {
232 let mut has_lxapp = false;
233 let mut input_ty = None;
234 let mut has_cancel = false;
235 let input_count = input_fn.sig.inputs.len();
236
237 for (index, arg) in input_fn.sig.inputs.iter().enumerate() {
238 let FnArg::Typed(arg) = arg else {
239 return Err(syn::Error::new_spanned(
240 arg,
241 "#[host] does not support methods with a receiver",
242 ));
243 };
244
245 if index == 0 && is_lxapp_arg(arg) {
246 has_lxapp = true;
247 continue;
248 }
249
250 if is_host_cancel_arg(arg) {
251 if index + 1 != input_count {
252 return Err(syn::Error::new_spanned(
253 arg,
254 "HostCancel must be the last argument in a #[host] function",
255 ));
256 }
257 if has_cancel {
258 return Err(syn::Error::new_spanned(
259 arg,
260 "#[host] functions can only take one HostCancel argument",
261 ));
262 }
263 has_cancel = true;
264 continue;
265 }
266
267 if input_ty.is_some() {
268 return Err(syn::Error::new_spanned(
269 arg,
270 "#[host] functions support at most one JSON payload argument",
271 ));
272 }
273 input_ty = Some((*arg.ty).clone());
274 }
275
276 Ok(Self {
277 has_lxapp,
278 input_ty,
279 has_cancel,
280 })
281 }
282
283 fn call_expr(&self, fn_ident: &syn::Ident, is_async: bool) -> proc_macro2::TokenStream {
284 let mut args = Vec::new();
285 let mut prelude = Vec::new();
286
287 if self.has_lxapp {
288 args.push(quote! { __lingxia_lxapp });
289 }
290
291 if let Some(input_ty) = &self.input_ty {
292 prelude.push(quote! {
293 let __lingxia_payload: #input_ty =
294 ::lingxia::host::parse_input(__lingxia_input.as_deref())?;
295 });
296 args.push(quote! { __lingxia_payload });
297 }
298
299 if self.has_cancel {
300 args.push(quote! { __lingxia_cancel });
301 }
302
303 let invoke = if is_async {
304 quote! { #fn_ident(#(#args),*).await }
305 } else {
306 quote! { #fn_ident(#(#args),*) }
307 };
308
309 quote! {
310 {
311 #(#prelude)*
312 #invoke
313 }
314 }
315 }
316}
317
318fn is_lxapp_arg(arg: &PatType) -> bool {
319 type_is_arc_lxapp(&arg.ty)
320}
321
322fn is_host_cancel_arg(arg: &PatType) -> bool {
323 type_is_host_cancel(&arg.ty)
324}
325
326fn type_is_arc_lxapp(ty: &Type) -> bool {
327 let Type::Path(type_path) = ty else {
328 return false;
329 };
330 let Some(last_segment) = type_path.path.segments.last() else {
331 return false;
332 };
333 if last_segment.ident != "Arc" {
334 return false;
335 }
336 let PathArguments::AngleBracketed(args) = &last_segment.arguments else {
337 return false;
338 };
339 let Some(GenericArgument::Type(inner_ty)) = args.args.first() else {
340 return false;
341 };
342 type_is_lxapp(inner_ty)
343}
344
345fn type_is_lxapp(ty: &Type) -> bool {
346 let Type::Path(type_path) = ty else {
347 return false;
348 };
349 type_path
350 .path
351 .segments
352 .last()
353 .map(|segment| segment.ident == "LxApp")
354 .unwrap_or(false)
355}
356
357fn type_is_host_cancel(ty: &Type) -> bool {
358 let Type::Path(type_path) = ty else {
359 return false;
360 };
361 type_path
362 .path
363 .segments
364 .last()
365 .map(|segment| segment.ident == "HostCancel")
366 .unwrap_or(false)
367}
368
369fn type_is_stream_context(ty: &Type) -> bool {
370 let Type::Path(type_path) = ty else {
371 return false;
372 };
373 type_path
374 .path
375 .segments
376 .last()
377 .map(|segment| segment.ident == "StreamContext")
378 .unwrap_or(false)
379}
380
381fn type_is_channel_context(ty: &Type) -> bool {
382 let Type::Path(type_path) = ty else {
383 return false;
384 };
385 type_path
386 .path
387 .segments
388 .last()
389 .map(|segment| segment.ident == "ChannelContext")
390 .unwrap_or(false)
391}
392
393fn context_type_args(ty: &Type, expected_ident: &str) -> syn::Result<Vec<Type>> {
394 let Type::Path(type_path) = ty else {
395 return Err(syn::Error::new_spanned(
396 ty,
397 format!("expected `{expected_ident}`"),
398 ));
399 };
400 let Some(last_segment) = type_path.path.segments.last() else {
401 return Err(syn::Error::new_spanned(
402 ty,
403 format!("expected `{expected_ident}`"),
404 ));
405 };
406 if last_segment.ident != expected_ident {
407 return Err(syn::Error::new_spanned(
408 ty,
409 format!("expected `{expected_ident}`"),
410 ));
411 }
412
413 let PathArguments::AngleBracketed(args) = &last_segment.arguments else {
414 return Ok(Vec::new());
415 };
416
417 let mut out = Vec::new();
418 for arg in &args.args {
419 let GenericArgument::Type(ty) = arg else {
420 return Err(syn::Error::new_spanned(
421 arg,
422 format!("`{expected_ident}` only supports type generic arguments"),
423 ));
424 };
425 out.push(ty.clone());
426 }
427 Ok(out)
428}
429
430fn parse_stream_context_types(ty: &Type) -> syn::Result<(Type, Type)> {
431 let args = context_type_args(ty, "StreamContext")?;
432 Ok(match args.len() {
433 0 => (
434 syn::parse_quote!(::lingxia::host::JsonValue),
435 syn::parse_quote!(()),
436 ),
437 1 => (args[0].clone(), syn::parse_quote!(())),
438 2 => (args[0].clone(), args[1].clone()),
439 _ => {
440 return Err(syn::Error::new_spanned(
441 ty,
442 "`StreamContext` supports at most two generic arguments",
443 ));
444 }
445 })
446}
447
448fn parse_channel_context_types(ty: &Type) -> syn::Result<(Type, Type)> {
449 let args = context_type_args(ty, "ChannelContext")?;
450 Ok(match args.len() {
451 0 => (
452 syn::parse_quote!(::lingxia::host::JsonValue),
453 syn::parse_quote!(::lingxia::host::JsonValue),
454 ),
455 1 => (args[0].clone(), args[0].clone()),
456 2 => (args[0].clone(), args[1].clone()),
457 _ => {
458 return Err(syn::Error::new_spanned(
459 ty,
460 "`ChannelContext` supports at most two generic arguments",
461 ));
462 }
463 })
464}
465
466struct StreamFnPlan {
469 has_lxapp: bool,
470 input_ty: Option<Type>,
471 event_ty: Type,
472 result_ty: Type,
473}
474
475impl StreamFnPlan {
476 fn from_fn(input_fn: &ItemFn) -> syn::Result<Self> {
477 let inputs = &input_fn.sig.inputs;
478
479 let Some(last) = inputs.last() else {
480 return Err(syn::Error::new(
481 proc_macro2::Span::call_site(),
482 "#[host(..., stream)] function must take `StreamContext` as its last argument",
483 ));
484 };
485 let FnArg::Typed(last_arg) = last else {
486 return Err(syn::Error::new_spanned(
487 last,
488 "#[host] does not support methods with a receiver",
489 ));
490 };
491 if !type_is_stream_context(&last_arg.ty) {
492 return Err(syn::Error::new_spanned(
493 last,
494 "last argument of a #[host(..., stream)] function must be `StreamContext`",
495 ));
496 }
497
498 let (event_ty, result_ty) = parse_stream_context_types(&last_arg.ty)?;
499 let mut has_lxapp = false;
500 let mut input_ty = None;
501 let prefix_count = inputs.len() - 1;
502
503 for (index, arg) in inputs.iter().take(prefix_count).enumerate() {
504 let FnArg::Typed(arg) = arg else {
505 return Err(syn::Error::new_spanned(
506 arg,
507 "#[host] does not support methods with a receiver",
508 ));
509 };
510 if index == 0 && is_lxapp_arg(arg) {
511 has_lxapp = true;
512 continue;
513 }
514 if input_ty.is_some() {
515 return Err(syn::Error::new_spanned(
516 arg,
517 "#[host(stream)] functions support at most one JSON payload argument",
518 ));
519 }
520 input_ty = Some((*arg.ty).clone());
521 }
522
523 Ok(Self {
524 has_lxapp,
525 input_ty,
526 event_ty,
527 result_ty,
528 })
529 }
530
531 fn call_expr(&self, fn_ident: &syn::Ident, is_async: bool) -> proc_macro2::TokenStream {
532 let mut args: Vec<proc_macro2::TokenStream> = Vec::new();
533 let mut prelude: Vec<proc_macro2::TokenStream> = Vec::new();
534
535 if self.has_lxapp {
536 args.push(quote! { __lingxia_lxapp });
537 }
538
539 if let Some(input_ty) = &self.input_ty {
540 prelude.push(quote! {
541 let __lingxia_payload: #input_ty =
542 ::lingxia::host::parse_input(__lingxia_input.as_deref())?;
543 });
544 args.push(quote! { __lingxia_payload });
545 }
546
547 args.push(quote! { __lingxia_stream });
548
549 let invoke = if is_async {
550 quote! { #fn_ident(#(#args),*).await }
551 } else {
552 quote! { #fn_ident(#(#args),*) }
553 };
554
555 quote! {
556 {
557 #(#prelude)*
558 #invoke
559 }
560 }
561 }
562}
563
564fn expand_stream(
565 route_lit: LitStr,
566 namespace: &str,
567 method: &str,
568 input_fn: ItemFn,
569) -> proc_macro2::TokenStream {
570 let fn_ident = input_fn.sig.ident.clone();
571 let helper_ident = format_ident!("{}_host", fn_ident);
572 let handler_ident = format_ident!("__LingxiaStreamHandler_{}", fn_ident);
573 let namespace_lit = LitStr::new(namespace, route_lit.span());
574 let method_lit = LitStr::new(method, route_lit.span());
575
576 let plan = match StreamFnPlan::from_fn(&input_fn) {
577 Ok(p) => p,
578 Err(err) => return err.to_compile_error(),
579 };
580 let call_expr = plan.call_expr(&fn_ident, input_fn.sig.asyncness.is_some());
581 let event_ty = &plan.event_ty;
582 let result_ty = &plan.result_ty;
583
584 quote! {
585 #input_fn
586
587 #[doc(hidden)]
588 #[allow(non_camel_case_types)]
589 pub struct #handler_ident;
590
591 impl ::lingxia::host::HostHandler for #handler_ident {
592 fn call<'a>(
593 &'a self,
594 __lingxia_lxapp: std::sync::Arc<::lingxia::LxApp>,
595 __lingxia_input: Option<String>,
596 __lingxia_cancel: ::lingxia::host::HostCancel,
597 ) -> ::lingxia::host::HostFuture<'a> {
598 Box::pin(async move {
599 let (__lingxia_stream, __lingxia_rx) =
600 ::lingxia::host::new_stream_context::<#event_ty, #result_ty>(__lingxia_cancel);
601 let __lingxia_error_tx = __lingxia_stream.error_sender();
602
603 ::lingxia::tokio::task::spawn(async move {
604 let __lingxia_result: ::lingxia::host::HostResult<()> = {
605 let __lingxia_lxapp = __lingxia_lxapp;
606 let __lingxia_input = __lingxia_input;
607 let __lingxia_stream = __lingxia_stream;
608 #call_expr
609 };
610 if let Err(err) = __lingxia_result {
611 let _ = __lingxia_error_tx.send(Err(err));
612 }
613 });
614
615 Ok(::lingxia::host::stream_output_from_rx(__lingxia_rx))
616 })
617 }
618 }
619
620 #[doc(hidden)]
621 pub fn #helper_ident() -> ::lingxia::host::HostRegistrationEntry {
622 ::lingxia::host::HostRegistrationEntry::Handler(
623 ::lingxia::host::HostRegistration::stream(
624 #namespace_lit,
625 #method_lit,
626 std::sync::Arc::new(#handler_ident),
627 )
628 )
629 }
630 }
631}
632
633struct ChannelFnPlan {
636 has_lxapp: bool,
637 input_ty: Option<Type>,
638 inbound_ty: Type,
639 outbound_ty: Type,
640}
641
642impl ChannelFnPlan {
643 fn from_fn(input_fn: &ItemFn) -> syn::Result<Self> {
644 let inputs = &input_fn.sig.inputs;
645
646 let Some(last) = inputs.last() else {
648 return Err(syn::Error::new(
649 proc_macro2::Span::call_site(),
650 "#[host(..., channel)] function must take `ChannelContext` as its last argument",
651 ));
652 };
653 let FnArg::Typed(last_arg) = last else {
654 return Err(syn::Error::new_spanned(
655 last,
656 "#[host] does not support methods with a receiver",
657 ));
658 };
659 if !type_is_channel_context(&last_arg.ty) {
660 return Err(syn::Error::new_spanned(
661 last,
662 "last argument of a #[host(..., channel)] function must be `ChannelContext`",
663 ));
664 }
665
666 let (inbound_ty, outbound_ty) = parse_channel_context_types(&last_arg.ty)?;
667
668 let mut has_lxapp = false;
669 let mut input_ty = None;
670 let prefix_count = inputs.len() - 1;
671
672 for (index, arg) in inputs.iter().take(prefix_count).enumerate() {
673 let FnArg::Typed(arg) = arg else {
674 return Err(syn::Error::new_spanned(
675 arg,
676 "#[host] does not support methods with a receiver",
677 ));
678 };
679 if index == 0 && is_lxapp_arg(arg) {
680 has_lxapp = true;
681 continue;
682 }
683 if input_ty.is_some() {
684 return Err(syn::Error::new_spanned(
685 arg,
686 "#[host(channel)] functions support at most one JSON payload argument",
687 ));
688 }
689 input_ty = Some((*arg.ty).clone());
690 }
691
692 Ok(Self {
693 has_lxapp,
694 input_ty,
695 inbound_ty,
696 outbound_ty,
697 })
698 }
699
700 fn call_expr(&self, fn_ident: &syn::Ident, is_async: bool) -> proc_macro2::TokenStream {
701 let mut args: Vec<proc_macro2::TokenStream> = Vec::new();
702 let mut prelude: Vec<proc_macro2::TokenStream> = Vec::new();
703
704 if self.has_lxapp {
705 args.push(quote! { __lingxia_lxapp });
706 }
707
708 if let Some(input_ty) = &self.input_ty {
709 prelude.push(quote! {
710 let __lingxia_payload: #input_ty =
711 match ::lingxia::host::parse_input(__lingxia_input.as_deref()) {
712 Ok(v) => v,
713 Err(e) => {
714 __lingxia_ctx.close_with("INVALID_PARAMS", e.to_string());
715 return;
716 }
717 };
718 });
719 args.push(quote! { __lingxia_payload });
720 }
721
722 args.push(quote! { __lingxia_ctx });
723
724 if is_async {
725 quote! {
726 {
727 #(#prelude)*
728 #fn_ident(#(#args),*).await
729 }
730 }
731 } else {
732 quote! {
733 {
734 #(#prelude)*
735 #fn_ident(#(#args),*)
736 }
737 }
738 }
739 }
740}
741
742fn expand_channel(
743 route_lit: LitStr,
744 namespace: &str,
745 method: &str,
746 input_fn: ItemFn,
747) -> proc_macro2::TokenStream {
748 let fn_ident = input_fn.sig.ident.clone();
749 let helper_ident = format_ident!("{}_host", fn_ident);
750 let handler_ident = format_ident!("__LingxiaChannelHandler_{}", fn_ident);
751 let namespace_lit = LitStr::new(namespace, route_lit.span());
752 let method_lit = LitStr::new(method, route_lit.span());
753
754 let plan = match ChannelFnPlan::from_fn(&input_fn) {
755 Ok(p) => p,
756 Err(err) => return err.to_compile_error(),
757 };
758
759 let call_expr = plan.call_expr(&fn_ident, input_fn.sig.asyncness.is_some());
760 let inbound_ty = &plan.inbound_ty;
761 let outbound_ty = &plan.outbound_ty;
762
763 quote! {
764 #input_fn
765
766 #[doc(hidden)]
767 #[allow(non_camel_case_types)]
768 pub struct #handler_ident;
769
770 impl ::lingxia::host::ChannelHandler for #handler_ident {
771 #[allow(unused_variables)]
772 fn on_open(
773 &self,
774 __lingxia_lxapp: std::sync::Arc<::lingxia::LxApp>,
775 __lingxia_ctx: ::lingxia::host::ChannelContext,
776 __lingxia_input: Option<String>,
777 ) {
778 ::lingxia::tokio::task::spawn(async move {
779 let __lingxia_ctx =
780 __lingxia_ctx.with_types::<#inbound_ty, #outbound_ty>();
781 #call_expr
782 });
783 }
784 }
785
786 #[doc(hidden)]
787 pub fn #helper_ident() -> ::lingxia::host::HostRegistrationEntry {
788 ::lingxia::host::HostRegistrationEntry::Channel(
789 ::lingxia::host::ChannelRegistration::new(
790 #namespace_lit,
791 #method_lit,
792 std::sync::Arc::new(#handler_ident),
793 )
794 )
795 }
796 }
797}