rpc_toolkit_macro_internals/command/
build.rs

1use std::collections::HashSet;
2
3use proc_macro2::*;
4use quote::*;
5use syn::fold::Fold;
6use syn::punctuated::Punctuated;
7use syn::spanned::Spanned;
8use syn::token::{Add, Comma, Where};
9
10use super::parse::*;
11use super::*;
12
13fn ctx_trait(ctx_ty: Option<Type>, opt: &mut Options) -> TokenStream {
14    let mut bounds: Punctuated<TypeParamBound, Add> = Punctuated::new();
15    bounds.push(macro_try!(parse2(quote! { ::rpc_toolkit::Context })));
16    let mut rpc_bounds = bounds.clone();
17    let mut cli_bounds = bounds;
18
19    let (use_cli, use_rpc) = match &opt.common().exec_ctx {
20        ExecutionContext::CliOnly(_) => (Some(None), false),
21        ExecutionContext::RpcOnly(_) | ExecutionContext::Standard => (None, true),
22        ExecutionContext::Local(_) => (Some(None), true),
23        ExecutionContext::CustomCli { context, .. } => (Some(Some(context.clone())), true),
24    };
25
26    if let Options::Parent(ParentOptions {
27        subcommands,
28        self_impl,
29        ..
30    }) = opt
31    {
32        if let Some(ctx_ty) = ctx_ty {
33            cli_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> })));
34            cli_bounds.push(macro_try!(parse2(quote! { Clone })));
35            rpc_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> })));
36            rpc_bounds.push(macro_try!(parse2(quote! { Clone })));
37        }
38        if let Some(SelfImplInfo { context, .. }) = self_impl {
39            if let Some(cli_ty) = use_cli.as_ref() {
40                if let Some(cli_ty) = cli_ty {
41                    cli_bounds.push(macro_try!(parse2(quote! { Into<#cli_ty> })));
42                } else {
43                    cli_bounds.push(macro_try!(parse2(quote! { Into<#context> })));
44                }
45            }
46            if use_rpc {
47                rpc_bounds.push(macro_try!(parse2(quote! { Into<#context> })));
48            }
49        }
50        for subcmd in subcommands {
51            let mut path = subcmd.clone();
52            std::mem::take(&mut path.segments.last_mut().unwrap().arguments);
53            cli_bounds.push(macro_try!(parse2(quote! { #path::CommandContextCli })));
54            rpc_bounds.push(macro_try!(parse2(quote! { #path::CommandContextRpc })));
55        }
56    } else {
57        if let Some(cli_ty) = use_cli.as_ref() {
58            if let Some(cli_ty) = cli_ty {
59                cli_bounds.push(macro_try!(parse2(quote! { Into<#cli_ty> })));
60            } else if let Some(ctx_ty) = &ctx_ty {
61                cli_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> })));
62            }
63        }
64        if use_rpc {
65            if let Some(ctx_ty) = &ctx_ty {
66                rpc_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> })));
67            }
68        }
69    }
70
71    let res = quote! {
72        pub trait CommandContextCli: #cli_bounds {}
73        impl<T> CommandContextCli for T where T: #cli_bounds {}
74
75        pub trait CommandContextRpc: #rpc_bounds {}
76        impl<T> CommandContextRpc for T where T: #rpc_bounds {}
77    };
78    res
79}
80
81fn metadata(full_options: &Options) -> TokenStream {
82    let options = match full_options {
83        Options::Leaf(a) => a,
84        Options::Parent(ParentOptions { common, .. }) => common,
85    };
86    let fallthrough = |ty: &str| {
87        let getter_name = Ident::new(&format!("get_{}", ty), Span::call_site());
88        match &*full_options {
89            Options::Parent(ParentOptions { subcommands, .. }) => {
90                let subcmd_handler = subcommands.iter().map(|subcmd| {
91                    let mut subcmd = subcmd.clone();
92                    subcmd.segments.last_mut().unwrap().arguments = PathArguments::None;
93                    quote_spanned!{ subcmd.span() =>
94                        [#subcmd::NAME, rest] => if let Some(val) = #subcmd::Metadata.#getter_name(rest, key) {
95                            return Some(val);
96                        },
97                    }
98                });
99                quote! {
100                    if !command.is_empty() {
101                        match command.splitn(2, ".").chain(std::iter::repeat("")).take(2).collect::<Vec<_>>().as_slice() {
102                            #(
103                                #subcmd_handler
104                            )*
105                            _ => ()
106                        }
107                    }
108                }
109            }
110            _ => quote! {},
111        }
112    };
113    fn impl_getter<I: Iterator<Item = TokenStream>>(
114        ty: &str,
115        metadata: I,
116        fallthrough: TokenStream,
117    ) -> TokenStream {
118        let getter_name = Ident::new(&format!("get_{}", ty), Span::call_site());
119        let ty: Type = syn::parse_str(ty).unwrap();
120        quote! {
121            fn #getter_name(&self, command: &str, key: &str) -> Option<#ty> {
122                #fallthrough
123                match key {
124                    #(#metadata)*
125                    _ => None,
126                }
127            }
128        }
129    }
130    let bool_metadata = options
131        .metadata
132        .iter()
133        .filter(|(_, lit)| matches!(lit, Lit::Bool(_)))
134        .map(|(name, value)| {
135            let name = LitStr::new(&name.to_string(), name.span());
136            quote! {
137                #name => Some(#value),
138            }
139        });
140    let number_metadata = |ty: &str| {
141        let ty: Type = syn::parse_str(ty).unwrap();
142        options
143            .metadata
144            .iter()
145            .filter(|(_, lit)| matches!(lit, Lit::Int(_) | Lit::Float(_) | Lit::Byte(_)))
146            .map(move |(name, value)| {
147                let name = LitStr::new(&name.to_string(), name.span());
148                quote! {
149                    #name => Some(#value as #ty),
150                }
151            })
152    };
153    let char_metadata = options
154        .metadata
155        .iter()
156        .filter(|(_, lit)| matches!(lit, Lit::Char(_)))
157        .map(|(name, value)| {
158            let name = LitStr::new(&name.to_string(), name.span());
159            quote! {
160                #name => Some(#value),
161            }
162        });
163    let str_metadata = options
164        .metadata
165        .iter()
166        .filter(|(_, lit)| matches!(lit, Lit::Str(_)))
167        .map(|(name, value)| {
168            let name = LitStr::new(&name.to_string(), name.span());
169            quote! {
170                #name => Some(#value),
171            }
172        });
173    let bstr_metadata = options
174        .metadata
175        .iter()
176        .filter(|(_, lit)| matches!(lit, Lit::ByteStr(_)))
177        .map(|(name, value)| {
178            let name = LitStr::new(&name.to_string(), name.span());
179            quote! {
180                #name => Some(#value),
181            }
182        });
183
184    let bool_getter = impl_getter("bool", bool_metadata, fallthrough("bool"));
185    let u8_getter = impl_getter("u8", number_metadata("u8"), fallthrough("u8"));
186    let u16_getter = impl_getter("u16", number_metadata("u16"), fallthrough("u16"));
187    let u32_getter = impl_getter("u32", number_metadata("u32"), fallthrough("u32"));
188    let u64_getter = impl_getter("u64", number_metadata("u64"), fallthrough("u64"));
189    let usize_getter = impl_getter("usize", number_metadata("usize"), fallthrough("usize"));
190    let i8_getter = impl_getter("i8", number_metadata("i8"), fallthrough("i8"));
191    let i16_getter = impl_getter("i16", number_metadata("i16"), fallthrough("i16"));
192    let i32_getter = impl_getter("i32", number_metadata("i32"), fallthrough("i32"));
193    let i64_getter = impl_getter("i64", number_metadata("i64"), fallthrough("i64"));
194    let isize_getter = impl_getter("isize", number_metadata("isize"), fallthrough("isize"));
195    let f32_getter = impl_getter("f32", number_metadata("f32"), fallthrough("f32"));
196    let f64_getter = impl_getter("f64", number_metadata("f64"), fallthrough("f64"));
197    let char_getter = impl_getter("char", char_metadata, fallthrough("char"));
198    let str_fallthrough = fallthrough("str");
199    let str_getter = quote! {
200        fn get_str(&self, command: &str, key: &str) -> Option<&'static str> {
201            #str_fallthrough
202            match key {
203                #(#str_metadata)*
204                _ => None,
205            }
206        }
207    };
208    let bstr_fallthrough = fallthrough("bstr");
209    let bstr_getter = quote! {
210        fn get_bstr(&self, command: &str, key: &str) -> Option<&'static [u8]> {
211            #bstr_fallthrough
212            match key {
213                #(#bstr_metadata)*
214                _ => None,
215            }
216        }
217    };
218
219    let res = quote! {
220        #[derive(Clone, Copy, Default)]
221        pub struct Metadata;
222
223        #[allow(overflowing_literals)]
224        impl ::rpc_toolkit::Metadata for Metadata {
225            #bool_getter
226            #u8_getter
227            #u16_getter
228            #u32_getter
229            #u64_getter
230            #usize_getter
231            #i8_getter
232            #i16_getter
233            #i32_getter
234            #i64_getter
235            #isize_getter
236            #f32_getter
237            #f64_getter
238            #char_getter
239            #str_getter
240            #bstr_getter
241        }
242    };
243    // panic!("{}", res);
244    res
245}
246
247fn build_app(name: LitStr, opt: &mut Options, params: &mut [ParamType]) -> TokenStream {
248    let about = opt.common().about.clone().into_iter();
249    let (subcommand, subcommand_required) = if let Options::Parent(opt) = opt {
250        (
251            opt.subcommands
252                .iter()
253                .map(|subcmd| {
254                    let mut path = subcmd.clone();
255                    path.segments.last_mut().unwrap().arguments = PathArguments::None;
256                    path
257                })
258                .collect(),
259            opt.self_impl.is_none(),
260        )
261    } else {
262        (Vec::new(), false)
263    };
264    let arg = params
265        .iter_mut()
266        .filter_map(|param| {
267            if let ParamType::Arg(arg) = param {
268                if arg.stdin.is_some() {
269                    return None;
270                }
271                let name = arg.name.clone().unwrap();
272                let name_str = arg.rename.clone().unwrap_or_else(|| LitStr::new(&name.to_string(), name.span()));
273                let help = arg.help.clone().into_iter();
274                let short = arg.short.clone().into_iter();
275                let long = arg.long.clone().into_iter();
276                let mut modifications = TokenStream::default();
277                let ty_span = arg.ty.span();
278                if let Type::Path(p) = &mut arg.ty {
279                    if p.path.is_ident("bool")
280                        && arg.parse.is_none()
281                        && (arg.short.is_some() || arg.long.is_some())
282                    {
283                        arg.check_is_present = true;
284                        modifications.extend(quote_spanned! { ty_span =>
285                            arg = arg.takes_value(false);
286                        });
287                    } else if arg.count.is_some() {
288                        modifications.extend(quote_spanned! { ty_span =>
289                            arg = arg.takes_value(false);
290                            arg = arg.multiple(true);
291                        });
292                    } else {
293                        modifications.extend(quote_spanned! { ty_span =>
294                            arg = arg.takes_value(true);
295                        });
296                        if let Some(_) = &arg.default {
297                            modifications.extend(quote_spanned! { ty_span =>
298                                arg = arg.required(false);
299                            });
300                        } else if p.path.segments.last().unwrap().ident == "Option" {
301                            arg.optional = true;
302                            modifications.extend(quote_spanned! { ty_span =>
303                                arg = arg.required(false);
304                            });
305                        } else if arg.multiple.is_some() {
306                            modifications.extend(quote_spanned! { ty_span =>
307                                arg = arg.multiple(true);
308                            });
309                        } else {
310                            modifications.extend(quote_spanned! { ty_span =>
311                                arg = arg.required(true);
312                            });
313                        }
314                    }
315                };
316                Some(quote! {
317                    {
318                        let mut arg = ::rpc_toolkit::command_helpers::prelude::Arg::with_name(#name_str);
319                        #(
320                            arg = arg.help(#help);
321                        )*
322                        #(
323                            arg = arg.short(#short);
324                        )*
325                        #(
326                            arg = arg.long(#long);
327                        )*
328                        #modifications
329
330                        arg
331                    }
332                })
333            } else {
334                None
335            }
336        })
337        .collect::<Vec<_>>();
338    let required = LitBool::new(subcommand_required, Span::call_site());
339    let alias = &opt.common().aliases;
340    quote! {
341        pub fn build_app() -> ::rpc_toolkit::command_helpers::prelude::App<'static> {
342            let mut app = ::rpc_toolkit::command_helpers::prelude::App::new(#name);
343            #(
344                app = app.about(#about);
345            )*
346            #(
347                app = app.alias(#alias);
348            )*
349            #(
350                app = app.arg(#arg);
351            )*
352            #(
353                app = app.subcommand(#subcommand::build_app());
354            )*
355            if #required {
356                app = app.setting(::rpc_toolkit::command_helpers::prelude::AppSettings::SubcommandRequired);
357            }
358            app
359        }
360    }
361}
362
363struct GenericFilter<'a> {
364    src: &'a Generics,
365    lifetimes: HashSet<Lifetime>,
366    types: HashSet<Ident>,
367}
368impl<'a> GenericFilter<'a> {
369    fn new(src: &'a Generics) -> Self {
370        GenericFilter {
371            src,
372            lifetimes: HashSet::new(),
373            types: HashSet::new(),
374        }
375    }
376    fn finish(self) -> Generics {
377        let mut params: Punctuated<GenericParam, Comma> = Default::default();
378        let mut where_clause = self
379            .src
380            .where_clause
381            .as_ref()
382            .map(|wc| WhereClause {
383                where_token: wc.where_token,
384                predicates: Default::default(),
385            })
386            .unwrap_or_else(|| WhereClause {
387                where_token: Where(Span::call_site()),
388                predicates: Default::default(),
389            });
390        for src_param in &self.src.params {
391            match src_param {
392                GenericParam::Lifetime(l) if self.lifetimes.contains(&l.lifetime) => {
393                    params.push(src_param.clone())
394                }
395                GenericParam::Type(t) if self.types.contains(&t.ident) => {
396                    params.push(src_param.clone())
397                }
398                _ => (),
399            }
400        }
401        for src_predicate in self.src.where_clause.iter().flat_map(|wc| &wc.predicates) {
402            match src_predicate {
403                WherePredicate::Lifetime(l) if self.lifetimes.contains(&l.lifetime) => {
404                    where_clause.predicates.push(src_predicate.clone())
405                }
406                WherePredicate::Type(PredicateType {
407                    bounded_ty: Type::Path(t),
408                    ..
409                }) if self.types.contains(&t.path.segments.last().unwrap().ident) => {
410                    where_clause.predicates.push(src_predicate.clone())
411                }
412                _ => (),
413            }
414        }
415        Generics {
416            lt_token: if params.is_empty() {
417                None
418            } else {
419                self.src.lt_token.clone()
420            },
421            gt_token: if params.is_empty() {
422                None
423            } else {
424                self.src.gt_token.clone()
425            },
426            params,
427            where_clause: if where_clause.predicates.is_empty() {
428                None
429            } else {
430                Some(where_clause)
431            },
432        }
433    }
434}
435impl<'a> Fold for GenericFilter<'a> {
436    fn fold_lifetime(&mut self, i: Lifetime) -> Lifetime {
437        self.lifetimes
438            .extend(self.src.params.iter().filter_map(|param| match param {
439                GenericParam::Lifetime(l) if l.lifetime == i => Some(l.lifetime.clone()),
440                _ => None,
441            }));
442        i
443    }
444    fn fold_type(&mut self, i: Type) -> Type {
445        self.types.extend(
446            self.src
447                .params
448                .iter()
449                .filter_map(|param| match (param, &i) {
450                    (GenericParam::Type(t), Type::Path(i)) if i.path.is_ident(&t.ident) => {
451                        Some(t.ident.clone())
452                    }
453                    _ => None,
454                }),
455        );
456        i
457    }
458}
459
460fn rpc_handler(
461    fn_name: &Ident,
462    fn_generics: &Generics,
463    opt: &Options,
464    params: &[ParamType],
465) -> TokenStream {
466    let mut parent_data_ty = quote! { () };
467    let mut generics = fn_generics.clone();
468    generics.params.push(macro_try!(syn::parse2(
469        quote! { GenericContext: CommandContextRpc }
470    )));
471    if generics.lt_token.is_none() {
472        generics.lt_token = Some(Default::default());
473    }
474    if generics.gt_token.is_none() {
475        generics.gt_token = Some(Default::default());
476    }
477    let mut param_def = Vec::new();
478    for param in params {
479        match param {
480            ParamType::Arg(arg) => {
481                let name = arg.name.clone().unwrap();
482                let rename = arg
483                    .rename
484                    .clone()
485                    .unwrap_or_else(|| LitStr::new(&name.to_string(), name.span()));
486                let field_name = Ident::new(&format!("arg_{}", name), name.span());
487                let ty = arg.ty.clone();
488                let def = quote! {
489                    #[serde(rename = #rename)]
490                    #field_name: #ty,
491                };
492                let def = match &arg.default {
493                    Some(Some(default)) => {
494                        quote! {
495                            #[serde(default = #default)]
496                            #def
497                        }
498                    }
499                    Some(None) => {
500                        quote! {
501                            #[serde(default)]
502                            #def
503                        }
504                    }
505                    None => def,
506                };
507                param_def.push(def);
508            }
509            ParamType::ParentData(ty) => parent_data_ty = quote! { #ty },
510            _ => (),
511        }
512    }
513    let (_, fn_type_generics, _) = fn_generics.split_for_impl();
514    let fn_turbofish = fn_type_generics.as_turbofish();
515    let fn_path: Path = macro_try!(syn::parse2(quote! { super::#fn_name#fn_turbofish }));
516    let mut param_generics_filter = GenericFilter::new(fn_generics);
517    for param in params {
518        if let ParamType::Arg(a) = param {
519            param_generics_filter.fold_type(a.ty.clone());
520        }
521    }
522    let param_generics = param_generics_filter.finish();
523    let (_, param_ty_generics, _) = param_generics.split_for_impl();
524    let param_struct_def = quote! {
525        #[allow(dead_code)]
526        #[derive(::rpc_toolkit::command_helpers::prelude::Deserialize)]
527        pub struct Params#param_ty_generics {
528            #(
529                #param_def
530            )*
531            #[serde(flatten)]
532            #[serde(default)]
533            rest: ::rpc_toolkit::command_helpers::prelude::Value,
534        }
535    };
536    let param = params.iter().map(|param| match param {
537        ParamType::Arg(arg) => {
538            let name = arg.name.clone().unwrap();
539            let field_name = Ident::new(&format!("arg_{}", name), name.span());
540            quote! { args.#field_name }
541        }
542        ParamType::Context(ty) => {
543            if matches!(opt, Options::Parent { .. }) {
544                quote! { <GenericContext as Into<#ty>>::into(ctx.clone()) }
545            } else {
546                quote! { <GenericContext as Into<#ty>>::into(ctx) }
547            }
548        }
549        ParamType::ParentData(_) => {
550            quote! { parent_data }
551        }
552        ParamType::Request => quote! { request },
553        ParamType::Response => quote! { response },
554        ParamType::None => unreachable!(),
555    });
556    match opt {
557        Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::CliOnly(_)) => quote! {
558            #param_struct_def
559
560            pub async fn rpc_handler#generics(
561                _ctx: GenericContext,
562                _parent_data: #parent_data_ty,
563                _request: &::rpc_toolkit::command_helpers::prelude::RequestParts,
564                _response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts,
565                method: &str,
566                _args: Params#param_ty_generics,
567            ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> {
568                Err(::rpc_toolkit::command_helpers::prelude::RpcError {
569                    data: Some(method.into()),
570                    ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR
571                })
572            }
573        },
574        Options::Leaf(opt) => {
575            let invocation = if opt.is_async {
576                quote! {
577                    #fn_path(#(#param),*).await?
578                }
579            } else if opt.blocking.is_some() {
580                quote! {
581                    ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #fn_path(#(#param),*)).await?
582                }
583            } else {
584                quote! {
585                    #fn_path(#(#param),*)?
586                }
587            };
588            quote! {
589                #param_struct_def
590
591                pub async fn rpc_handler#generics(
592                    ctx: GenericContext,
593                    parent_data: #parent_data_ty,
594                    request: &::rpc_toolkit::command_helpers::prelude::RequestParts,
595                    response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts,
596                    method: &str,
597                    args: Params#param_ty_generics,
598                ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> {
599                    if method.is_empty() {
600                        Ok(::rpc_toolkit::command_helpers::prelude::to_value(#invocation)?)
601                    } else {
602                        Err(::rpc_toolkit::command_helpers::prelude::RpcError {
603                            data: Some(method.into()),
604                            ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR
605                        })
606                    }
607                }
608            }
609        }
610        Options::Parent(ParentOptions {
611            common,
612            subcommands,
613            self_impl,
614        }) => {
615            let cmd_preprocess = if common.is_async {
616                quote! {
617                    let parent_data = #fn_path(#(#param),*).await?;
618                }
619            } else if common.blocking.is_some() {
620                quote! {
621                    let parent_data = ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #fn_path(#(#param),*)).await?;
622                }
623            } else {
624                quote! {
625                    let parent_data = #fn_path(#(#param),*)?;
626                }
627            };
628            let subcmd_impl = subcommands.iter().map(|subcommand| {
629                let mut subcommand = subcommand.clone();
630                let mut rpc_handler = PathSegment {
631                    ident: Ident::new("rpc_handler", Span::call_site()),
632                    arguments: std::mem::replace(
633                        &mut subcommand.segments.last_mut().unwrap().arguments,
634                        PathArguments::None,
635                    ),
636                };
637                rpc_handler.arguments = match rpc_handler.arguments {
638                    PathArguments::None => PathArguments::AngleBracketed(
639                        syn::parse2(quote! { ::<GenericContext> })
640                            .unwrap(),
641                    ),
642                    PathArguments::AngleBracketed(mut a) => {
643                        a.args.push(syn::parse2(quote! { GenericContext }).unwrap());
644                        PathArguments::AngleBracketed(a)
645                    }
646                    _ => unreachable!(),
647                };
648                quote_spanned!{ subcommand.span() =>
649                    [#subcommand::NAME, rest] => #subcommand::#rpc_handler(ctx, parent_data, request, response, rest, ::rpc_toolkit::command_helpers::prelude::from_value(args.rest)?).await
650                }
651            });
652            let subcmd_impl = quote! {
653                match method.splitn(2, ".").chain(std::iter::repeat("")).take(2).collect::<Vec<_>>().as_slice() {
654                    #(
655                        #subcmd_impl,
656                    )*
657                    _ => Err(::rpc_toolkit::command_helpers::prelude::RpcError {
658                        data: Some(method.into()),
659                        ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR
660                    })
661                }
662            };
663            match self_impl {
664                Some(self_impl) if !matches!(common.exec_ctx, ExecutionContext::CliOnly(_)) => {
665                    let self_impl_fn = &self_impl.path;
666                    let self_impl = if self_impl.is_async {
667                        quote_spanned! { self_impl_fn.span() =>
668                            #self_impl_fn(Into::into(ctx), parent_data).await?
669                        }
670                    } else if self_impl.blocking {
671                        quote_spanned! { self_impl_fn.span() =>
672                            {
673                                let ctx = Into::into(ctx);
674                                ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #self_impl_fn(ctx, parent_data)).await?
675                            }
676                        }
677                    } else {
678                        quote_spanned! { self_impl_fn.span() =>
679                            #self_impl_fn(Into::into(ctx), parent_data)?
680                        }
681                    };
682                    quote! {
683                        #param_struct_def
684
685                        pub async fn rpc_handler#generics(
686                            ctx: GenericContext,
687                            parent_data: #parent_data_ty,
688                            request: &::rpc_toolkit::command_helpers::prelude::RequestParts,
689                            response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts,
690                            method: &str,
691                            args: Params#param_ty_generics,
692                        ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> {
693                            #cmd_preprocess
694
695                            if method.is_empty() {
696                                Ok(::rpc_toolkit::command_helpers::prelude::to_value(&#self_impl)?)
697                            } else {
698                                #subcmd_impl
699                            }
700                        }
701                    }
702                }
703                _ => {
704                    quote! {
705                        #param_struct_def
706
707                        pub async fn rpc_handler#generics(
708                            ctx: GenericContext,
709                            parent_data: #parent_data_ty,
710                            request: &::rpc_toolkit::command_helpers::prelude::RequestParts,
711                            response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts,
712                            method: &str,
713                            args: Params#param_ty_generics,
714                        ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> {
715                            #cmd_preprocess
716
717                            #subcmd_impl
718                        }
719                    }
720                }
721            }
722        }
723    }
724}
725
726fn cli_handler(
727    fn_name: &Ident,
728    fn_generics: &Generics,
729    opt: &mut Options,
730    params: &[ParamType],
731) -> TokenStream {
732    let mut parent_data_ty = quote! { () };
733    let mut generics = fn_generics.clone();
734    generics.params.push(macro_try!(syn::parse2(
735        quote! { ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize }
736    )));
737    generics.params.push(macro_try!(syn::parse2(
738        quote! { GenericContext: CommandContextCli }
739    )));
740    if generics.lt_token.is_none() {
741        generics.lt_token = Some(Default::default());
742    }
743    if generics.gt_token.is_none() {
744        generics.gt_token = Some(Default::default());
745    }
746    let (_, fn_type_generics, _) = fn_generics.split_for_impl();
747    let fn_turbofish = fn_type_generics.as_turbofish();
748    let fn_path: Path = macro_try!(syn::parse2(quote! { super::#fn_name#fn_turbofish }));
749    let is_parent = matches!(opt, Options::Parent { .. });
750    let param: Vec<_> = params
751        .iter()
752        .map(|param| match param {
753            ParamType::Arg(arg) => {
754                let name = arg.name.clone().unwrap();
755                let field_name = Ident::new(&format!("arg_{}", name), name.span());
756                quote! { params.#field_name.clone() }
757            }
758            ParamType::Context(ty) => {
759                if is_parent {
760                    quote! { <GenericContext as Into<#ty>>::into(ctx.clone()) }
761                } else {
762                    quote! { <GenericContext as Into<#ty>>::into(ctx) }
763                }
764            }
765            ParamType::ParentData(ty) => {
766                parent_data_ty = quote! { #ty };
767                quote! { parent_data }
768            }
769            ParamType::Request => quote! { request },
770            ParamType::Response => quote! { response },
771            ParamType::None => unreachable!(),
772        })
773        .collect();
774    let mut param_generics_filter = GenericFilter::new(fn_generics);
775    for param in params {
776        if let ParamType::Arg(a) = param {
777            param_generics_filter.fold_type(a.ty.clone());
778        }
779    }
780    let mut param_generics = param_generics_filter.finish();
781    param_generics.params.push(macro_try!(syn::parse2(quote! {
782        ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize
783    })));
784    if param_generics.lt_token.is_none() {
785        param_generics.lt_token = Some(Default::default());
786    }
787    if param_generics.gt_token.is_none() {
788        param_generics.gt_token = Some(Default::default());
789    }
790    let (_, param_ty_generics, _) = param_generics.split_for_impl();
791    let mut arg_def = Vec::new();
792    for param in params {
793        match param {
794            ParamType::Arg(arg) => {
795                let name = arg.name.clone().unwrap();
796                let rename = arg
797                    .rename
798                    .clone()
799                    .unwrap_or_else(|| LitStr::new(&name.to_string(), name.span()));
800                let field_name = Ident::new(&format!("arg_{}", name), name.span());
801                let ty = arg.ty.clone();
802                arg_def.push(quote! {
803                    #[serde(rename = #rename)]
804                    #field_name: #ty,
805                })
806            }
807            _ => (),
808        }
809    }
810    let arg = params
811        .iter()
812        .filter_map(|param| {
813            if let ParamType::Arg(a) = param {
814                Some(a)
815            } else {
816                None
817            }
818        })
819        .map(|arg| {
820            let name = arg.name.clone().unwrap();
821            let arg_name = arg.rename.clone().unwrap_or_else(|| LitStr::new(&name.to_string(), name.span()));
822            let field_name = Ident::new(&format!("arg_{}", name), name.span());
823            if arg.stdin.is_some() {
824                if let Some(parse) = &arg.parse {
825                    quote! {
826                        #field_name: #parse(&mut std::io::stdin(), matches)?,
827                    }
828                } else {
829                    quote! {
830                        #field_name: ::rpc_toolkit::command_helpers::prelude::default_stdin_parser(&mut std::io::stdin(), matches)?,
831                    }
832                }
833            } else if arg.check_is_present {
834                quote! {
835                    #field_name: matches.is_present(#arg_name),
836                }
837            } else if arg.count.is_some() {
838                quote! {
839                    #field_name: matches.occurrences_of(#arg_name),
840                }
841            } else {
842                let parse_val = if let Some(parse) = &arg.parse {
843                    quote! {
844                        #parse(arg_val, matches)
845                    }
846                } else {
847                    quote! {
848                        ::rpc_toolkit::command_helpers::prelude::default_arg_parser(arg_val, matches)
849                    }
850                };
851                if arg.optional {
852                    quote! {
853                        #field_name: if let Some(arg_val) = matches.value_of(#arg_name) {
854                            Some(#parse_val?)
855                        } else {
856                            None
857                        },
858                    }
859                } else if let Some(default) = &arg.default {
860                    if let Some(default) = default {
861                        let path: Path = match syn::parse_str(&default.value()) {
862                            Ok(a) => a,
863                            Err(e) => return e.into_compile_error(),
864                        };
865                        quote! {
866                            #field_name: if let Some(arg_val) = matches.value_of(#arg_name) {
867                                #parse_val?
868                            } else {
869                                #path()
870                            },
871                        }
872                    } else {
873                        quote! {
874                            #field_name: if let Some(arg_val) = matches.value_of(#arg_name) {
875                                #parse_val?
876                            } else {
877                                Default::default()
878                            },
879                        }
880                    }
881                } else if arg.multiple.is_some() {
882                    quote! {
883                        #field_name: matches.values_of(#arg_name).iter().flatten().map(|arg_val| #parse_val).collect::<Result<_, _>>()?,
884                    }
885                } else {
886                    quote! {
887                        #field_name: {
888                            let arg_val = matches.value_of(#arg_name).unwrap();
889                            #parse_val?
890                        },
891                    }
892                }
893            }
894        });
895    let param_struct_def = quote! {
896        #[derive(::rpc_toolkit::command_helpers::prelude::Serialize)]
897        struct Params#param_ty_generics {
898            #(
899                #arg_def
900            )*
901            #[serde(flatten)]
902            rest: ParentParams,
903        }
904        let params: Params#param_ty_generics = Params {
905            #(
906                #arg
907            )*
908            rest: parent_params,
909        };
910    };
911    let create_rt = quote! {
912        let rt_ref = if let Some(rt) = rt.as_mut() {
913            &*rt
914        } else {
915            rt = Some(::rpc_toolkit::command_helpers::prelude::Runtime::new().map_err(|e| ::rpc_toolkit::command_helpers::prelude::RpcError {
916                data: Some(format!("{}", e).into()),
917                ..::rpc_toolkit::command_helpers::prelude::yajrc::INTERNAL_ERROR
918            })?);
919            rt.as_ref().unwrap()
920        };
921    };
922    let display = if let Some(display) = &opt.common().display {
923        quote! { #display }
924    } else {
925        quote! { ::rpc_toolkit::command_helpers::prelude::default_display }
926    };
927    match opt {
928        Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::RpcOnly(_)) => quote! {
929            pub fn cli_handler#generics(
930                _ctx: GenericContext,
931                _parent_data: #parent_data_ty,
932                _rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>,
933                _matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches,
934                method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>,
935                _parent_params: ParentParams,
936            ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> {
937                Err(::rpc_toolkit::command_helpers::prelude::RpcError {
938                    data: Some(method.into()),
939                    ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR
940                })
941            }
942        },
943        Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::Standard) => {
944            let param = param.into_iter().map(|_| quote! { unreachable!() });
945            let invocation = if opt.is_async {
946                quote! {
947                    rt_ref.block_on(#fn_path(#(#param),*))?
948                }
949            } else {
950                quote! {
951                    #fn_path(#(#param),*)?
952                }
953            };
954            quote! {
955                pub fn cli_handler#generics(
956                    ctx: GenericContext,
957                    parent_data: #parent_data_ty,
958                    mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>,
959                    matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches,
960                    method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>,
961                    parent_params: ParentParams,
962                ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> {
963                    #param_struct_def
964
965                    #create_rt
966
967                    #[allow(unreachable_code)]
968                    let return_ty = if true {
969                        ::rpc_toolkit::command_helpers::prelude::PhantomData
970                    } else {
971                        let ctx_new = unreachable!();
972                        ::rpc_toolkit::command_helpers::prelude::match_types(&ctx, &ctx_new);
973                        let ctx = ctx_new;
974                        ::rpc_toolkit::command_helpers::prelude::make_phantom(#invocation)
975                    };
976
977                    let res = rt_ref.block_on(::rpc_toolkit::command_helpers::prelude::call_remote(ctx, method.as_ref(), params, return_ty))?;
978                    Ok(#display(res.result?, matches))
979                }
980            }
981        }
982        Options::Leaf(opt) => {
983            if let ExecutionContext::CustomCli {
984                ref cli, is_async, ..
985            } = opt.exec_ctx
986            {
987                let fn_path = cli;
988                let cli_param = params.iter().filter_map(|param| match param {
989                    ParamType::Arg(arg) => {
990                        let name = arg.name.clone().unwrap();
991                        let field_name = Ident::new(&format!("arg_{}", name), name.span());
992                        Some(quote! { params.#field_name.clone() })
993                    }
994                    ParamType::Context(_) => Some(quote! { Into::into(ctx) }),
995                    ParamType::ParentData(_) => Some(quote! { parent_data }),
996                    ParamType::Request => None,
997                    ParamType::Response => None,
998                    ParamType::None => unreachable!(),
999                });
1000                let invocation = if is_async {
1001                    quote! {
1002                        rt_ref.block_on(#fn_path(#(#cli_param),*))?
1003                    }
1004                } else {
1005                    quote! {
1006                        #fn_path(#(#cli_param),*)?
1007                    }
1008                };
1009                let display_res = if let Some(display_fn) = &opt.display {
1010                    quote! {
1011                        #display_fn(#invocation, matches)
1012                    }
1013                } else {
1014                    quote! {
1015                        ::rpc_toolkit::command_helpers::prelude::default_display(#invocation, matches)
1016                    }
1017                };
1018                let rt_action = if is_async {
1019                    create_rt
1020                } else {
1021                    quote! {
1022                        drop(rt);
1023                    }
1024                };
1025                quote! {
1026                    pub fn cli_handler#generics(
1027                        ctx: GenericContext,
1028                        parent_data: #parent_data_ty,
1029                        mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>,
1030                        matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches,
1031                        _method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>,
1032                        parent_params: ParentParams
1033                    ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> {
1034                        #param_struct_def
1035
1036                        #rt_action
1037
1038                        Ok(#display_res)
1039                    }
1040                }
1041            } else {
1042                let invocation = if opt.is_async {
1043                    quote! {
1044                        rt_ref.block_on(#fn_path(#(#param),*))?
1045                    }
1046                } else {
1047                    quote! {
1048                        #fn_path(#(#param),*)?
1049                    }
1050                };
1051                let display_res = if let Some(display_fn) = &opt.display {
1052                    quote! {
1053                        #display_fn(#invocation, matches)
1054                    }
1055                } else {
1056                    quote! {
1057                        ::rpc_toolkit::command_helpers::prelude::default_display(#invocation, matches)
1058                    }
1059                };
1060                let rt_action = if opt.is_async {
1061                    create_rt
1062                } else {
1063                    quote! {
1064                        drop(rt);
1065                    }
1066                };
1067                quote! {
1068                    pub fn cli_handler#generics(
1069                        ctx: GenericContext,
1070                        parent_data: #parent_data_ty,
1071                        mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>,
1072                        matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches,
1073                        _method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>,
1074                        parent_params: ParentParams
1075                    ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> {
1076                        #param_struct_def
1077
1078                        #rt_action
1079
1080                        Ok(#display_res)
1081                    }
1082                }
1083            }
1084        }
1085        Options::Parent(ParentOptions {
1086            common,
1087            subcommands,
1088            self_impl,
1089        }) => {
1090            let cmd_preprocess = if common.is_async {
1091                quote! {
1092                    #create_rt
1093                    let parent_data = rt_ref.block_on(#fn_path(#(#param),*))?;
1094                }
1095            } else {
1096                quote! {
1097                    let parent_data = #fn_path(#(#param),*)?;
1098                }
1099            };
1100            let subcmd_impl = subcommands.iter().map(|subcommand| {
1101                let mut subcommand = subcommand.clone();
1102                let mut cli_handler = PathSegment {
1103                    ident: Ident::new("cli_handler", Span::call_site()),
1104                    arguments: std::mem::replace(
1105                        &mut subcommand.segments.last_mut().unwrap().arguments,
1106                        PathArguments::None,
1107                    ),
1108                };
1109                cli_handler.arguments = match cli_handler.arguments {
1110                    PathArguments::None => PathArguments::AngleBracketed(
1111                        syn::parse2(quote! { ::<Params#param_ty_generics, GenericContext> })
1112                            .unwrap(),
1113                    ),
1114                    PathArguments::AngleBracketed(mut a) => {
1115                        a.args
1116                            .push(syn::parse2(quote! { Params#param_ty_generics }).unwrap());
1117                        a.args.push(syn::parse2(quote! { GenericContext }).unwrap());
1118                        PathArguments::AngleBracketed(a)
1119                    }
1120                    _ => unreachable!(),
1121                };
1122                quote_spanned! { subcommand.span() =>
1123                    Some((#subcommand::NAME, sub_m)) => {
1124                        let method = if method.is_empty() {
1125                            #subcommand::NAME.into()
1126                        } else {
1127                            method + "." + #subcommand::NAME
1128                        };
1129                        #subcommand::#cli_handler(ctx, parent_data, rt, sub_m, method, params)
1130                    },
1131                }
1132            });
1133            let self_impl = match (self_impl, &common.exec_ctx) {
1134                (Some(self_impl), ExecutionContext::CliOnly(_))
1135                | (Some(self_impl), ExecutionContext::Local(_))
1136                | (Some(self_impl), ExecutionContext::CustomCli { .. }) => {
1137                    let (self_impl_fn, is_async) =
1138                        if let ExecutionContext::CustomCli { cli, is_async, .. } = &common.exec_ctx
1139                        {
1140                            (cli, *is_async)
1141                        } else {
1142                            (&self_impl.path, self_impl.is_async)
1143                        };
1144                    let create_rt = if common.is_async {
1145                        None
1146                    } else {
1147                        Some(create_rt)
1148                    };
1149                    let self_impl = if is_async {
1150                        quote_spanned! { self_impl_fn.span() =>
1151                            #create_rt
1152                            rt_ref.block_on(#self_impl_fn(Into::into(ctx), parent_data))?
1153                        }
1154                    } else {
1155                        quote_spanned! { self_impl_fn.span() =>
1156                            #self_impl_fn(Into::into(ctx), parent_data)?
1157                        }
1158                    };
1159                    quote! {
1160                        Ok(#display(#self_impl, matches)),
1161                    }
1162                }
1163                (Some(self_impl), ExecutionContext::Standard) => {
1164                    let self_impl_fn = &self_impl.path;
1165                    let self_impl = if self_impl.is_async {
1166                        quote! {
1167                            rt_ref.block_on(#self_impl_fn(unreachable!(), parent_data))
1168                        }
1169                    } else {
1170                        quote! {
1171                            #self_impl_fn(unreachable!(), parent_data)
1172                        }
1173                    };
1174                    let create_rt = if common.is_async {
1175                        None
1176                    } else {
1177                        Some(create_rt)
1178                    };
1179                    quote! {
1180                        {
1181                            #create_rt
1182
1183                            #[allow(unreachable_code)]
1184                            let return_ty = if true {
1185                                ::rpc_toolkit::command_helpers::prelude::PhantomData
1186                            } else {
1187                                ::rpc_toolkit::command_helpers::prelude::make_phantom(#self_impl?)
1188                            };
1189
1190                            let res = rt_ref.block_on(::rpc_toolkit::command_helpers::prelude::call_remote(ctx, method.as_ref(), params, return_ty))?;
1191                            Ok(#display(res.result?, matches))
1192                        }
1193                    }
1194                }
1195                (None, _) | (Some(_), ExecutionContext::RpcOnly(_)) => quote! {
1196                    Err(::rpc_toolkit::command_helpers::prelude::RpcError {
1197                        data: Some(method.into()),
1198                        ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR
1199                    }),
1200                },
1201            };
1202            quote! {
1203                pub fn cli_handler#generics(
1204                    ctx: GenericContext,
1205                    parent_data: #parent_data_ty,
1206                    mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>,
1207                    matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches,
1208                    method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>,
1209                    parent_params: ParentParams,
1210                ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> {
1211                    #param_struct_def
1212
1213                    #cmd_preprocess
1214
1215                    match matches.subcommand() {
1216                        #(
1217                            #subcmd_impl
1218                        )*
1219                        _ => #self_impl
1220                    }
1221                }
1222            }
1223        }
1224    }
1225}
1226
1227pub fn build(args: AttributeArgs, mut item: ItemFn) -> TokenStream {
1228    let mut params = macro_try!(parse_param_attrs(&mut item));
1229    let mut opt = macro_try!(parse_command_attr(args));
1230    if let Some(a) = &opt.common().blocking {
1231        if item.sig.asyncness.is_some() {
1232            return Error::new(a.span(), "cannot use `blocking` on an async fn").to_compile_error();
1233        }
1234    }
1235    opt.common().is_async = item.sig.asyncness.is_some();
1236    let fn_vis = &item.vis;
1237    let fn_name = &item.sig.ident;
1238    let fn_generics = &item.sig.generics;
1239    let command_name_str = opt
1240        .common()
1241        .rename
1242        .clone()
1243        .unwrap_or_else(|| LitStr::new(&fn_name.to_string(), fn_name.span()));
1244    let is_async = LitBool::new(
1245        opt.common().is_async,
1246        item.sig
1247            .asyncness
1248            .map(|a| a.span())
1249            .unwrap_or_else(Span::call_site),
1250    );
1251    let ctx_ty = params.iter().find_map(|a| {
1252        if let ParamType::Context(ty) = a {
1253            Some(ty.clone())
1254        } else {
1255            None
1256        }
1257    });
1258    let ctx_trait = ctx_trait(ctx_ty, &mut opt);
1259    let metadata = metadata(&mut opt);
1260    let build_app = build_app(command_name_str.clone(), &mut opt, &mut params);
1261    let rpc_handler = rpc_handler(fn_name, fn_generics, &opt, &params);
1262    let cli_handler = cli_handler(fn_name, fn_generics, &mut opt, &params);
1263
1264    let res = quote! {
1265        #item
1266        #fn_vis mod #fn_name {
1267            use super::*;
1268
1269            pub const NAME: &'static str = #command_name_str;
1270            pub const ASYNC: bool = #is_async;
1271
1272            #ctx_trait
1273
1274            #metadata
1275
1276            #build_app
1277
1278            #rpc_handler
1279
1280            #cli_handler
1281        }
1282    };
1283    if opt.common().macro_debug {
1284        panic!("EXPANDED MACRO:\n{}", res);
1285    }
1286    res
1287}