From 434d521c7484db3256fbbfe4188203e5b35d1ba5 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Wed, 27 Dec 2023 22:53:59 -0700 Subject: [PATCH] macros wip --- Cargo.lock | 37 + rpc-toolkit-macro-internals/Cargo.toml | 1 + .../src/command/build.rs | 2494 +++++++++-------- .../src/command/mod.rs | 15 +- .../src/command/parse.rs | 204 +- rpc-toolkit-macro-internals/src/lib.rs | 9 - .../src/rpc_handler/build.rs | 129 - .../src/rpc_handler/mod.rs | 12 - .../src/rpc_handler/parse.rs | 50 - .../src/rpc_server/build.rs | 25 - .../src/rpc_server/mod.rs | 5 - .../src/run_cli/build.rs | 81 - .../src/run_cli/mod.rs | 22 - .../src/run_cli/parse.rs | 68 - rpc-toolkit-macro/src/lib.rs | 18 - rpc-toolkit/Cargo.toml | 1 + rpc-toolkit/src/cli.rs | 263 +- rpc-toolkit/src/command_helpers.rs | 33 + rpc-toolkit/src/context.rs | 13 +- rpc-toolkit/src/handler.rs | 330 ++- rpc-toolkit/src/lib.rs | 1 + rpc-toolkit/src/server/socket.rs | 12 +- rpc-toolkit/src/util.rs | 52 +- rpc-toolkit/tests/compat.rs | 182 +- rpc-toolkit/tests/handler.rs | 153 +- 25 files changed, 2157 insertions(+), 2053 deletions(-) delete mode 100644 rpc-toolkit-macro-internals/src/rpc_handler/build.rs delete mode 100644 rpc-toolkit-macro-internals/src/rpc_handler/mod.rs delete mode 100644 rpc-toolkit-macro-internals/src/rpc_handler/parse.rs delete mode 100644 rpc-toolkit-macro-internals/src/rpc_server/build.rs delete mode 100644 rpc-toolkit-macro-internals/src/rpc_server/mod.rs delete mode 100644 rpc-toolkit-macro-internals/src/run_cli/build.rs delete mode 100644 rpc-toolkit-macro-internals/src/run_cli/mod.rs delete mode 100644 rpc-toolkit-macro-internals/src/run_cli/parse.rs create mode 100644 rpc-toolkit/src/command_helpers.rs diff --git a/Cargo.lock b/Cargo.lock index a2d76e7..5b323fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -251,6 +251,12 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -675,6 +681,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -888,6 +903,26 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1001,6 +1036,7 @@ dependencies = [ "lazy_format", "lazy_static", "openssl", + "pin-project", "reqwest", "rpc-toolkit-macro", "serde", @@ -1026,6 +1062,7 @@ dependencies = [ name = "rpc-toolkit-macro-internals" version = "0.2.2" dependencies = [ + "itertools", "proc-macro2", "quote", "syn 1.0.109", diff --git a/rpc-toolkit-macro-internals/Cargo.toml b/rpc-toolkit-macro-internals/Cargo.toml index 87552e5..dde611b 100644 --- a/rpc-toolkit-macro-internals/Cargo.toml +++ b/rpc-toolkit-macro-internals/Cargo.toml @@ -11,3 +11,4 @@ license = "MIT" proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0", features = ["full", "fold"] } +itertools = "0.12" diff --git a/rpc-toolkit-macro-internals/src/command/build.rs b/rpc-toolkit-macro-internals/src/command/build.rs index 2692af0..15d45dd 100644 --- a/rpc-toolkit-macro-internals/src/command/build.rs +++ b/rpc-toolkit-macro-internals/src/command/build.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; +use itertools::MultiUnzip; use proc_macro2::*; use quote::*; use syn::fold::Fold; @@ -10,355 +11,355 @@ use syn::token::{Add, Comma, Where}; use super::parse::*; use super::*; -fn ctx_trait(ctx_ty: Option, opt: &mut Options) -> TokenStream { - let mut bounds: Punctuated = Punctuated::new(); - bounds.push(macro_try!(parse2(quote! { ::rpc_toolkit::Context }))); - let mut rpc_bounds = bounds.clone(); - let mut cli_bounds = bounds; +// fn ctx_trait(ctx_ty: Option, opt: &mut Options) -> TokenStream { +// let mut bounds: Punctuated = Punctuated::new(); +// bounds.push(macro_try!(parse2(quote! { ::rpc_toolkit::Context }))); +// let mut rpc_bounds = bounds.clone(); +// let mut cli_bounds = bounds; - let (use_cli, use_rpc) = match &opt.common().exec_ctx { - ExecutionContext::CliOnly(_) => (Some(None), false), - ExecutionContext::RpcOnly(_) | ExecutionContext::Standard => (None, true), - ExecutionContext::Local(_) => (Some(None), true), - ExecutionContext::CustomCli { context, .. } => (Some(Some(context.clone())), true), - }; +// let (use_cli, use_rpc) = match &opt.common().exec_ctx { +// ExecutionContext::CliOnly(_) => (Some(None), false), +// ExecutionContext::RpcOnly(_) | ExecutionContext::Standard => (None, true), +// ExecutionContext::Local(_) => (Some(None), true), +// ExecutionContext::CustomCli { context, .. } => (Some(Some(context.clone())), true), +// }; - if let Options::Parent(ParentOptions { - subcommands, - self_impl, - .. - }) = opt - { - if let Some(ctx_ty) = ctx_ty { - cli_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); - cli_bounds.push(macro_try!(parse2(quote! { Clone }))); - rpc_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); - rpc_bounds.push(macro_try!(parse2(quote! { Clone }))); - } - if let Some(SelfImplInfo { context, .. }) = self_impl { - if let Some(cli_ty) = use_cli.as_ref() { - if let Some(cli_ty) = cli_ty { - cli_bounds.push(macro_try!(parse2(quote! { Into<#cli_ty> }))); - } else { - cli_bounds.push(macro_try!(parse2(quote! { Into<#context> }))); - } - } - if use_rpc { - rpc_bounds.push(macro_try!(parse2(quote! { Into<#context> }))); - } - } - for subcmd in subcommands { - let mut path = subcmd.clone(); - std::mem::take(&mut path.segments.last_mut().unwrap().arguments); - cli_bounds.push(macro_try!(parse2(quote! { #path::CommandContextCli }))); - rpc_bounds.push(macro_try!(parse2(quote! { #path::CommandContextRpc }))); - } - } else { - if let Some(cli_ty) = use_cli.as_ref() { - if let Some(cli_ty) = cli_ty { - cli_bounds.push(macro_try!(parse2(quote! { Into<#cli_ty> }))); - } else if let Some(ctx_ty) = &ctx_ty { - cli_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); - } - } - if use_rpc { - if let Some(ctx_ty) = &ctx_ty { - rpc_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); - } - } - } +// if let Options::Parent(ParentOptions { +// subcommands, +// self_impl, +// .. +// }) = opt +// { +// if let Some(ctx_ty) = ctx_ty { +// cli_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); +// cli_bounds.push(macro_try!(parse2(quote! { Clone }))); +// rpc_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); +// rpc_bounds.push(macro_try!(parse2(quote! { Clone }))); +// } +// if let Some(SelfImplInfo { context, .. }) = self_impl { +// if let Some(cli_ty) = use_cli.as_ref() { +// if let Some(cli_ty) = cli_ty { +// cli_bounds.push(macro_try!(parse2(quote! { Into<#cli_ty> }))); +// } else { +// cli_bounds.push(macro_try!(parse2(quote! { Into<#context> }))); +// } +// } +// if use_rpc { +// rpc_bounds.push(macro_try!(parse2(quote! { Into<#context> }))); +// } +// } +// for subcmd in subcommands { +// let mut path = subcmd.clone(); +// std::mem::take(&mut path.segments.last_mut().unwrap().arguments); +// cli_bounds.push(macro_try!(parse2(quote! { #path::CommandContextCli }))); +// rpc_bounds.push(macro_try!(parse2(quote! { #path::CommandContextRpc }))); +// } +// } else { +// if let Some(cli_ty) = use_cli.as_ref() { +// if let Some(cli_ty) = cli_ty { +// cli_bounds.push(macro_try!(parse2(quote! { Into<#cli_ty> }))); +// } else if let Some(ctx_ty) = &ctx_ty { +// cli_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); +// } +// } +// if use_rpc { +// if let Some(ctx_ty) = &ctx_ty { +// rpc_bounds.push(macro_try!(parse2(quote! { Into<#ctx_ty> }))); +// } +// } +// } - let res = quote! { - pub trait CommandContextCli: #cli_bounds {} - impl CommandContextCli for T where T: #cli_bounds {} +// let res = quote! { +// pub trait CommandContextCli: #cli_bounds {} +// impl CommandContextCli for T where T: #cli_bounds {} - pub trait CommandContextRpc: #rpc_bounds {} - impl CommandContextRpc for T where T: #rpc_bounds {} - }; - res -} +// pub trait CommandContextRpc: #rpc_bounds {} +// impl CommandContextRpc for T where T: #rpc_bounds {} +// }; +// res +// } -fn metadata(full_options: &Options) -> TokenStream { - let options = match full_options { - Options::Leaf(a) => a, - Options::Parent(ParentOptions { common, .. }) => common, - }; - let fallthrough = |ty: &str| { - let getter_name = Ident::new(&format!("get_{}", ty), Span::call_site()); - match &*full_options { - Options::Parent(ParentOptions { subcommands, .. }) => { - let subcmd_handler = subcommands.iter().map(|subcmd| { - let mut subcmd = subcmd.clone(); - subcmd.segments.last_mut().unwrap().arguments = PathArguments::None; - quote_spanned!{ subcmd.span() => - [#subcmd::NAME, rest] => if let Some(val) = #subcmd::Metadata.#getter_name(rest, key) { - return Some(val); - }, - } - }); - quote! { - if !command.is_empty() { - match command.splitn(2, ".").chain(std::iter::repeat("")).take(2).collect::>().as_slice() { - #( - #subcmd_handler - )* - _ => () - } - } - } - } - _ => quote! {}, - } - }; - fn impl_getter>( - ty: &str, - metadata: I, - fallthrough: TokenStream, - ) -> TokenStream { - let getter_name = Ident::new(&format!("get_{}", ty), Span::call_site()); - let ty: Type = syn::parse_str(ty).unwrap(); - quote! { - fn #getter_name(&self, command: &str, key: &str) -> Option<#ty> { - #fallthrough - match key { - #(#metadata)* - _ => None, - } - } - } - } - let bool_metadata = options - .metadata - .iter() - .filter(|(_, lit)| matches!(lit, Lit::Bool(_))) - .map(|(name, value)| { - let name = LitStr::new(&name.to_string(), name.span()); - quote! { - #name => Some(#value), - } - }); - let number_metadata = |ty: &str| { - let ty: Type = syn::parse_str(ty).unwrap(); - options - .metadata - .iter() - .filter(|(_, lit)| matches!(lit, Lit::Int(_) | Lit::Float(_) | Lit::Byte(_))) - .map(move |(name, value)| { - let name = LitStr::new(&name.to_string(), name.span()); - quote! { - #name => Some(#value as #ty), - } - }) - }; - let char_metadata = options - .metadata - .iter() - .filter(|(_, lit)| matches!(lit, Lit::Char(_))) - .map(|(name, value)| { - let name = LitStr::new(&name.to_string(), name.span()); - quote! { - #name => Some(#value), - } - }); - let str_metadata = options - .metadata - .iter() - .filter(|(_, lit)| matches!(lit, Lit::Str(_))) - .map(|(name, value)| { - let name = LitStr::new(&name.to_string(), name.span()); - quote! { - #name => Some(#value), - } - }); - let bstr_metadata = options - .metadata - .iter() - .filter(|(_, lit)| matches!(lit, Lit::ByteStr(_))) - .map(|(name, value)| { - let name = LitStr::new(&name.to_string(), name.span()); - quote! { - #name => Some(#value), - } - }); +// fn metadata(full_options: &Options) -> TokenStream { +// let options = match full_options { +// Options::Leaf(a) => a, +// Options::Parent(ParentOptions { common, .. }) => common, +// }; +// let fallthrough = |ty: &str| { +// let getter_name = Ident::new(&format!("get_{}", ty), Span::call_site()); +// match &*full_options { +// Options::Parent(ParentOptions { subcommands, .. }) => { +// let subcmd_handler = subcommands.iter().map(|subcmd| { +// let mut subcmd = subcmd.clone(); +// subcmd.segments.last_mut().unwrap().arguments = PathArguments::None; +// quote_spanned!{ subcmd.span() => +// [#subcmd::NAME, rest] => if let Some(val) = #subcmd::Metadata.#getter_name(rest, key) { +// return Some(val); +// }, +// } +// }); +// quote! { +// if !command.is_empty() { +// match command.splitn(2, ".").chain(std::iter::repeat("")).take(2).collect::>().as_slice() { +// #( +// #subcmd_handler +// )* +// _ => () +// } +// } +// } +// } +// _ => quote! {}, +// } +// }; +// fn impl_getter>( +// ty: &str, +// metadata: I, +// fallthrough: TokenStream, +// ) -> TokenStream { +// let getter_name = Ident::new(&format!("get_{}", ty), Span::call_site()); +// let ty: Type = syn::parse_str(ty).unwrap(); +// quote! { +// fn #getter_name(&self, command: &str, key: &str) -> Option<#ty> { +// #fallthrough +// match key { +// #(#metadata)* +// _ => None, +// } +// } +// } +// } +// let bool_metadata = options +// .metadata +// .iter() +// .filter(|(_, lit)| matches!(lit, Lit::Bool(_))) +// .map(|(name, value)| { +// let name = LitStr::new(&name.to_string(), name.span()); +// quote! { +// #name => Some(#value), +// } +// }); +// let number_metadata = |ty: &str| { +// let ty: Type = syn::parse_str(ty).unwrap(); +// options +// .metadata +// .iter() +// .filter(|(_, lit)| matches!(lit, Lit::Int(_) | Lit::Float(_) | Lit::Byte(_))) +// .map(move |(name, value)| { +// let name = LitStr::new(&name.to_string(), name.span()); +// quote! { +// #name => Some(#value as #ty), +// } +// }) +// }; +// let char_metadata = options +// .metadata +// .iter() +// .filter(|(_, lit)| matches!(lit, Lit::Char(_))) +// .map(|(name, value)| { +// let name = LitStr::new(&name.to_string(), name.span()); +// quote! { +// #name => Some(#value), +// } +// }); +// let str_metadata = options +// .metadata +// .iter() +// .filter(|(_, lit)| matches!(lit, Lit::Str(_))) +// .map(|(name, value)| { +// let name = LitStr::new(&name.to_string(), name.span()); +// quote! { +// #name => Some(#value), +// } +// }); +// let bstr_metadata = options +// .metadata +// .iter() +// .filter(|(_, lit)| matches!(lit, Lit::ByteStr(_))) +// .map(|(name, value)| { +// let name = LitStr::new(&name.to_string(), name.span()); +// quote! { +// #name => Some(#value), +// } +// }); - let bool_getter = impl_getter("bool", bool_metadata, fallthrough("bool")); - let u8_getter = impl_getter("u8", number_metadata("u8"), fallthrough("u8")); - let u16_getter = impl_getter("u16", number_metadata("u16"), fallthrough("u16")); - let u32_getter = impl_getter("u32", number_metadata("u32"), fallthrough("u32")); - let u64_getter = impl_getter("u64", number_metadata("u64"), fallthrough("u64")); - let usize_getter = impl_getter("usize", number_metadata("usize"), fallthrough("usize")); - let i8_getter = impl_getter("i8", number_metadata("i8"), fallthrough("i8")); - let i16_getter = impl_getter("i16", number_metadata("i16"), fallthrough("i16")); - let i32_getter = impl_getter("i32", number_metadata("i32"), fallthrough("i32")); - let i64_getter = impl_getter("i64", number_metadata("i64"), fallthrough("i64")); - let isize_getter = impl_getter("isize", number_metadata("isize"), fallthrough("isize")); - let f32_getter = impl_getter("f32", number_metadata("f32"), fallthrough("f32")); - let f64_getter = impl_getter("f64", number_metadata("f64"), fallthrough("f64")); - let char_getter = impl_getter("char", char_metadata, fallthrough("char")); - let str_fallthrough = fallthrough("str"); - let str_getter = quote! { - fn get_str(&self, command: &str, key: &str) -> Option<&'static str> { - #str_fallthrough - match key { - #(#str_metadata)* - _ => None, - } - } - }; - let bstr_fallthrough = fallthrough("bstr"); - let bstr_getter = quote! { - fn get_bstr(&self, command: &str, key: &str) -> Option<&'static [u8]> { - #bstr_fallthrough - match key { - #(#bstr_metadata)* - _ => None, - } - } - }; +// let bool_getter = impl_getter("bool", bool_metadata, fallthrough("bool")); +// let u8_getter = impl_getter("u8", number_metadata("u8"), fallthrough("u8")); +// let u16_getter = impl_getter("u16", number_metadata("u16"), fallthrough("u16")); +// let u32_getter = impl_getter("u32", number_metadata("u32"), fallthrough("u32")); +// let u64_getter = impl_getter("u64", number_metadata("u64"), fallthrough("u64")); +// let usize_getter = impl_getter("usize", number_metadata("usize"), fallthrough("usize")); +// let i8_getter = impl_getter("i8", number_metadata("i8"), fallthrough("i8")); +// let i16_getter = impl_getter("i16", number_metadata("i16"), fallthrough("i16")); +// let i32_getter = impl_getter("i32", number_metadata("i32"), fallthrough("i32")); +// let i64_getter = impl_getter("i64", number_metadata("i64"), fallthrough("i64")); +// let isize_getter = impl_getter("isize", number_metadata("isize"), fallthrough("isize")); +// let f32_getter = impl_getter("f32", number_metadata("f32"), fallthrough("f32")); +// let f64_getter = impl_getter("f64", number_metadata("f64"), fallthrough("f64")); +// let char_getter = impl_getter("char", char_metadata, fallthrough("char")); +// let str_fallthrough = fallthrough("str"); +// let str_getter = quote! { +// fn get_str(&self, command: &str, key: &str) -> Option<&'static str> { +// #str_fallthrough +// match key { +// #(#str_metadata)* +// _ => None, +// } +// } +// }; +// let bstr_fallthrough = fallthrough("bstr"); +// let bstr_getter = quote! { +// fn get_bstr(&self, command: &str, key: &str) -> Option<&'static [u8]> { +// #bstr_fallthrough +// match key { +// #(#bstr_metadata)* +// _ => None, +// } +// } +// }; - let res = quote! { - #[derive(Clone, Copy, Default)] - pub struct Metadata; +// let res = quote! { +// #[derive(Clone, Copy, Default)] +// pub struct Metadata; - #[allow(overflowing_literals)] - impl ::rpc_toolkit::Metadata for Metadata { - #bool_getter - #u8_getter - #u16_getter - #u32_getter - #u64_getter - #usize_getter - #i8_getter - #i16_getter - #i32_getter - #i64_getter - #isize_getter - #f32_getter - #f64_getter - #char_getter - #str_getter - #bstr_getter - } - }; - // panic!("{}", res); - res -} +// #[allow(overflowing_literals)] +// impl ::rpc_toolkit::Metadata for Metadata { +// #bool_getter +// #u8_getter +// #u16_getter +// #u32_getter +// #u64_getter +// #usize_getter +// #i8_getter +// #i16_getter +// #i32_getter +// #i64_getter +// #isize_getter +// #f32_getter +// #f64_getter +// #char_getter +// #str_getter +// #bstr_getter +// } +// }; +// // panic!("{}", res); +// res +// } -fn build_app(name: LitStr, opt: &mut Options, params: &mut [ParamType]) -> TokenStream { - let about = opt.common().about.clone().into_iter(); - let (subcommand, subcommand_required) = if let Options::Parent(opt) = opt { - ( - opt.subcommands - .iter() - .map(|subcmd| { - let mut path = subcmd.clone(); - path.segments.last_mut().unwrap().arguments = PathArguments::None; - path - }) - .collect(), - opt.self_impl.is_none(), - ) - } else { - (Vec::new(), false) - }; - let arg = params - .iter_mut() - .filter_map(|param| { - if let ParamType::Arg(arg) = param { - if arg.stdin.is_some() { - return None; - } - let name = arg.name.clone().unwrap(); - let name_str = arg.rename.clone().unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); - let help = arg.help.clone().into_iter(); - let short = arg.short.clone().into_iter(); - let long = arg.long.clone().into_iter(); - let mut modifications = TokenStream::default(); - let ty_span = arg.ty.span(); - if let Type::Path(p) = &mut arg.ty { - if p.path.is_ident("bool") - && arg.parse.is_none() - && (arg.short.is_some() || arg.long.is_some()) - { - arg.check_is_present = true; - modifications.extend(quote_spanned! { ty_span => - arg = arg.takes_value(false); - }); - } else if arg.count.is_some() { - modifications.extend(quote_spanned! { ty_span => - arg = arg.takes_value(false); - arg = arg.multiple(true); - }); - } else { - modifications.extend(quote_spanned! { ty_span => - arg = arg.takes_value(true); - }); - if let Some(_) = &arg.default { - modifications.extend(quote_spanned! { ty_span => - arg = arg.required(false); - }); - } else if p.path.segments.last().unwrap().ident == "Option" { - arg.optional = true; - modifications.extend(quote_spanned! { ty_span => - arg = arg.required(false); - }); - } else if arg.multiple.is_some() { - modifications.extend(quote_spanned! { ty_span => - arg = arg.multiple(true); - }); - } else { - modifications.extend(quote_spanned! { ty_span => - arg = arg.required(true); - }); - } - } - }; - Some(quote! { - { - let mut arg = ::rpc_toolkit::command_helpers::prelude::Arg::with_name(#name_str); - #( - arg = arg.help(#help); - )* - #( - arg = arg.short(#short); - )* - #( - arg = arg.long(#long); - )* - #modifications +// fn build_app(name: LitStr, opt: &mut Options, params: &mut [ParamType]) -> TokenStream { +// let about = opt.common().about.clone().into_iter(); +// let (subcommand, subcommand_required) = if let Options::Parent(opt) = opt { +// ( +// opt.subcommands +// .iter() +// .map(|subcmd| { +// let mut path = subcmd.clone(); +// path.segments.last_mut().unwrap().arguments = PathArguments::None; +// path +// }) +// .collect(), +// opt.self_impl.is_none(), +// ) +// } else { +// (Vec::new(), false) +// }; +// let arg = params +// .iter_mut() +// .filter_map(|param| { +// if let ParamType::Arg(arg) = param { +// if arg.stdin.is_some() { +// return None; +// } +// let name = arg.name.clone().unwrap(); +// let name_str = arg.rename.clone().unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); +// let help = arg.help.clone().into_iter(); +// let short = arg.short.clone().into_iter(); +// let long = arg.long.clone().into_iter(); +// let mut modifications = TokenStream::default(); +// let ty_span = arg.ty.span(); +// if let Type::Path(p) = &mut arg.ty { +// if p.path.is_ident("bool") +// && arg.parse.is_none() +// && (arg.short.is_some() || arg.long.is_some()) +// { +// arg.check_is_present = true; +// modifications.extend(quote_spanned! { ty_span => +// arg = arg.takes_value(false); +// }); +// } else if arg.count.is_some() { +// modifications.extend(quote_spanned! { ty_span => +// arg = arg.takes_value(false); +// arg = arg.multiple(true); +// }); +// } else { +// modifications.extend(quote_spanned! { ty_span => +// arg = arg.takes_value(true); +// }); +// if let Some(_) = &arg.default { +// modifications.extend(quote_spanned! { ty_span => +// arg = arg.required(false); +// }); +// } else if p.path.segments.last().unwrap().ident == "Option" { +// arg.optional = true; +// modifications.extend(quote_spanned! { ty_span => +// arg = arg.required(false); +// }); +// } else if arg.multiple.is_some() { +// modifications.extend(quote_spanned! { ty_span => +// arg = arg.multiple(true); +// }); +// } else { +// modifications.extend(quote_spanned! { ty_span => +// arg = arg.required(true); +// }); +// } +// } +// }; +// Some(quote! { +// { +// let mut arg = ::rpc_toolkit::command_helpers::prelude::Arg::with_name(#name_str); +// #( +// arg = arg.help(#help); +// )* +// #( +// arg = arg.short(#short); +// )* +// #( +// arg = arg.long(#long); +// )* +// #modifications - arg - } - }) - } else { - None - } - }) - .collect::>(); - let required = LitBool::new(subcommand_required, Span::call_site()); - let alias = &opt.common().aliases; - quote! { - pub fn build_app() -> ::rpc_toolkit::command_helpers::prelude::App<'static> { - let mut app = ::rpc_toolkit::command_helpers::prelude::App::new(#name); - #( - app = app.about(#about); - )* - #( - app = app.alias(#alias); - )* - #( - app = app.arg(#arg); - )* - #( - app = app.subcommand(#subcommand::build_app()); - )* - if #required { - app = app.setting(::rpc_toolkit::command_helpers::prelude::AppSettings::SubcommandRequired); - } - app - } - } -} +// arg +// } +// }) +// } else { +// None +// } +// }) +// .collect::>(); +// let required = LitBool::new(subcommand_required, Span::call_site()); +// let alias = &opt.common().aliases; +// quote! { +// pub fn build_app() -> ::rpc_toolkit::command_helpers::prelude::App<'static> { +// let mut app = ::rpc_toolkit::command_helpers::prelude::App::new(#name); +// #( +// app = app.about(#about); +// )* +// #( +// app = app.alias(#alias); +// )* +// #( +// app = app.arg(#arg); +// )* +// #( +// app = app.subcommand(#subcommand::build_app()); +// )* +// if #required { +// app = app.setting(::rpc_toolkit::command_helpers::prelude::AppSettings::SubcommandRequired); +// } +// app +// } +// } +// } struct GenericFilter<'a> { src: &'a Generics, @@ -406,7 +407,7 @@ impl<'a> GenericFilter<'a> { WherePredicate::Type(PredicateType { bounded_ty: Type::Path(t), .. - }) if self.types.contains(&t.path.segments.last().unwrap().ident) => { + }) if self.types.contains(&t.path.segments.first().unwrap().ident) => { where_clause.predicates.push(src_predicate.clone()) } _ => (), @@ -447,7 +448,9 @@ impl<'a> Fold for GenericFilter<'a> { .params .iter() .filter_map(|param| match (param, &i) { - (GenericParam::Type(t), Type::Path(i)) if i.path.is_ident(&t.ident) => { + (GenericParam::Type(t), Type::Path(i)) + if &i.path.segments.first().unwrap().ident == &t.ident => + { Some(t.ident.clone()) } _ => None, @@ -457,831 +460,1052 @@ impl<'a> Fold for GenericFilter<'a> { } } -fn rpc_handler( - fn_name: &Ident, - fn_generics: &Generics, - opt: &Options, - params: &[ParamType], -) -> TokenStream { - let mut parent_data_ty = quote! { () }; - let mut generics = fn_generics.clone(); - generics.params.push(macro_try!(syn::parse2( - quote! { GenericContext: CommandContextRpc } - ))); - if generics.lt_token.is_none() { - generics.lt_token = Some(Default::default()); - } - if generics.gt_token.is_none() { - generics.gt_token = Some(Default::default()); - } - let mut param_def = Vec::new(); - for param in params { - match param { - ParamType::Arg(arg) => { - let name = arg.name.clone().unwrap(); - let rename = arg - .rename - .clone() - .unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); - let field_name = Ident::new(&format!("arg_{}", name), name.span()); - let ty = arg.ty.clone(); - let def = quote! { - #[serde(rename = #rename)] - #field_name: #ty, - }; - let def = match &arg.default { - Some(Some(default)) => { - quote! { - #[serde(default = #default)] - #def - } - } - Some(None) => { - quote! { - #[serde(default)] - #def - } - } - None => def, - }; - param_def.push(def); - } - ParamType::ParentData(ty) => parent_data_ty = quote! { #ty }, - _ => (), - } - } - let (_, fn_type_generics, _) = fn_generics.split_for_impl(); - let fn_turbofish = fn_type_generics.as_turbofish(); - let fn_path: Path = macro_try!(syn::parse2(quote! { super::#fn_name#fn_turbofish })); - let mut param_generics_filter = GenericFilter::new(fn_generics); - for param in params { - if let ParamType::Arg(a) = param { - param_generics_filter.fold_type(a.ty.clone()); - } +// fn rpc_handler( +// fn_name: &Ident, +// fn_generics: &Generics, +// opt: &Options, +// params: &[ParamType], +// ) -> TokenStream { +// let mut parent_data_ty = quote! { () }; +// let mut generics = fn_generics.clone(); +// generics.params.push(macro_try!(syn::parse2( +// quote! { GenericContext: CommandContextRpc } +// ))); +// if generics.lt_token.is_none() { +// generics.lt_token = Some(Default::default()); +// } +// if generics.gt_token.is_none() { +// generics.gt_token = Some(Default::default()); +// } +// let mut param_def = Vec::new(); +// for param in params { +// match param { +// ParamType::Arg(arg) => { +// let name = arg.name.clone().unwrap(); +// let rename = arg +// .rename +// .clone() +// .unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); +// let field_name = Ident::new(&format!("arg_{}", name), name.span()); +// let ty = arg.ty.clone(); +// let def = quote! { +// #[serde(rename = #rename)] +// #field_name: #ty, +// }; +// let def = match &arg.default { +// Some(Some(default)) => { +// quote! { +// #[serde(default = #default)] +// #def +// } +// } +// Some(None) => { +// quote! { +// #[serde(default)] +// #def +// } +// } +// None => def, +// }; +// param_def.push(def); +// } +// ParamType::ParentData(ty) => parent_data_ty = quote! { #ty }, +// _ => (), +// } +// } +// let (_, fn_type_generics, _) = fn_generics.split_for_impl(); +// let fn_turbofish = fn_type_generics.as_turbofish(); +// let fn_path: Path = macro_try!(syn::parse2(quote! { super::#fn_name#fn_turbofish })); +// let mut param_generics_filter = GenericFilter::new(fn_generics); +// for param in params { +// if let ParamType::Arg(a) = param { +// param_generics_filter.fold_type(a.ty.clone()); +// } +// } +// let param_generics = param_generics_filter.finish(); +// let (_, param_ty_generics, _) = param_generics.split_for_impl(); +// let param_struct_def = quote! { +// #[allow(dead_code)] +// #[derive(::rpc_toolkit::command_helpers::prelude::Deserialize)] +// pub struct Params#param_ty_generics { +// #( +// #param_def +// )* +// #[serde(flatten)] +// #[serde(default)] +// rest: ::rpc_toolkit::command_helpers::prelude::Value, +// } +// }; +// let param = params.iter().map(|param| match param { +// ParamType::Arg(arg) => { +// let name = arg.name.clone().unwrap(); +// let field_name = Ident::new(&format!("arg_{}", name), name.span()); +// quote! { args.#field_name } +// } +// ParamType::Context(ty) => { +// if matches!(opt, Options::Parent { .. }) { +// quote! { >::into(ctx.clone()) } +// } else { +// quote! { >::into(ctx) } +// } +// } +// ParamType::ParentData(_) => { +// quote! { parent_data } +// } +// ParamType::Request => quote! { request }, +// ParamType::Response => quote! { response }, +// ParamType::None => unreachable!(), +// }); +// match opt { +// Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::CliOnly(_)) => quote! { +// #param_struct_def + +// pub async fn rpc_handler#generics( +// _ctx: GenericContext, +// _parent_data: #parent_data_ty, +// _request: &::rpc_toolkit::command_helpers::prelude::RequestParts, +// _response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, +// method: &str, +// _args: Params#param_ty_generics, +// ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { +// Err(::rpc_toolkit::command_helpers::prelude::RpcError { +// data: Some(method.into()), +// ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR +// }) +// } +// }, +// Options::Leaf(opt) => { +// let invocation = if opt.is_async { +// quote! { +// #fn_path(#(#param),*).await? +// } +// } else if opt.blocking.is_some() { +// quote! { +// ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #fn_path(#(#param),*)).await? +// } +// } else { +// quote! { +// #fn_path(#(#param),*)? +// } +// }; +// quote! { +// #param_struct_def + +// pub async fn rpc_handler#generics( +// ctx: GenericContext, +// parent_data: #parent_data_ty, +// request: &::rpc_toolkit::command_helpers::prelude::RequestParts, +// response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, +// method: &str, +// args: Params#param_ty_generics, +// ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { +// if method.is_empty() { +// Ok(::rpc_toolkit::command_helpers::prelude::to_value(#invocation)?) +// } else { +// Err(::rpc_toolkit::command_helpers::prelude::RpcError { +// data: Some(method.into()), +// ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR +// }) +// } +// } +// } +// } +// Options::Parent(ParentOptions { +// common, +// subcommands, +// self_impl, +// }) => { +// let cmd_preprocess = if common.is_async { +// quote! { +// let parent_data = #fn_path(#(#param),*).await?; +// } +// } else if common.blocking.is_some() { +// quote! { +// let parent_data = ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #fn_path(#(#param),*)).await?; +// } +// } else { +// quote! { +// let parent_data = #fn_path(#(#param),*)?; +// } +// }; +// let subcmd_impl = subcommands.iter().map(|subcommand| { +// let mut subcommand = subcommand.clone(); +// let mut rpc_handler = PathSegment { +// ident: Ident::new("rpc_handler", Span::call_site()), +// arguments: std::mem::replace( +// &mut subcommand.segments.last_mut().unwrap().arguments, +// PathArguments::None, +// ), +// }; +// rpc_handler.arguments = match rpc_handler.arguments { +// PathArguments::None => PathArguments::AngleBracketed( +// syn::parse2(quote! { :: }) +// .unwrap(), +// ), +// PathArguments::AngleBracketed(mut a) => { +// a.args.push(syn::parse2(quote! { GenericContext }).unwrap()); +// PathArguments::AngleBracketed(a) +// } +// _ => unreachable!(), +// }; +// quote_spanned!{ subcommand.span() => +// [#subcommand::NAME, rest] => #subcommand::#rpc_handler(ctx, parent_data, request, response, rest, ::rpc_toolkit::command_helpers::prelude::from_value(args.rest)?).await +// } +// }); +// let subcmd_impl = quote! { +// match method.splitn(2, ".").chain(std::iter::repeat("")).take(2).collect::>().as_slice() { +// #( +// #subcmd_impl, +// )* +// _ => Err(::rpc_toolkit::command_helpers::prelude::RpcError { +// data: Some(method.into()), +// ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR +// }) +// } +// }; +// match self_impl { +// Some(self_impl) if !matches!(common.exec_ctx, ExecutionContext::CliOnly(_)) => { +// let self_impl_fn = &self_impl.path; +// let self_impl = if self_impl.is_async { +// quote_spanned! { self_impl_fn.span() => +// #self_impl_fn(Into::into(ctx), parent_data).await? +// } +// } else if self_impl.blocking { +// quote_spanned! { self_impl_fn.span() => +// { +// let ctx = Into::into(ctx); +// ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #self_impl_fn(ctx, parent_data)).await? +// } +// } +// } else { +// quote_spanned! { self_impl_fn.span() => +// #self_impl_fn(Into::into(ctx), parent_data)? +// } +// }; +// quote! { +// #param_struct_def + +// pub async fn rpc_handler#generics( +// ctx: GenericContext, +// parent_data: #parent_data_ty, +// request: &::rpc_toolkit::command_helpers::prelude::RequestParts, +// response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, +// method: &str, +// args: Params#param_ty_generics, +// ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { +// #cmd_preprocess + +// if method.is_empty() { +// Ok(::rpc_toolkit::command_helpers::prelude::to_value(&#self_impl)?) +// } else { +// #subcmd_impl +// } +// } +// } +// } +// _ => { +// quote! { +// #param_struct_def + +// pub async fn rpc_handler#generics( +// ctx: GenericContext, +// parent_data: #parent_data_ty, +// request: &::rpc_toolkit::command_helpers::prelude::RequestParts, +// response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, +// method: &str, +// args: Params#param_ty_generics, +// ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { +// #cmd_preprocess + +// #subcmd_impl +// } +// } +// } +// } +// } +// } +// } + +// fn cli_handler( +// fn_name: &Ident, +// fn_generics: &Generics, +// opt: &mut Options, +// params: &[ParamType], +// ) -> TokenStream { +// let mut parent_data_ty = quote! { () }; +// let mut generics = fn_generics.clone(); +// generics.params.push(macro_try!(syn::parse2( +// quote! { ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize } +// ))); +// generics.params.push(macro_try!(syn::parse2( +// quote! { GenericContext: CommandContextCli } +// ))); +// if generics.lt_token.is_none() { +// generics.lt_token = Some(Default::default()); +// } +// if generics.gt_token.is_none() { +// generics.gt_token = Some(Default::default()); +// } +// let (_, fn_type_generics, _) = fn_generics.split_for_impl(); +// let fn_turbofish = fn_type_generics.as_turbofish(); +// let fn_path: Path = macro_try!(syn::parse2(quote! { super::#fn_name#fn_turbofish })); +// let is_parent = matches!(opt, Options::Parent { .. }); +// let param: Vec<_> = params +// .iter() +// .map(|param| match param { +// ParamType::Arg(arg) => { +// let name = arg.name.clone().unwrap(); +// let field_name = Ident::new(&format!("arg_{}", name), name.span()); +// quote! { params.#field_name.clone() } +// } +// ParamType::Context(ty) => { +// if is_parent { +// quote! { >::into(ctx.clone()) } +// } else { +// quote! { >::into(ctx) } +// } +// } +// ParamType::ParentData(ty) => { +// parent_data_ty = quote! { #ty }; +// quote! { parent_data } +// } +// ParamType::Request => quote! { request }, +// ParamType::Response => quote! { response }, +// ParamType::None => unreachable!(), +// }) +// .collect(); +// let mut param_generics_filter = GenericFilter::new(fn_generics); +// for param in params { +// if let ParamType::Arg(a) = param { +// param_generics_filter.fold_type(a.ty.clone()); +// } +// } +// let mut param_generics = param_generics_filter.finish(); +// param_generics.params.push(macro_try!(syn::parse2(quote! { +// ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize +// }))); +// if param_generics.lt_token.is_none() { +// param_generics.lt_token = Some(Default::default()); +// } +// if param_generics.gt_token.is_none() { +// param_generics.gt_token = Some(Default::default()); +// } +// let (_, param_ty_generics, _) = param_generics.split_for_impl(); +// let mut arg_def = Vec::new(); +// for param in params { +// match param { +// ParamType::Arg(arg) => { +// let name = arg.name.clone().unwrap(); +// let rename = arg +// .rename +// .clone() +// .unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); +// let field_name = Ident::new(&format!("arg_{}", name), name.span()); +// let ty = arg.ty.clone(); +// arg_def.push(quote! { +// #[serde(rename = #rename)] +// #field_name: #ty, +// }) +// } +// _ => (), +// } +// } +// let arg = params +// .iter() +// .filter_map(|param| { +// if let ParamType::Arg(a) = param { +// Some(a) +// } else { +// None +// } +// }) +// .map(|arg| { +// let name = arg.name.clone().unwrap(); +// let arg_name = arg.rename.clone().unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); +// let field_name = Ident::new(&format!("arg_{}", name), name.span()); +// if arg.stdin.is_some() { +// if let Some(parse) = &arg.parse { +// quote! { +// #field_name: #parse(&mut std::io::stdin(), matches)?, +// } +// } else { +// quote! { +// #field_name: ::rpc_toolkit::command_helpers::prelude::default_stdin_parser(&mut std::io::stdin(), matches)?, +// } +// } +// } else if arg.check_is_present { +// quote! { +// #field_name: matches.is_present(#arg_name), +// } +// } else if arg.count.is_some() { +// quote! { +// #field_name: matches.occurrences_of(#arg_name), +// } +// } else { +// let parse_val = if let Some(parse) = &arg.parse { +// quote! { +// #parse(arg_val, matches) +// } +// } else { +// quote! { +// ::rpc_toolkit::command_helpers::prelude::default_arg_parser(arg_val, matches) +// } +// }; +// if arg.optional { +// quote! { +// #field_name: if let Some(arg_val) = matches.value_of(#arg_name) { +// Some(#parse_val?) +// } else { +// None +// }, +// } +// } else if let Some(default) = &arg.default { +// if let Some(default) = default { +// let path: Path = match syn::parse_str(&default.value()) { +// Ok(a) => a, +// Err(e) => return e.into_compile_error(), +// }; +// quote! { +// #field_name: if let Some(arg_val) = matches.value_of(#arg_name) { +// #parse_val? +// } else { +// #path() +// }, +// } +// } else { +// quote! { +// #field_name: if let Some(arg_val) = matches.value_of(#arg_name) { +// #parse_val? +// } else { +// Default::default() +// }, +// } +// } +// } else if arg.multiple.is_some() { +// quote! { +// #field_name: matches.values_of(#arg_name).iter().flatten().map(|arg_val| #parse_val).collect::>()?, +// } +// } else { +// quote! { +// #field_name: { +// let arg_val = matches.value_of(#arg_name).unwrap(); +// #parse_val? +// }, +// } +// } +// } +// }); +// let param_struct_def = quote! { +// #[derive(::rpc_toolkit::command_helpers::prelude::Serialize)] +// struct Params#param_ty_generics { +// #( +// #arg_def +// )* +// #[serde(flatten)] +// rest: ParentParams, +// } +// let params: Params#param_ty_generics = Params { +// #( +// #arg +// )* +// rest: parent_params, +// }; +// }; +// let create_rt = quote! { +// let rt_ref = if let Some(rt) = rt.as_mut() { +// &*rt +// } else { +// rt = Some(::rpc_toolkit::command_helpers::prelude::Runtime::new().map_err(|e| ::rpc_toolkit::command_helpers::prelude::RpcError { +// data: Some(format!("{}", e).into()), +// ..::rpc_toolkit::command_helpers::prelude::yajrc::INTERNAL_ERROR +// })?); +// rt.as_ref().unwrap() +// }; +// }; +// let display = if let Some(display) = &opt.common().display { +// quote! { #display } +// } else { +// quote! { ::rpc_toolkit::command_helpers::prelude::default_display } +// }; +// match opt { +// Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::RpcOnly(_)) => quote! { +// pub fn cli_handler#generics( +// _ctx: GenericContext, +// _parent_data: #parent_data_ty, +// _rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, +// _matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches, +// method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, +// _parent_params: ParentParams, +// ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { +// Err(::rpc_toolkit::command_helpers::prelude::RpcError { +// data: Some(method.into()), +// ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR +// }) +// } +// }, +// Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::Standard) => { +// let param = param.into_iter().map(|_| quote! { unreachable!() }); +// let invocation = if opt.is_async { +// quote! { +// rt_ref.block_on(#fn_path(#(#param),*))? +// } +// } else { +// quote! { +// #fn_path(#(#param),*)? +// } +// }; +// quote! { +// pub fn cli_handler#generics( +// ctx: GenericContext, +// parent_data: #parent_data_ty, +// mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, +// matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches, +// method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, +// parent_params: ParentParams, +// ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { +// #param_struct_def + +// #create_rt + +// #[allow(unreachable_code)] +// let return_ty = if true { +// ::rpc_toolkit::command_helpers::prelude::PhantomData +// } else { +// let ctx_new = unreachable!(); +// ::rpc_toolkit::command_helpers::prelude::match_types(&ctx, &ctx_new); +// let ctx = ctx_new; +// ::rpc_toolkit::command_helpers::prelude::make_phantom(#invocation) +// }; + +// let res = rt_ref.block_on(::rpc_toolkit::command_helpers::prelude::call_remote(ctx, method.as_ref(), params, return_ty))?; +// Ok(#display(res.result?, matches)) +// } +// } +// } +// Options::Leaf(opt) => { +// if let ExecutionContext::CustomCli { +// ref cli, is_async, .. +// } = opt.exec_ctx +// { +// let fn_path = cli; +// let cli_param = params.iter().filter_map(|param| match param { +// ParamType::Arg(arg) => { +// let name = arg.name.clone().unwrap(); +// let field_name = Ident::new(&format!("arg_{}", name), name.span()); +// Some(quote! { params.#field_name.clone() }) +// } +// ParamType::Context(_) => Some(quote! { Into::into(ctx) }), +// ParamType::ParentData(_) => Some(quote! { parent_data }), +// ParamType::Request => None, +// ParamType::Response => None, +// ParamType::None => unreachable!(), +// }); +// let invocation = if is_async { +// quote! { +// rt_ref.block_on(#fn_path(#(#cli_param),*))? +// } +// } else { +// quote! { +// #fn_path(#(#cli_param),*)? +// } +// }; +// let display_res = if let Some(display_fn) = &opt.display { +// quote! { +// #display_fn(#invocation, matches) +// } +// } else { +// quote! { +// ::rpc_toolkit::command_helpers::prelude::default_display(#invocation, matches) +// } +// }; +// let rt_action = if is_async { +// create_rt +// } else { +// quote! { +// drop(rt); +// } +// }; +// quote! { +// pub fn cli_handler#generics( +// ctx: GenericContext, +// parent_data: #parent_data_ty, +// mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, +// matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches, +// _method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, +// parent_params: ParentParams +// ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { +// #param_struct_def + +// #rt_action + +// Ok(#display_res) +// } +// } +// } else { +// let invocation = if opt.is_async { +// quote! { +// rt_ref.block_on(#fn_path(#(#param),*))? +// } +// } else { +// quote! { +// #fn_path(#(#param),*)? +// } +// }; +// let display_res = if let Some(display_fn) = &opt.display { +// quote! { +// #display_fn(#invocation, matches) +// } +// } else { +// quote! { +// ::rpc_toolkit::command_helpers::prelude::default_display(#invocation, matches) +// } +// }; +// let rt_action = if opt.is_async { +// create_rt +// } else { +// quote! { +// drop(rt); +// } +// }; +// quote! { +// pub fn cli_handler#generics( +// ctx: GenericContext, +// parent_data: #parent_data_ty, +// mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, +// matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches, +// _method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, +// parent_params: ParentParams +// ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { +// #param_struct_def + +// #rt_action + +// Ok(#display_res) +// } +// } +// } +// } +// Options::Parent(ParentOptions { +// common, +// subcommands, +// self_impl, +// }) => { +// let cmd_preprocess = if common.is_async { +// quote! { +// #create_rt +// let parent_data = rt_ref.block_on(#fn_path(#(#param),*))?; +// } +// } else { +// quote! { +// let parent_data = #fn_path(#(#param),*)?; +// } +// }; +// let subcmd_impl = subcommands.iter().map(|subcommand| { +// let mut subcommand = subcommand.clone(); +// let mut cli_handler = PathSegment { +// ident: Ident::new("cli_handler", Span::call_site()), +// arguments: std::mem::replace( +// &mut subcommand.segments.last_mut().unwrap().arguments, +// PathArguments::None, +// ), +// }; +// cli_handler.arguments = match cli_handler.arguments { +// PathArguments::None => PathArguments::AngleBracketed( +// syn::parse2(quote! { :: }) +// .unwrap(), +// ), +// PathArguments::AngleBracketed(mut a) => { +// a.args +// .push(syn::parse2(quote! { Params#param_ty_generics }).unwrap()); +// a.args.push(syn::parse2(quote! { GenericContext }).unwrap()); +// PathArguments::AngleBracketed(a) +// } +// _ => unreachable!(), +// }; +// quote_spanned! { subcommand.span() => +// Some((#subcommand::NAME, sub_m)) => { +// let method = if method.is_empty() { +// #subcommand::NAME.into() +// } else { +// method + "." + #subcommand::NAME +// }; +// #subcommand::#cli_handler(ctx, parent_data, rt, sub_m, method, params) +// }, +// } +// }); +// let self_impl = match (self_impl, &common.exec_ctx) { +// (Some(self_impl), ExecutionContext::CliOnly(_)) +// | (Some(self_impl), ExecutionContext::Local(_)) +// | (Some(self_impl), ExecutionContext::CustomCli { .. }) => { +// let (self_impl_fn, is_async) = +// if let ExecutionContext::CustomCli { cli, is_async, .. } = &common.exec_ctx +// { +// (cli, *is_async) +// } else { +// (&self_impl.path, self_impl.is_async) +// }; +// let create_rt = if common.is_async { +// None +// } else { +// Some(create_rt) +// }; +// let self_impl = if is_async { +// quote_spanned! { self_impl_fn.span() => +// #create_rt +// rt_ref.block_on(#self_impl_fn(Into::into(ctx), parent_data))? +// } +// } else { +// quote_spanned! { self_impl_fn.span() => +// #self_impl_fn(Into::into(ctx), parent_data)? +// } +// }; +// quote! { +// Ok(#display(#self_impl, matches)), +// } +// } +// (Some(self_impl), ExecutionContext::Standard) => { +// let self_impl_fn = &self_impl.path; +// let self_impl = if self_impl.is_async { +// quote! { +// rt_ref.block_on(#self_impl_fn(unreachable!(), parent_data)) +// } +// } else { +// quote! { +// #self_impl_fn(unreachable!(), parent_data) +// } +// }; +// let create_rt = if common.is_async { +// None +// } else { +// Some(create_rt) +// }; +// quote! { +// { +// #create_rt + +// #[allow(unreachable_code)] +// let return_ty = if true { +// ::rpc_toolkit::command_helpers::prelude::PhantomData +// } else { +// ::rpc_toolkit::command_helpers::prelude::make_phantom(#self_impl?) +// }; + +// let res = rt_ref.block_on(::rpc_toolkit::command_helpers::prelude::call_remote(ctx, method.as_ref(), params, return_ty))?; +// Ok(#display(res.result?, matches)) +// } +// } +// } +// (None, _) | (Some(_), ExecutionContext::RpcOnly(_)) => quote! { +// Err(::rpc_toolkit::command_helpers::prelude::RpcError { +// data: Some(method.into()), +// ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR +// }), +// }, +// }; +// quote! { +// pub fn cli_handler#generics( +// ctx: GenericContext, +// parent_data: #parent_data_ty, +// mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, +// matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches, +// method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, +// parent_params: ParentParams, +// ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { +// #param_struct_def + +// #cmd_preprocess + +// match matches.subcommand() { +// #( +// #subcmd_impl +// )* +// _ => #self_impl +// } +// } +// } +// } +// } +// } + +fn build_params(params: Vec, generics: &Generics) -> (TokenStream, Generics) { + let mut param_generics_filter = GenericFilter::new(generics); + for param in ¶ms { + param_generics_filter.fold_type(param.ty.clone()); } let param_generics = param_generics_filter.finish(); - let (_, param_ty_generics, _) = param_generics.split_for_impl(); - let param_struct_def = quote! { - #[allow(dead_code)] - #[derive(::rpc_toolkit::command_helpers::prelude::Deserialize)] - pub struct Params#param_ty_generics { - #( - #param_def - )* - #[serde(flatten)] - #[serde(default)] - rest: ::rpc_toolkit::command_helpers::prelude::Value, + let (impl_generics, ty_generics, where_clause) = param_generics.split_for_impl(); + let param_arg = params.iter().enumerate().map(|(idx, p)| { + let mut res = TokenStream::new(); + let p_ty = &p.ty; + if let Some(rename) = &p.rename { + res.extend(quote! { #[serde(rename = #rename)] }); + } else if let Some(name) = &p.name { + let name = LitStr::new(&name.to_string(), name.span()); + res.extend(quote! { #[serde(rename = #name)] }); + }; + if let Some(default) = &p.default { + res.extend(quote! { #[serde(#default)] }); } - }; - let param = params.iter().map(|param| match param { - ParamType::Arg(arg) => { - let name = arg.name.clone().unwrap(); - let field_name = Ident::new(&format!("arg_{}", name), name.span()); - quote! { args.#field_name } - } - ParamType::Context(ty) => { - if matches!(opt, Options::Parent { .. }) { - quote! { >::into(ctx.clone()) } - } else { - quote! { >::into(ctx) } - } - } - ParamType::ParentData(_) => { - quote! { parent_data } - } - ParamType::Request => quote! { request }, - ParamType::Response => quote! { response }, - ParamType::None => unreachable!(), + let arg_ident = Ident::new(&format!("arg_{idx}"), Span::call_site()); + let p_name = p.name.as_ref().unwrap_or(&arg_ident); + res.extend(quote! { pub #p_name: #p_ty, }); + res }); - match opt { - Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::CliOnly(_)) => quote! { - #param_struct_def - - pub async fn rpc_handler#generics( - _ctx: GenericContext, - _parent_data: #parent_data_ty, - _request: &::rpc_toolkit::command_helpers::prelude::RequestParts, - _response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, - method: &str, - _args: Params#param_ty_generics, - ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { - Err(::rpc_toolkit::command_helpers::prelude::RpcError { - data: Some(method.into()), - ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR - }) - } - }, - Options::Leaf(opt) => { - let invocation = if opt.is_async { - quote! { - #fn_path(#(#param),*).await? - } - } else if opt.blocking.is_some() { - quote! { - ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #fn_path(#(#param),*)).await? - } + let (clap_param_arg, clap_param_from_matches): (Vec<_>, Vec<_>) = params + .iter() + .enumerate() + .map(|(idx, p)| { + let (mut arg, mut from_matches) = (TokenStream::new(), TokenStream::new()); + let arg_ident = Ident::new(&format!("arg_{idx}"), Span::call_site()); + let p_name = p + .name + .as_ref() + .unwrap_or(&arg_ident); + if p.stdin.is_some() { + let parser = p + .parse.as_ref() + .map(|p| quote! { #p }) + .unwrap_or(quote!(::rpc_toolkit::command_helpers::default_stdin_parser)); + from_matches.extend(quote! { #p_name: #parser(&mut std::io::stdin(), matches)? }); + } else if matches!(&p.ty, Type::Path(p) if p.path.is_ident("bool")) { + arg.extend(if p.clap_attr.is_empty() { + quote! { #[arg] } + } else { + let clap_attr = &p.clap_attr; + quote! { #[arg(#(#clap_attr),*)] } + }); + arg.extend(quote! { #p_name: bool, }); + from_matches.extend(quote! { #p_name: clap_args.#p_name, }); + } else if matches!(&p.ty, Type::Path(p) if p.path.segments.first().unwrap().ident == "Option") { + let parser = p + .parse.as_ref() + .map(|p| quote!(#p)) + .unwrap_or(quote!(::rpc_toolkit::command_helpers::default_arg_parser)); + arg.extend(if p.clap_attr.is_empty() { + quote! { #[arg] } + } else { + let clap_attr = &p.clap_attr; + quote! { #[arg(#(#clap_attr),*)] } + }); + arg.extend(quote! { #p_name: Option, }); + from_matches.extend(quote! { #p_name: clap_args.#p_name.as_ref().map(|arg_str| #parser(arg_str, matches)).transpose()?, }); } else { - quote! { - #fn_path(#(#param),*)? - } - }; - quote! { - #param_struct_def - - pub async fn rpc_handler#generics( - ctx: GenericContext, - parent_data: #parent_data_ty, - request: &::rpc_toolkit::command_helpers::prelude::RequestParts, - response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, - method: &str, - args: Params#param_ty_generics, - ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { - if method.is_empty() { - Ok(::rpc_toolkit::command_helpers::prelude::to_value(#invocation)?) - } else { - Err(::rpc_toolkit::command_helpers::prelude::RpcError { - data: Some(method.into()), - ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR - }) - } - } + let parser = p + .parse.as_ref() + .map(|p| quote!(#p)) + .unwrap_or(quote!(::rpc_toolkit::command_helpers::default_arg_parser)); + arg.extend(if p.clap_attr.is_empty() { + quote! { #[arg] } + } else { + let clap_attr = &p.clap_attr; + quote! { #[arg(#(#clap_attr),*)] } + }); + arg.extend(quote! { #p_name: String, }); + from_matches.extend(quote! { #p_name: #parser(&clap_args.#p_name, matches)?, }); } - } - Options::Parent(ParentOptions { - common, - subcommands, - self_impl, - }) => { - let cmd_preprocess = if common.is_async { - quote! { - let parent_data = #fn_path(#(#param),*).await?; - } - } else if common.blocking.is_some() { - quote! { - let parent_data = ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #fn_path(#(#param),*)).await?; - } - } else { - quote! { - let parent_data = #fn_path(#(#param),*)?; - } - }; - let subcmd_impl = subcommands.iter().map(|subcommand| { - let mut subcommand = subcommand.clone(); - let mut rpc_handler = PathSegment { - ident: Ident::new("rpc_handler", Span::call_site()), - arguments: std::mem::replace( - &mut subcommand.segments.last_mut().unwrap().arguments, - PathArguments::None, - ), - }; - rpc_handler.arguments = match rpc_handler.arguments { - PathArguments::None => PathArguments::AngleBracketed( - syn::parse2(quote! { :: }) - .unwrap(), - ), - PathArguments::AngleBracketed(mut a) => { - a.args.push(syn::parse2(quote! { GenericContext }).unwrap()); - PathArguments::AngleBracketed(a) - } - _ => unreachable!(), - }; - quote_spanned!{ subcommand.span() => - [#subcommand::NAME, rest] => #subcommand::#rpc_handler(ctx, parent_data, request, response, rest, ::rpc_toolkit::command_helpers::prelude::from_value(args.rest)?).await - } - }); - let subcmd_impl = quote! { - match method.splitn(2, ".").chain(std::iter::repeat("")).take(2).collect::>().as_slice() { - #( - #subcmd_impl, - )* - _ => Err(::rpc_toolkit::command_helpers::prelude::RpcError { - data: Some(method.into()), - ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR + (arg, from_matches) + }) + .unzip(); + ( + quote! { + #[derive(::rpc_toolkit::serde::Serialize, ::rpc_toolkit::serde::Deserialize)] + pub struct Params #ty_generics { + #( + #param_arg + )* + } + #[derive(::rpc_toolkit::clap::Parser)] + struct ClapParams { + #( + #clap_param_arg + )* + } + impl #impl_generics ::rpc_toolkit::command_helpers::clap::FromArgMatches for Params #ty_generics #where_clause { + fn from_arg_matches(matches: &::rpc_toolkit::command_helpers::clap::ArgMatches) -> Result { + let clap_args = ClapParams::from_arg_matches(matches)?; + Ok(Self { + #( + #clap_param_from_matches + )* }) } - }; - match self_impl { - Some(self_impl) if !matches!(common.exec_ctx, ExecutionContext::CliOnly(_)) => { - let self_impl_fn = &self_impl.path; - let self_impl = if self_impl.is_async { - quote_spanned! { self_impl_fn.span() => - #self_impl_fn(Into::into(ctx), parent_data).await? - } - } else if self_impl.blocking { - quote_spanned! { self_impl_fn.span() => - { - let ctx = Into::into(ctx); - ::rpc_toolkit::command_helpers::prelude::spawn_blocking(move || #self_impl_fn(ctx, parent_data)).await? - } - } - } else { - quote_spanned! { self_impl_fn.span() => - #self_impl_fn(Into::into(ctx), parent_data)? - } - }; - quote! { - #param_struct_def - - pub async fn rpc_handler#generics( - ctx: GenericContext, - parent_data: #parent_data_ty, - request: &::rpc_toolkit::command_helpers::prelude::RequestParts, - response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, - method: &str, - args: Params#param_ty_generics, - ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { - #cmd_preprocess - - if method.is_empty() { - Ok(::rpc_toolkit::command_helpers::prelude::to_value(&#self_impl)?) - } else { - #subcmd_impl - } - } - } - } - _ => { - quote! { - #param_struct_def - - pub async fn rpc_handler#generics( - ctx: GenericContext, - parent_data: #parent_data_ty, - request: &::rpc_toolkit::command_helpers::prelude::RequestParts, - response: &mut ::rpc_toolkit::command_helpers::prelude::ResponseParts, - method: &str, - args: Params#param_ty_generics, - ) -> Result<::rpc_toolkit::command_helpers::prelude::Value, ::rpc_toolkit::command_helpers::prelude::RpcError> { - #cmd_preprocess - - #subcmd_impl - } - } + fn update_from_arg_matches(&mut self, matches: &::rpc_toolkit::command_helpers::clap::ArgMatches) -> Result<(), ::rpc_toolkit::command_helpers::clap::Error> { + unimplemented!() } } - } - } -} - -fn cli_handler( - fn_name: &Ident, - fn_generics: &Generics, - opt: &mut Options, - params: &[ParamType], -) -> TokenStream { - let mut parent_data_ty = quote! { () }; - let mut generics = fn_generics.clone(); - generics.params.push(macro_try!(syn::parse2( - quote! { ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize } - ))); - generics.params.push(macro_try!(syn::parse2( - quote! { GenericContext: CommandContextCli } - ))); - if generics.lt_token.is_none() { - generics.lt_token = Some(Default::default()); - } - if generics.gt_token.is_none() { - generics.gt_token = Some(Default::default()); - } - let (_, fn_type_generics, _) = fn_generics.split_for_impl(); - let fn_turbofish = fn_type_generics.as_turbofish(); - let fn_path: Path = macro_try!(syn::parse2(quote! { super::#fn_name#fn_turbofish })); - let is_parent = matches!(opt, Options::Parent { .. }); - let param: Vec<_> = params - .iter() - .map(|param| match param { - ParamType::Arg(arg) => { - let name = arg.name.clone().unwrap(); - let field_name = Ident::new(&format!("arg_{}", name), name.span()); - quote! { params.#field_name.clone() } - } - ParamType::Context(ty) => { - if is_parent { - quote! { >::into(ctx.clone()) } - } else { - quote! { >::into(ctx) } + impl #impl_generics ::rpc_toolkit::command_helpers::clap::CommandFactory for Params #ty_generics #where_clause { + fn command() -> ::rpc_toolkit::command_helpers::clap::Command { + ClapParams::command() } - } - ParamType::ParentData(ty) => { - parent_data_ty = quote! { #ty }; - quote! { parent_data } - } - ParamType::Request => quote! { request }, - ParamType::Response => quote! { response }, - ParamType::None => unreachable!(), - }) - .collect(); - let mut param_generics_filter = GenericFilter::new(fn_generics); - for param in params { - if let ParamType::Arg(a) = param { - param_generics_filter.fold_type(a.ty.clone()); - } - } - let mut param_generics = param_generics_filter.finish(); - param_generics.params.push(macro_try!(syn::parse2(quote! { - ParentParams: ::rpc_toolkit::command_helpers::prelude::Serialize - }))); - if param_generics.lt_token.is_none() { - param_generics.lt_token = Some(Default::default()); - } - if param_generics.gt_token.is_none() { - param_generics.gt_token = Some(Default::default()); - } - let (_, param_ty_generics, _) = param_generics.split_for_impl(); - let mut arg_def = Vec::new(); - for param in params { - match param { - ParamType::Arg(arg) => { - let name = arg.name.clone().unwrap(); - let rename = arg - .rename - .clone() - .unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); - let field_name = Ident::new(&format!("arg_{}", name), name.span()); - let ty = arg.ty.clone(); - arg_def.push(quote! { - #[serde(rename = #rename)] - #field_name: #ty, - }) - } - _ => (), - } - } - let arg = params - .iter() - .filter_map(|param| { - if let ParamType::Arg(a) = param { - Some(a) - } else { - None - } - }) - .map(|arg| { - let name = arg.name.clone().unwrap(); - let arg_name = arg.rename.clone().unwrap_or_else(|| LitStr::new(&name.to_string(), name.span())); - let field_name = Ident::new(&format!("arg_{}", name), name.span()); - if arg.stdin.is_some() { - if let Some(parse) = &arg.parse { - quote! { - #field_name: #parse(&mut std::io::stdin(), matches)?, - } - } else { - quote! { - #field_name: ::rpc_toolkit::command_helpers::prelude::default_stdin_parser(&mut std::io::stdin(), matches)?, - } + fn command_for_update() -> ::rpc_toolkit::command_helpers::clap::Command { + ClapParams::command_for_update() } - } else if arg.check_is_present { - quote! { - #field_name: matches.is_present(#arg_name), - } - } else if arg.count.is_some() { - quote! { - #field_name: matches.occurrences_of(#arg_name), - } - } else { - let parse_val = if let Some(parse) = &arg.parse { - quote! { - #parse(arg_val, matches) - } - } else { - quote! { - ::rpc_toolkit::command_helpers::prelude::default_arg_parser(arg_val, matches) - } - }; - if arg.optional { - quote! { - #field_name: if let Some(arg_val) = matches.value_of(#arg_name) { - Some(#parse_val?) - } else { - None - }, - } - } else if let Some(default) = &arg.default { - if let Some(default) = default { - let path: Path = match syn::parse_str(&default.value()) { - Ok(a) => a, - Err(e) => return e.into_compile_error(), - }; - quote! { - #field_name: if let Some(arg_val) = matches.value_of(#arg_name) { - #parse_val? - } else { - #path() - }, - } - } else { - quote! { - #field_name: if let Some(arg_val) = matches.value_of(#arg_name) { - #parse_val? - } else { - Default::default() - }, - } - } - } else if arg.multiple.is_some() { - quote! { - #field_name: matches.values_of(#arg_name).iter().flatten().map(|arg_val| #parse_val).collect::>()?, - } - } else { - quote! { - #field_name: { - let arg_val = matches.value_of(#arg_name).unwrap(); - #parse_val? - }, - } - } - } - }); - let param_struct_def = quote! { - #[derive(::rpc_toolkit::command_helpers::prelude::Serialize)] - struct Params#param_ty_generics { - #( - #arg_def - )* - #[serde(flatten)] - rest: ParentParams, - } - let params: Params#param_ty_generics = Params { - #( - #arg - )* - rest: parent_params, - }; - }; - let create_rt = quote! { - let rt_ref = if let Some(rt) = rt.as_mut() { - &*rt - } else { - rt = Some(::rpc_toolkit::command_helpers::prelude::Runtime::new().map_err(|e| ::rpc_toolkit::command_helpers::prelude::RpcError { - data: Some(format!("{}", e).into()), - ..::rpc_toolkit::command_helpers::prelude::yajrc::INTERNAL_ERROR - })?); - rt.as_ref().unwrap() - }; - }; - let display = if let Some(display) = &opt.common().display { - quote! { #display } - } else { - quote! { ::rpc_toolkit::command_helpers::prelude::default_display } - }; - match opt { - Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::RpcOnly(_)) => quote! { - pub fn cli_handler#generics( - _ctx: GenericContext, - _parent_data: #parent_data_ty, - _rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, - _matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches, - method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, - _parent_params: ParentParams, - ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { - Err(::rpc_toolkit::command_helpers::prelude::RpcError { - data: Some(method.into()), - ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR - }) } }, - Options::Leaf(opt) if matches!(opt.exec_ctx, ExecutionContext::Standard) => { - let param = param.into_iter().map(|_| quote! { unreachable!() }); - let invocation = if opt.is_async { - quote! { - rt_ref.block_on(#fn_path(#(#param),*))? - } - } else { - quote! { - #fn_path(#(#param),*)? - } - }; + param_generics, + ) +} + +fn build_inherited(parent_data: Option, generics: &Generics) -> (TokenStream, Generics) { + let mut inherited_generics_filter = GenericFilter::new(generics); + if let Some(inherited) = &parent_data { + inherited_generics_filter.fold_type(inherited.clone()); + } + let inherited_generics = inherited_generics_filter.finish(); + if let Some(inherited) = parent_data { + let (_, ty_generics, _) = inherited_generics.split_for_impl(); + ( quote! { - pub fn cli_handler#generics( - ctx: GenericContext, - parent_data: #parent_data_ty, - mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, - matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches, - method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, - parent_params: ParentParams, - ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { - #param_struct_def - - #create_rt - - #[allow(unreachable_code)] - let return_ty = if true { - ::rpc_toolkit::command_helpers::prelude::PhantomData - } else { - let ctx_new = unreachable!(); - ::rpc_toolkit::command_helpers::prelude::match_types(&ctx, &ctx_new); - let ctx = ctx_new; - ::rpc_toolkit::command_helpers::prelude::make_phantom(#invocation) - }; - - let res = rt_ref.block_on(::rpc_toolkit::command_helpers::prelude::call_remote(ctx, method.as_ref(), params, return_ty))?; - Ok(#display(res.result?, matches)) - } - } - } - Options::Leaf(opt) => { - if let ExecutionContext::CustomCli { - ref cli, is_async, .. - } = opt.exec_ctx - { - let fn_path = cli; - let cli_param = params.iter().filter_map(|param| match param { - ParamType::Arg(arg) => { - let name = arg.name.clone().unwrap(); - let field_name = Ident::new(&format!("arg_{}", name), name.span()); - Some(quote! { params.#field_name.clone() }) - } - ParamType::Context(_) => Some(quote! { Into::into(ctx) }), - ParamType::ParentData(_) => Some(quote! { parent_data }), - ParamType::Request => None, - ParamType::Response => None, - ParamType::None => unreachable!(), - }); - let invocation = if is_async { - quote! { - rt_ref.block_on(#fn_path(#(#cli_param),*))? - } - } else { - quote! { - #fn_path(#(#cli_param),*)? - } - }; - let display_res = if let Some(display_fn) = &opt.display { - quote! { - #display_fn(#invocation, matches) - } - } else { - quote! { - ::rpc_toolkit::command_helpers::prelude::default_display(#invocation, matches) - } - }; - let rt_action = if is_async { - create_rt - } else { - quote! { - drop(rt); - } - }; - quote! { - pub fn cli_handler#generics( - ctx: GenericContext, - parent_data: #parent_data_ty, - mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, - matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches, - _method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, - parent_params: ParentParams - ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { - #param_struct_def - - #rt_action - - Ok(#display_res) - } - } - } else { - let invocation = if opt.is_async { - quote! { - rt_ref.block_on(#fn_path(#(#param),*))? - } - } else { - quote! { - #fn_path(#(#param),*)? - } - }; - let display_res = if let Some(display_fn) = &opt.display { - quote! { - #display_fn(#invocation, matches) - } - } else { - quote! { - ::rpc_toolkit::command_helpers::prelude::default_display(#invocation, matches) - } - }; - let rt_action = if opt.is_async { - create_rt - } else { - quote! { - drop(rt); - } - }; - quote! { - pub fn cli_handler#generics( - ctx: GenericContext, - parent_data: #parent_data_ty, - mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, - matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches, - _method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, - parent_params: ParentParams - ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { - #param_struct_def - - #rt_action - - Ok(#display_res) - } - } - } - } - Options::Parent(ParentOptions { - common, - subcommands, - self_impl, - }) => { - let cmd_preprocess = if common.is_async { - quote! { - #create_rt - let parent_data = rt_ref.block_on(#fn_path(#(#param),*))?; - } - } else { - quote! { - let parent_data = #fn_path(#(#param),*)?; - } - }; - let subcmd_impl = subcommands.iter().map(|subcommand| { - let mut subcommand = subcommand.clone(); - let mut cli_handler = PathSegment { - ident: Ident::new("cli_handler", Span::call_site()), - arguments: std::mem::replace( - &mut subcommand.segments.last_mut().unwrap().arguments, - PathArguments::None, - ), - }; - cli_handler.arguments = match cli_handler.arguments { - PathArguments::None => PathArguments::AngleBracketed( - syn::parse2(quote! { :: }) - .unwrap(), - ), - PathArguments::AngleBracketed(mut a) => { - a.args - .push(syn::parse2(quote! { Params#param_ty_generics }).unwrap()); - a.args.push(syn::parse2(quote! { GenericContext }).unwrap()); - PathArguments::AngleBracketed(a) - } - _ => unreachable!(), - }; - quote_spanned! { subcommand.span() => - Some((#subcommand::NAME, sub_m)) => { - let method = if method.is_empty() { - #subcommand::NAME.into() - } else { - method + "." + #subcommand::NAME - }; - #subcommand::#cli_handler(ctx, parent_data, rt, sub_m, method, params) - }, - } - }); - let self_impl = match (self_impl, &common.exec_ctx) { - (Some(self_impl), ExecutionContext::CliOnly(_)) - | (Some(self_impl), ExecutionContext::Local(_)) - | (Some(self_impl), ExecutionContext::CustomCli { .. }) => { - let (self_impl_fn, is_async) = - if let ExecutionContext::CustomCli { cli, is_async, .. } = &common.exec_ctx - { - (cli, *is_async) - } else { - (&self_impl.path, self_impl.is_async) - }; - let create_rt = if common.is_async { - None - } else { - Some(create_rt) - }; - let self_impl = if is_async { - quote_spanned! { self_impl_fn.span() => - #create_rt - rt_ref.block_on(#self_impl_fn(Into::into(ctx), parent_data))? - } - } else { - quote_spanned! { self_impl_fn.span() => - #self_impl_fn(Into::into(ctx), parent_data)? - } - }; - quote! { - Ok(#display(#self_impl, matches)), - } - } - (Some(self_impl), ExecutionContext::Standard) => { - let self_impl_fn = &self_impl.path; - let self_impl = if self_impl.is_async { - quote! { - rt_ref.block_on(#self_impl_fn(unreachable!(), parent_data)) - } - } else { - quote! { - #self_impl_fn(unreachable!(), parent_data) - } - }; - let create_rt = if common.is_async { - None - } else { - Some(create_rt) - }; - quote! { - { - #create_rt - - #[allow(unreachable_code)] - let return_ty = if true { - ::rpc_toolkit::command_helpers::prelude::PhantomData - } else { - ::rpc_toolkit::command_helpers::prelude::make_phantom(#self_impl?) - }; - - let res = rt_ref.block_on(::rpc_toolkit::command_helpers::prelude::call_remote(ctx, method.as_ref(), params, return_ty))?; - Ok(#display(res.result?, matches)) - } - } - } - (None, _) | (Some(_), ExecutionContext::RpcOnly(_)) => quote! { - Err(::rpc_toolkit::command_helpers::prelude::RpcError { - data: Some(method.into()), - ..::rpc_toolkit::command_helpers::prelude::yajrc::METHOD_NOT_FOUND_ERROR - }), - }, - }; - quote! { - pub fn cli_handler#generics( - ctx: GenericContext, - parent_data: #parent_data_ty, - mut rt: Option<::rpc_toolkit::command_helpers::prelude::Runtime>, - matches: &::rpc_toolkit::command_helpers::prelude::ArgMatches, - method: ::rpc_toolkit::command_helpers::prelude::Cow<'_, str>, - parent_params: ParentParams, - ) -> Result<(), ::rpc_toolkit::command_helpers::prelude::RpcError> { - #param_struct_def - - #cmd_preprocess - - match matches.subcommand() { - #( - #subcmd_impl - )* - _ => #self_impl - } - } - } - } + type InheritedParams #ty_generics = #inherited; + }, + inherited_generics, + ) + } else { + ( + quote! { type InheritedParams = ::rpc_toolkit::NoParams; }, + inherited_generics, + ) } } pub fn build(args: AttributeArgs, mut item: ItemFn) -> TokenStream { - let mut params = macro_try!(parse_param_attrs(&mut item)); + let params = macro_try!(parse_param_attrs(&mut item)); let mut opt = macro_try!(parse_command_attr(args)); - if let Some(a) = &opt.common().blocking { - if item.sig.asyncness.is_some() { - return Error::new(a.span(), "cannot use `blocking` on an async fn").to_compile_error(); - } - } - opt.common().is_async = item.sig.asyncness.is_some(); let fn_vis = &item.vis; let fn_name = &item.sig.ident; - let fn_generics = &item.sig.generics; - let command_name_str = opt + let (params_impl, params_generics) = build_params( + params + .iter() + .filter_map(|a| { + if let ParamType::Arg(a) = a { + Some(a.clone()) + } else { + None + } + }) + .collect(), + &item.sig.generics, + ); + let (_, params_ty_generics, _) = params_generics.split_for_impl(); + let (inherited_impl, inherited_generics) = build_inherited( + params.iter().find_map(|a| { + if let ParamType::ParentData(a) = a { + Some(a.clone()) + } else { + None + } + }), + &item.sig.generics, + ); + let (_, inherited_ty_generics, _) = inherited_generics.split_for_impl(); + let mut params_and_inherited_generics_filter = GenericFilter::new(&item.sig.generics); + params_and_inherited_generics_filter.fold_generics(params_generics.clone()); + params_and_inherited_generics_filter.fold_generics(inherited_generics.clone()); + let params_and_inherited_generics = params_and_inherited_generics_filter.finish(); + let (_, params_and_inherited_ty_generics, _) = params_and_inherited_generics.split_for_impl(); + let phantom_ty = params_and_inherited_generics + .type_params() + .map(|t| &t.ident) + .map(|t| quote! { #t }) + .chain( + params_and_inherited_generics + .lifetimes() + .map(|l| &l.lifetime) + .map(|l| quote! { &#l () }), + ); + let phantom_ty = quote! { ( #( #phantom_ty, )* ) }; + let module = if let Options::Parent(parent) = &opt { + // let subcommands = parent.subcommands.iter().map(|c| quote! { .subcommand_with_inherited_remote_cli(c::handler(), |a, b| ) }) + // quote! { + // pub type Handler #params_and_inherited_ty_generics = ::rpc_toolkit::ParentHandler; + // #params_impl + // #inherited_impl + // pub fn handler #params_and_inherited_ty_generics () -> Handler #params_and_inherited_ty_generics { + // Handler::new() + // #( + // #subcommands + // )* + // } + // } + Error::new(Span::call_site(), "derived parent handlers not implemented").to_compile_error() + } else { + let (ok_ty, err_ty) = match &item.sig.output { + ReturnType::Type(_, p) => match &**p { + Type::Path(p) if p.path.segments.last().unwrap().ident == "Result" => { + match &p.path.segments.last().unwrap().arguments { + PathArguments::AngleBracketed(a) if a.args.len() == 2 => { + match (a.args.first(), a.args.last()) { + ( + Some(GenericArgument::Type(ok)), + Some(GenericArgument::Type(err)), + ) => (ok, err), + _ => { + return Error::new(a.span(), "return type must be a Result") + .to_compile_error() + } + } + } + a => { + return Error::new(a.span(), "return type must be a Result") + .to_compile_error() + } + } + } + a => { + return Error::new(a.span(), "return type must be a Result").to_compile_error(); + } + }, + a => return Error::new(a.span(), "return type must be a Result").to_compile_error(), + }; + let handler_impl: TokenStream = todo!(); + let cli_bindings_impl: TokenStream = todo!(); + quote! { + pub struct Handler #params_and_inherited_ty_generics (::core::marker::PhantomData<#phantom_ty>); + impl #params_and_inherited_ty_generics ::core::fmt::Debug for Handler #params_and_inherited_ty_generics { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_tuple("Handler") + .finish() + } + } + impl #params_and_inherited_ty_generics ::core::clone::Clone for Handler #params_and_inherited_ty_generics { + fn clone(&self) -> Self { + Self(::core::marker::PhantomData) + } + } + impl #params_and_inherited_ty_generics ::core::marker::Copy for Handler #params_and_inherited_ty_generics { } + #params_impl + #inherited_impl + impl #params_and_inherited_ty_generics ::rpc_toolkit::HandlerTypes for Handler #params_and_inherited_ty_generics { + type Params = Params; + type InheritedParams = InheritedParams; + type Ok = #ok_ty; + type Err = #err_ty; + } + #handler_impl + #cli_bindings_impl + pub fn handler #params_and_inherited_ty_generics () -> Handler #params_and_inherited_ty_generics { + Handler(::core::marker::PhantomData) + } + } + }; + + let fn_rename = opt .common() .rename .clone() - .unwrap_or_else(|| LitStr::new(&fn_name.to_string(), fn_name.span())); - let is_async = LitBool::new( - opt.common().is_async, - item.sig - .asyncness - .map(|a| a.span()) - .unwrap_or_else(Span::call_site), - ); - let ctx_ty = params.iter().find_map(|a| { - if let ParamType::Context(ty) = a { - Some(ty.clone()) - } else { - None - } - }); - let ctx_trait = ctx_trait(ctx_ty, &mut opt); - let metadata = metadata(&mut opt); - let build_app = build_app(command_name_str.clone(), &mut opt, &mut params); - let rpc_handler = rpc_handler(fn_name, fn_generics, &opt, ¶ms); - let cli_handler = cli_handler(fn_name, fn_generics, &mut opt, ¶ms); + .unwrap_or(LitStr::new(&fn_name.to_string(), fn_name.span())); let res = quote! { #item #fn_vis mod #fn_name { use super::*; - - pub const NAME: &'static str = #command_name_str; - pub const ASYNC: bool = #is_async; - - #ctx_trait - - #metadata - - #build_app - - #rpc_handler - - #cli_handler + pub const NAME: &str = #fn_rename; + #module } }; - if opt.common().macro_debug { - panic!("EXPANDED MACRO:\n{}", res); + if let Some(debug) = &opt.common().macro_debug { + Error::new(debug.span(), res).into_compile_error() + } else { + res } - res } diff --git a/rpc-toolkit-macro-internals/src/command/mod.rs b/rpc-toolkit-macro-internals/src/command/mod.rs index 3876d78..662064a 100644 --- a/rpc-toolkit-macro-internals/src/command/mod.rs +++ b/rpc-toolkit-macro-internals/src/command/mod.rs @@ -25,7 +25,7 @@ impl Default for ExecutionContext { #[derive(Default)] pub struct LeafOptions { - macro_debug: bool, + macro_debug: Option, blocking: Option, is_async: bool, aliases: Vec, @@ -34,6 +34,7 @@ pub struct LeafOptions { exec_ctx: ExecutionContext, display: Option, metadata: HashMap, + clap_attr: Vec, } pub struct SelfImplInfo { @@ -79,22 +80,18 @@ impl Options { } } +#[derive(Clone)] pub struct ArgOptions { ty: Type, - optional: bool, - check_is_present: bool, - help: Option, name: Option, rename: Option, - short: Option, - long: Option, parse: Option, - default: Option>, - count: Option, - multiple: Option, stdin: Option, + default: Option, + clap_attr: Vec, } +#[derive(Clone)] pub enum ParamType { None, Arg(ArgOptions), diff --git a/rpc-toolkit-macro-internals/src/command/parse.rs b/rpc-toolkit-macro-internals/src/command/parse.rs index fe45a03..c504b7d 100644 --- a/rpc-toolkit-macro-internals/src/command/parse.rs +++ b/rpc-toolkit-macro-internals/src/command/parse.rs @@ -7,7 +7,7 @@ pub fn parse_command_attr(args: AttributeArgs) -> Result { for arg in args { match arg { NestedMeta::Meta(Meta::Path(p)) if p.is_ident("macro_debug") => { - opt.common().macro_debug = true; + opt.common().macro_debug = Some(p); } NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("subcommands") => { let inner = opt.to_parent()?; @@ -536,25 +536,20 @@ pub fn parse_command_attr(args: AttributeArgs) -> Result { pub fn parse_arg_attr(attr: Attribute, arg: PatType) -> Result { let arg_span = arg.span(); + let meta = attr.parse_meta()?; let mut opt = ArgOptions { ty: *arg.ty, - optional: false, - check_is_present: false, - help: None, name: match *arg.pat { Pat::Ident(i) => Some(i.ident), _ => None, }, rename: None, - short: None, - long: None, parse: None, - default: None, - count: None, - multiple: None, stdin: None, + default: None, + clap_attr: Vec::new(), }; - match attr.parse_meta()? { + match meta { Meta::List(list) => { for arg in list.nested { match arg { @@ -591,34 +586,10 @@ pub fn parse_arg_attr(attr: Attribute, arg: PatType) -> Result { return Err(Error::new(nv.path.span(), "`parse` cannot be assigned to")); } NestedMeta::Meta(Meta::Path(p)) if p.is_ident("stdin") => { - if opt.short.is_some() { + if !opt.clap_attr.is_empty() { return Err(Error::new( p.span(), - "`stdin` and `short` are mutually exclusive", - )); - } - if opt.long.is_some() { - return Err(Error::new( - p.span(), - "`stdin` and `long` are mutually exclusive", - )); - } - if opt.help.is_some() { - return Err(Error::new( - p.span(), - "`stdin` and `help` are mutually exclusive", - )); - } - if opt.count.is_some() { - return Err(Error::new( - p.span(), - "`stdin` and `count` are mutually exclusive", - )); - } - if opt.multiple.is_some() { - return Err(Error::new( - p.span(), - "`stdin` and `multiple` are mutually exclusive", + "`stdin` and clap parser attributes are mutually exclusive", )); } opt.stdin = Some(p); @@ -632,79 +603,6 @@ pub fn parse_arg_attr(attr: Attribute, arg: PatType) -> Result { NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("stdin") => { return Err(Error::new(nv.path.span(), "`stdin` cannot be assigned to")); } - NestedMeta::Meta(Meta::Path(p)) if p.is_ident("count") => { - if opt.stdin.is_some() { - return Err(Error::new( - p.span(), - "`stdin` and `count` are mutually exclusive", - )); - } - if opt.multiple.is_some() { - return Err(Error::new( - p.span(), - "`count` and `multiple` are mutually exclusive", - )); - } - opt.count = Some(p); - } - NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("count") => { - return Err(Error::new( - list.path.span(), - "`count` does not take any arguments", - )); - } - NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("count") => { - return Err(Error::new(nv.path.span(), "`count` cannot be assigned to")); - } - NestedMeta::Meta(Meta::Path(p)) if p.is_ident("multiple") => { - if opt.stdin.is_some() { - return Err(Error::new( - p.span(), - "`stdin` and `multiple` are mutually exclusive", - )); - } - if opt.count.is_some() { - return Err(Error::new( - p.span(), - "`count` and `multiple` are mutually exclusive", - )); - } - opt.multiple = Some(p); - } - NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("multiple") => { - return Err(Error::new( - list.path.span(), - "`multiple` does not take any arguments", - )); - } - NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("count") => { - return Err(Error::new(nv.path.span(), "`count` cannot be assigned to")); - } - NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("help") => { - if let Lit::Str(help) = nv.lit { - if opt.help.is_some() { - return Err(Error::new(help.span(), "duplicate argument `help`")); - } - if opt.stdin.is_some() { - return Err(Error::new( - help.span(), - "`stdin` and `help` are mutually exclusive", - )); - } - opt.help = Some(help); - } else { - return Err(Error::new(nv.lit.span(), "help message must be a string")); - } - } - NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("help") => { - return Err(Error::new( - list.path.span(), - "`help` does not take any arguments", - )); - } - NestedMeta::Meta(Meta::Path(p)) if p.is_ident("help") => { - return Err(Error::new(p.span(), "`help` must be assigned to")); - } NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("rename") => { if let Lit::Str(rename) = nv.lit { if opt.rename.is_some() { @@ -713,6 +611,12 @@ pub fn parse_arg_attr(attr: Attribute, arg: PatType) -> Result { "duplicate argument `rename`", )); } + opt.clap_attr + .push(NestedMeta::Meta(Meta::NameValue(MetaNameValue { + path: Path::from(Ident::new("name", nv.path.span())), + eq_token: nv.eq_token, + lit: Lit::Str(rename.clone()), + }))); opt.rename = Some(rename); } else { return Err(Error::new(nv.lit.span(), "`rename` must be a string")); @@ -727,68 +631,8 @@ pub fn parse_arg_attr(attr: Attribute, arg: PatType) -> Result { NestedMeta::Meta(Meta::Path(p)) if p.is_ident("rename") => { return Err(Error::new(p.span(), "`rename` must be assigned to")); } - NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("short") => { - if let Lit::Char(short) = nv.lit { - if opt.short.is_some() { - return Err(Error::new(short.span(), "duplicate argument `short`")); - } - if opt.stdin.is_some() { - return Err(Error::new( - short.span(), - "`stdin` and `short` are mutually exclusive", - )); - } - opt.short = Some(short); - } else { - return Err(Error::new(nv.lit.span(), "`short` must be a char")); - } - } - NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("short") => { - return Err(Error::new( - list.path.span(), - "`short` does not take any arguments", - )); - } - NestedMeta::Meta(Meta::Path(p)) if p.is_ident("short") => { - return Err(Error::new(p.span(), "`short` must be assigned to")); - } - NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("long") => { - if let Lit::Str(long) = nv.lit { - if opt.long.is_some() { - return Err(Error::new(long.span(), "duplicate argument `long`")); - } - if opt.stdin.is_some() { - return Err(Error::new( - long.span(), - "`stdin` and `long` are mutually exclusive", - )); - } - opt.long = Some(long); - } else { - return Err(Error::new(nv.lit.span(), "`long` must be a string")); - } - } - NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("long") => { - return Err(Error::new( - list.path.span(), - "`long` does not take any arguments", - )); - } - NestedMeta::Meta(Meta::Path(p)) if p.is_ident("long") => { - return Err(Error::new(p.span(), "`long` must be assigned to")); - } NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("default") => { - if let Lit::Str(default) = nv.lit { - if opt.default.is_some() { - return Err(Error::new( - default.span(), - "duplicate argument `default`", - )); - } - opt.default = Some(Some(default)); - } else { - return Err(Error::new(nv.lit.span(), "`default` must be a string")); - } + return Err(Error::new(nv.lit.span(), "`default` cannot be assigned to")); } NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("default") => { return Err(Error::new( @@ -797,13 +641,21 @@ pub fn parse_arg_attr(attr: Attribute, arg: PatType) -> Result { )); } NestedMeta::Meta(Meta::Path(p)) if p.is_ident("default") => { - if opt.default.is_some() { - return Err(Error::new(p.span(), "duplicate argument `default`")); - } - opt.default = Some(None); + opt.clap_attr + .push(NestedMeta::Meta(Meta::Path(Path::from(Ident::new( + "default_value_t", + p.span(), + ))))); + opt.default = Some(p); } - _ => { - return Err(Error::new(arg.span(), "unknown argument")); + unknown => { + if opt.stdin.is_some() { + return Err(Error::new( + unknown.span(), + "`stdin` and clap parser attributes are mutually exclusive", + )); + } + opt.clap_attr.push(unknown); } } } diff --git a/rpc-toolkit-macro-internals/src/lib.rs b/rpc-toolkit-macro-internals/src/lib.rs index c8535d7..2bbe0cb 100644 --- a/rpc-toolkit-macro-internals/src/lib.rs +++ b/rpc-toolkit-macro-internals/src/lib.rs @@ -8,14 +8,5 @@ macro_rules! macro_try { } mod command; -mod rpc_handler; -mod rpc_server; -mod run_cli; pub use command::build::build as build_command; -pub use rpc_handler::build::build as build_rpc_handler; -pub use rpc_handler::RpcHandlerArgs; -pub use rpc_server::build::build as build_rpc_server; -pub use rpc_server::RpcServerArgs; -pub use run_cli::build::build as build_run_cli; -pub use run_cli::RunCliArgs; diff --git a/rpc-toolkit-macro-internals/src/rpc_handler/build.rs b/rpc-toolkit-macro-internals/src/rpc_handler/build.rs deleted file mode 100644 index eb7d101..0000000 --- a/rpc-toolkit-macro-internals/src/rpc_handler/build.rs +++ /dev/null @@ -1,129 +0,0 @@ -use proc_macro2::{Span, TokenStream}; -use quote::quote; -use syn::spanned::Spanned; - -use super::*; - -pub fn build(args: RpcHandlerArgs) -> TokenStream { - let mut command = args.command; - let mut arguments = std::mem::replace( - &mut command.segments.last_mut().unwrap().arguments, - PathArguments::None, - ); - let command_module = command.clone(); - if let PathArguments::AngleBracketed(a) = &mut arguments { - a.args.push(syn::parse2(quote! { _ }).unwrap()); - } - command.segments.push(PathSegment { - ident: Ident::new("rpc_handler", command.span()), - arguments, - }); - let ctx = args.ctx; - let parent_data = if let Some(data) = args.parent_data { - quote! { #data } - } else { - quote! { () } - }; - let status_fn = args.status_fn.unwrap_or_else(|| { - syn::parse2(quote! { |_| ::rpc_toolkit::hyper::StatusCode::OK }).unwrap() - }); - let middleware_name_clone = (0..) - .map(|i| { - Ident::new( - &format!("__rpc_toolkit__rpc_handler__middleware_clone_{}", i), - Span::call_site(), - ) - }) - .take(args.middleware.len()); - let middleware_name_clone2 = middleware_name_clone.clone(); - let middleware_name_clone3 = middleware_name_clone.clone(); - let middleware_name_clone4 = middleware_name_clone.clone(); - let middleware_name_pre = (0..) - .map(|i| Ident::new(&format!("middleware_pre_{}", i), Span::call_site())) - .take(args.middleware.len()); - let middleware_name_pre2 = middleware_name_pre.clone(); - let middleware_name_post = (0..) - .map(|i| Ident::new(&format!("middleware_post_{}", i), Span::call_site())) - .take(args.middleware.len()); - let middleware_name_post_inv = middleware_name_post - .clone() - .collect::>() - .into_iter() - .rev(); - let middleware_name = (0..) - .map(|i| Ident::new(&format!("middleware_{}", i), Span::call_site())) - .take(args.middleware.len()); - let middleware_name2 = middleware_name.clone(); - let middleware = args.middleware.iter(); - let res = quote! { - { - let __rpc_toolkit__rpc_handler__context = #ctx; - let __rpc_toolkit__rpc_handler__parent_data = #parent_data; - let __rpc_toolkit__rpc_handler__status_fn = #status_fn; - #( - let #middleware_name_clone = ::std::sync::Arc::new(#middleware); - )* - let res: ::rpc_toolkit::RpcHandler = ::std::sync::Arc::new(move |mut req| { - let ctx = __rpc_toolkit__rpc_handler__context.clone(); - let parent_data = __rpc_toolkit__rpc_handler__parent_data.clone(); - let metadata = #command_module::Metadata::default(); - #( - let #middleware_name_clone3 = #middleware_name_clone2.clone(); - )* - ::rpc_toolkit::futures::FutureExt::boxed(async move { - #( - let #middleware_name_pre = match ::rpc_toolkit::rpc_server_helpers::constrain_middleware(&*#middleware_name_clone4)(&mut req, metadata).await? { - Ok(a) => a, - Err(res) => return Ok(res), - }; - )* - let (mut req_parts, req_body) = req.into_parts(); - let (mut res_parts, _) = ::rpc_toolkit::hyper::Response::new(()).into_parts(); - let rpc_req = ::rpc_toolkit::rpc_server_helpers::make_request(&req_parts, req_body).await; - match rpc_req { - Ok(mut rpc_req) => { - #( - let #middleware_name_post = match #middleware_name_pre2(&mut req_parts, &mut rpc_req).await? { - Ok(a) => a, - Err(res) => return Ok(res), - }; - )* - let mut rpc_res = match ::rpc_toolkit::serde_json::from_value(::rpc_toolkit::serde_json::Value::Object(rpc_req.params)) { - Ok(params) => #command(ctx, parent_data, &req_parts, &mut res_parts, ::rpc_toolkit::yajrc::RpcMethod::as_str(&rpc_req.method), params).await, - Err(e) => Err(e.into()) - }; - #( - let #middleware_name = match #middleware_name_post_inv(&mut res_parts, &mut rpc_res).await? { - Ok(a) => a, - Err(res) => return Ok(res), - }; - )* - let mut res = ::rpc_toolkit::rpc_server_helpers::to_response( - &req_parts.headers, - res_parts, - Ok(( - rpc_req.id, - rpc_res, - )), - __rpc_toolkit__rpc_handler__status_fn, - )?; - #( - #middleware_name2(&mut res).await?; - )* - Ok::<_, ::rpc_toolkit::hyper::http::Error>(res) - } - Err(e) => ::rpc_toolkit::rpc_server_helpers::to_response( - &req_parts.headers, - res_parts, - Err(e), - __rpc_toolkit__rpc_handler__status_fn, - ), - } - }) - }); - res - } - }; - // panic!("{}", res); - res -} diff --git a/rpc-toolkit-macro-internals/src/rpc_handler/mod.rs b/rpc-toolkit-macro-internals/src/rpc_handler/mod.rs deleted file mode 100644 index 2683733..0000000 --- a/rpc-toolkit-macro-internals/src/rpc_handler/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -use syn::*; - -pub struct RpcHandlerArgs { - pub(crate) command: Path, - pub(crate) ctx: Expr, - pub(crate) parent_data: Option, - pub(crate) status_fn: Option, - pub(crate) middleware: punctuated::Punctuated, -} - -pub mod build; -mod parse; diff --git a/rpc-toolkit-macro-internals/src/rpc_handler/parse.rs b/rpc-toolkit-macro-internals/src/rpc_handler/parse.rs deleted file mode 100644 index e9879d8..0000000 --- a/rpc-toolkit-macro-internals/src/rpc_handler/parse.rs +++ /dev/null @@ -1,50 +0,0 @@ -use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; - -use super::*; - -impl Parse for RpcHandlerArgs { - fn parse(input: ParseStream) -> Result { - let args; - braced!(args in input); - let mut command = None; - let mut ctx = None; - let mut parent_data = None; - let mut status_fn = None; - let mut middleware = Punctuated::new(); - while !args.is_empty() { - let arg_name: syn::Ident = args.parse()?; - let _: token::Colon = args.parse()?; - match arg_name.to_string().as_str() { - "command" => { - command = Some(args.parse()?); - } - "context" => { - ctx = Some(args.parse()?); - } - "parent_data" => { - parent_data = Some(args.parse()?); - } - "status" => { - status_fn = Some(args.parse()?); - } - "middleware" => { - let middlewares; - bracketed!(middlewares in args); - middleware = middlewares.parse_terminated(Expr::parse)?; - } - _ => return Err(Error::new(arg_name.span(), "unknown argument")), - } - if !args.is_empty() { - let _: token::Comma = args.parse()?; - } - } - Ok(RpcHandlerArgs { - command: command.expect("`command` is required"), - ctx: ctx.expect("`context` is required"), - parent_data, - status_fn, - middleware, - }) - } -} diff --git a/rpc-toolkit-macro-internals/src/rpc_server/build.rs b/rpc-toolkit-macro-internals/src/rpc_server/build.rs deleted file mode 100644 index e195999..0000000 --- a/rpc-toolkit-macro-internals/src/rpc_server/build.rs +++ /dev/null @@ -1,25 +0,0 @@ -use proc_macro2::TokenStream; -use quote::quote; - -use super::*; - -pub fn build(mut args: RpcServerArgs) -> TokenStream { - let ctx = std::mem::replace( - &mut args.ctx, - parse2(quote! { __rpc_toolkit__rpc_server__context }).unwrap(), - ); - let handler = crate::rpc_handler::build::build(args); - let res = quote! { - { - let __rpc_toolkit__rpc_server__context = #ctx; - let __rpc_toolkit__rpc_server__builder = ::rpc_toolkit::rpc_server_helpers::make_builder(&__rpc_toolkit__rpc_server__context); - let handler = #handler; - __rpc_toolkit__rpc_server__builder.serve(::rpc_toolkit::hyper::service::make_service_fn(move |_| { - let handler = handler.clone(); - async move { Ok::<_, ::std::convert::Infallible>(::rpc_toolkit::hyper::service::service_fn(move |req| handler(req))) } - })) - } - }; - // panic!("{}", res); - res -} diff --git a/rpc-toolkit-macro-internals/src/rpc_server/mod.rs b/rpc-toolkit-macro-internals/src/rpc_server/mod.rs deleted file mode 100644 index 0f6ca0c..0000000 --- a/rpc-toolkit-macro-internals/src/rpc_server/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -use syn::*; - -pub type RpcServerArgs = crate::RpcHandlerArgs; - -pub mod build; diff --git a/rpc-toolkit-macro-internals/src/run_cli/build.rs b/rpc-toolkit-macro-internals/src/run_cli/build.rs deleted file mode 100644 index bbf56af..0000000 --- a/rpc-toolkit-macro-internals/src/run_cli/build.rs +++ /dev/null @@ -1,81 +0,0 @@ -use proc_macro2::TokenStream; -use quote::quote; -use syn::spanned::Spanned; - -use super::*; - -pub fn build(args: RunCliArgs) -> TokenStream { - let mut command_handler = args.command.clone(); - let mut arguments = std::mem::replace( - &mut command_handler.segments.last_mut().unwrap().arguments, - PathArguments::None, - ); - let command = command_handler.clone(); - if let PathArguments::AngleBracketed(a) = &mut arguments { - a.args.push(syn::parse2(quote! { () }).unwrap()); - a.args.push(syn::parse2(quote! { _ }).unwrap()); - } - command_handler.segments.push(PathSegment { - ident: Ident::new("cli_handler", command.span()), - arguments, - }); - let app = if let Some(mut_app) = args.mut_app { - let ident = mut_app.app_ident; - let body = mut_app.body; - quote! { - { - let #ident = #command::build_app(); - #body - } - } - } else { - quote! { #command::build_app() } - }; - let make_ctx = if let Some(make_ctx) = args.make_ctx { - let ident = make_ctx.matches_ident; - let body = make_ctx.body; - quote! { - { - let #ident = &rpc_toolkit_matches; - #body - } - } - } else { - quote! { &rpc_toolkit_matches } - }; - let parent_data = if let Some(data) = args.parent_data { - quote! { #data } - } else { - quote! { () } - }; - let exit_fn = args.exit_fn.unwrap_or_else(|| { - syn::parse2(quote! { |err: ::rpc_toolkit::yajrc::RpcError| { - eprintln!("{}", err.message); - if let Some(data) = err.data { - eprintln!("{}", data); - } - std::process::exit(err.code); - } }) - .unwrap() - }); - quote! { - { - let rpc_toolkit_matches = #app.get_matches(); - let rpc_toolkit_ctx = #make_ctx; - let rpc_toolkit_parent_data = #parent_data; - if let Err(err) = #command_handler( - rpc_toolkit_ctx, - rpc_toolkit_parent_data, - None, - &rpc_toolkit_matches, - "".into(), - (), - ) { - drop(rpc_toolkit_matches); - (#exit_fn)(err); - } else { - drop(rpc_toolkit_matches); - } - } - } -} diff --git a/rpc-toolkit-macro-internals/src/run_cli/mod.rs b/rpc-toolkit-macro-internals/src/run_cli/mod.rs deleted file mode 100644 index 780ae90..0000000 --- a/rpc-toolkit-macro-internals/src/run_cli/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -use syn::*; - -pub struct MakeCtx { - matches_ident: Ident, - body: Expr, -} - -pub struct MutApp { - app_ident: Ident, - body: Expr, -} - -pub struct RunCliArgs { - command: Path, - mut_app: Option, - make_ctx: Option, - parent_data: Option, - exit_fn: Option, -} - -pub mod build; -mod parse; diff --git a/rpc-toolkit-macro-internals/src/run_cli/parse.rs b/rpc-toolkit-macro-internals/src/run_cli/parse.rs deleted file mode 100644 index 2ca05b1..0000000 --- a/rpc-toolkit-macro-internals/src/run_cli/parse.rs +++ /dev/null @@ -1,68 +0,0 @@ -use syn::parse::{Parse, ParseStream}; - -use super::*; - -impl Parse for MakeCtx { - fn parse(input: ParseStream) -> Result { - let matches_ident = input.parse()?; - let _: token::FatArrow = input.parse()?; - let body = input.parse()?; - Ok(MakeCtx { - matches_ident, - body, - }) - } -} - -impl Parse for MutApp { - fn parse(input: ParseStream) -> Result { - let app_ident = input.parse()?; - let _: token::FatArrow = input.parse()?; - let body = input.parse()?; - Ok(MutApp { app_ident, body }) - } -} - -impl Parse for RunCliArgs { - fn parse(input: ParseStream) -> Result { - let args; - braced!(args in input); - let mut command = None; - let mut mut_app = None; - let mut make_ctx = None; - let mut parent_data = None; - let mut exit_fn = None; - while !args.is_empty() { - let arg_name: syn::Ident = args.parse()?; - let _: token::Colon = args.parse()?; - match arg_name.to_string().as_str() { - "command" => { - command = Some(args.parse()?); - } - "app" => { - mut_app = Some(args.parse()?); - } - "context" => { - make_ctx = Some(args.parse()?); - } - "parent_data" => { - parent_data = Some(args.parse()?); - } - "exit" => { - exit_fn = Some(args.parse()?); - } - _ => return Err(Error::new(arg_name.span(), "unknown argument")), - } - if !args.is_empty() { - let _: token::Comma = args.parse()?; - } - } - Ok(RunCliArgs { - command: command.expect("`command` is required"), - mut_app, - make_ctx, - parent_data, - exit_fn, - }) - } -} diff --git a/rpc-toolkit-macro/src/lib.rs b/rpc-toolkit-macro/src/lib.rs index 83d3682..63965e3 100644 --- a/rpc-toolkit-macro/src/lib.rs +++ b/rpc-toolkit-macro/src/lib.rs @@ -42,21 +42,3 @@ pub fn context(_: TokenStream, _: TokenStream) -> TokenStream { .to_compile_error() .into() } - -#[proc_macro] -pub fn rpc_handler(item: TokenStream) -> TokenStream { - let item = syn::parse_macro_input!(item as RpcHandlerArgs); - build_rpc_handler(item).into() -} - -#[proc_macro] -pub fn rpc_server(item: TokenStream) -> TokenStream { - let item = syn::parse_macro_input!(item as RpcServerArgs); - build_rpc_server(item).into() -} - -#[proc_macro] -pub fn run_cli(item: TokenStream) -> TokenStream { - let item = syn::parse_macro_input!(item as RunCliArgs); - build_run_cli(item).into() -} diff --git a/rpc-toolkit/Cargo.toml b/rpc-toolkit/Cargo.toml index 6b225b2..146f205 100644 --- a/rpc-toolkit/Cargo.toml +++ b/rpc-toolkit/Cargo.toml @@ -27,6 +27,7 @@ imbl-value = "0.1" lazy_format = "2" lazy_static = "1.4" openssl = { version = "0.10", features = ["vendored"] } +pin-project = "1" reqwest = { version = "0.11" } rpc-toolkit-macro = { version = "0.2.2", path = "../rpc-toolkit-macro" } serde = { version = "1.0", features = ["derive"] } diff --git a/rpc-toolkit/src/cli.rs b/rpc-toolkit/src/cli.rs index 6f11f9f..da2ce96 100644 --- a/rpc-toolkit/src/cli.rs +++ b/rpc-toolkit/src/cli.rs @@ -13,8 +13,8 @@ use yajrc::{Id, RpcError}; use crate::util::{internal_error, parse_error, Flat}; use crate::{ - AnyHandler, CliBindingsAny, DynHandler, HandleAny, HandleAnyArgs, HandleArgs, Handler, - IntoContext, Name, ParentHandler, + AnyHandler, CliBindings, CliBindingsAny, DynHandler, HandleAny, HandleAnyArgs, HandleArgs, + Handler, HandlerTypes, IntoContext, Name, ParentHandler, }; type GenericRpcMethod<'a> = yajrc::GenericRpcMethod<&'a str, Value, Value>; @@ -81,120 +81,123 @@ impl } #[async_trait::async_trait] -pub trait CliContext: crate::Context { +pub trait CallRemote: crate::Context { async fn call_remote(&self, method: &str, params: Value) -> Result; } -#[async_trait::async_trait] -pub trait CliContextHttp: crate::Context { - fn client(&self) -> &Client; - fn url(&self) -> Url; - async fn call_remote(&self, method: &str, params: Value) -> Result { - let rpc_req = RpcRequest { - id: Some(Id::Number(0.into())), - method: GenericRpcMethod::new(method), - params, - }; - let mut req = self.client().request(Method::POST, self.url()); - let body; +pub async fn call_remote_http( + client: &Client, + url: Url, + method: &str, + params: Value, +) -> Result { + let rpc_req = RpcRequest { + id: Some(Id::Number(0.into())), + method: GenericRpcMethod::new(method), + params, + }; + let mut req = client.request(Method::POST, url); + let body; + #[cfg(feature = "cbor")] + { + req = req.header("content-type", "application/cbor"); + req = req.header("accept", "application/cbor, application/json"); + body = serde_cbor::to_vec(&rpc_req)?; + } + #[cfg(not(feature = "cbor"))] + { + req = req.header("content-type", "application/json"); + req = req.header("accept", "application/json"); + body = serde_json::to_vec(&req)?; + } + let res = req + .header("content-length", body.len()) + .body(body) + .send() + .await?; + + match res + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + { + Some("application/json") => { + serde_json::from_slice::(&*res.bytes().await.map_err(internal_error)?) + .map_err(parse_error)? + .result + } #[cfg(feature = "cbor")] - { - req = req.header("content-type", "application/cbor"); - req = req.header("accept", "application/cbor, application/json"); - body = serde_cbor::to_vec(&rpc_req)?; + Some("application/cbor") => { + serde_cbor::from_slice::(&*res.bytes().await.map_err(internal_error)?) + .map_err(parse_error)? + .result } - #[cfg(not(feature = "cbor"))] - { - req = req.header("content-type", "application/json"); - req = req.header("accept", "application/json"); - body = serde_json::to_vec(&req)?; - } - let res = req - .header("content-length", body.len()) - .body(body) - .send() - .await?; + _ => Err(internal_error("missing content type")), + } +} - match res - .headers() - .get("content-type") - .and_then(|v| v.to_str().ok()) - { - Some("application/json") => { - serde_json::from_slice::(&*res.bytes().await.map_err(internal_error)?) - .map_err(parse_error)? - .result - } - #[cfg(feature = "cbor")] - Some("application/cbor") => { - serde_cbor::from_slice::(&*res.bytes().await.map_err(internal_error)?) - .map_err(parse_error)? - .result - } - _ => Err(internal_error("missing content type")), +pub async fn call_remote_socket( + connection: impl AsyncRead + AsyncWrite, + method: &str, + params: Value, +) -> Result { + let rpc_req = RpcRequest { + id: Some(Id::Number(0.into())), + method: GenericRpcMethod::new(method), + params, + }; + let conn = connection; + tokio::pin!(conn); + let mut buf = serde_json::to_vec(&rpc_req).map_err(|e| RpcError { + data: Some(e.to_string().into()), + ..yajrc::INTERNAL_ERROR + })?; + buf.push(b'\n'); + conn.write_all(&buf).await.map_err(|e| RpcError { + data: Some(e.to_string().into()), + ..yajrc::INTERNAL_ERROR + })?; + let mut line = String::new(); + BufReader::new(conn).read_line(&mut line).await?; + serde_json::from_str::(&line) + .map_err(parse_error)? + .result +} + +pub struct CallRemoteHandler { + _phantom: PhantomData, + handler: RemoteHandler, +} +impl CallRemoteHandler { + pub fn new(handler: RemoteHandler) -> Self { + Self { + _phantom: PhantomData, + handler: handler, } } } -#[async_trait::async_trait] -impl CliContext for T -where - T: CliContextHttp, +impl Clone + for CallRemoteHandler { - async fn call_remote(&self, method: &str, params: Value) -> Result { - ::call_remote(&self, method, params).await - } -} - -#[async_trait::async_trait] -pub trait CliContextSocket: crate::Context { - type Stream: AsyncRead + AsyncWrite + Send; - async fn connect(&self) -> std::io::Result; - async fn call_remote(&self, method: &str, params: Value) -> Result { - let rpc_req = RpcRequest { - id: Some(Id::Number(0.into())), - method: GenericRpcMethod::new(method), - params, - }; - let conn = self.connect().await.map_err(|e| RpcError { - data: Some(e.to_string().into()), - ..yajrc::INTERNAL_ERROR - })?; - tokio::pin!(conn); - let mut buf = serde_json::to_vec(&rpc_req).map_err(|e| RpcError { - data: Some(e.to_string().into()), - ..yajrc::INTERNAL_ERROR - })?; - buf.push(b'\n'); - conn.write_all(&buf).await.map_err(|e| RpcError { - data: Some(e.to_string().into()), - ..yajrc::INTERNAL_ERROR - })?; - let mut line = String::new(); - BufReader::new(conn).read_line(&mut line).await?; - serde_json::from_str::(&line) - .map_err(parse_error)? - .result - } -} - -#[derive(Debug, Default)] -pub struct CallRemote(PhantomData<(RemoteContext, RemoteHandler)>); -impl CallRemote { - pub fn new() -> Self { - Self(PhantomData) - } -} -impl Clone for CallRemote { fn clone(&self) -> Self { - Self(PhantomData) + Self { + _phantom: PhantomData, + handler: self.handler.clone(), + } } } -#[async_trait::async_trait] -impl Handler - for CallRemote +impl std::fmt::Debug + for CallRemoteHandler +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("CallRemoteHandler").finish() + } +} + +impl HandlerTypes for CallRemoteHandler where RemoteContext: IntoContext, - RemoteHandler: Handler, + RemoteHandler: HandlerTypes, RemoteHandler::Params: Serialize, RemoteHandler::InheritedParams: Serialize, RemoteHandler::Ok: DeserializeOwned, @@ -204,6 +207,18 @@ where type InheritedParams = RemoteHandler::InheritedParams; type Ok = RemoteHandler::Ok; type Err = RemoteHandler::Err; +} +#[async_trait::async_trait] +impl Handler + for CallRemoteHandler +where + RemoteContext: IntoContext, + RemoteHandler: Handler, + RemoteHandler::Params: Serialize, + RemoteHandler::InheritedParams: Serialize, + RemoteHandler::Ok: DeserializeOwned, + RemoteHandler::Err: From, +{ async fn handle_async( &self, handle_args: HandleArgs, @@ -229,3 +244,49 @@ where } } } +// #[async_trait::async_trait] +impl CliBindings + for CallRemoteHandler +where + RemoteContext: IntoContext, + RemoteHandler: Handler + CliBindings, + RemoteHandler::Params: Serialize, + RemoteHandler::InheritedParams: Serialize, + RemoteHandler::Ok: DeserializeOwned, + RemoteHandler::Err: From, +{ + fn cli_command(&self, ctx_ty: TypeId) -> clap::Command { + self.handler.cli_command(ctx_ty) + } + fn cli_parse( + &self, + matches: &clap::ArgMatches, + ctx_ty: TypeId, + ) -> Result<(std::collections::VecDeque<&'static str>, Value), clap::Error> { + self.handler.cli_parse(matches, ctx_ty) + } + fn cli_display( + &self, + HandleArgs { + context, + parent_method, + method, + params, + inherited_params, + raw_params, + }: HandleArgs, + result: Self::Ok, + ) -> Result<(), Self::Err> { + self.handler.cli_display( + HandleArgs { + context, + parent_method, + method, + params, + inherited_params, + raw_params, + }, + result, + ) + } +} diff --git a/rpc-toolkit/src/command_helpers.rs b/rpc-toolkit/src/command_helpers.rs new file mode 100644 index 0000000..87f9cda --- /dev/null +++ b/rpc-toolkit/src/command_helpers.rs @@ -0,0 +1,33 @@ +use std::fmt::Display; +use std::io::Stdin; +use std::str::FromStr; + +use clap::ArgMatches; +pub use {clap, serde}; + +pub fn default_arg_parser(arg: &str, _: &ArgMatches) -> Result +where + T: FromStr, + T::Err: Display, +{ + arg.parse() + .map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e)) +} + +pub fn default_stdin_parser(stdin: &mut Stdin, _: &ArgMatches) -> Result +where + T: FromStr, + T::Err: Display, +{ + let mut s = String::new(); + stdin + .read_line(&mut s) + .map_err(|e| clap::Error::raw(clap::error::ErrorKind::Io, e))?; + if let Some(s) = s.strip_suffix("\n") { + s + } else { + &s + } + .parse() + .map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e)) +} diff --git a/rpc-toolkit/src/context.rs b/rpc-toolkit/src/context.rs index f2c0e43..0683fd2 100644 --- a/rpc-toolkit/src/context.rs +++ b/rpc-toolkit/src/context.rs @@ -6,6 +6,9 @@ use tokio::runtime::Handle; use crate::Handler; pub trait Context: Any + Send + Sync + 'static { + fn inner_type_id(&self) -> TypeId { + ::type_id(&self) + } fn runtime(&self) -> Handle { Handle::current() } @@ -36,7 +39,7 @@ impl IntoContext for C { AnyContext::new(self) } fn downcast(value: AnyContext) -> Result { - if value.0.type_id() == TypeId::of::() { + if value.0.inner_type_id() == TypeId::of::() { unsafe { Ok(value.downcast_unchecked::()) } } else { Err(value) @@ -90,10 +93,8 @@ impl AnyContext { Self(Box::new(value)) } unsafe fn downcast_unchecked(self) -> C { - unsafe { - let raw: *mut dyn Context = Box::into_raw(self.0); - *Box::from_raw(raw as *mut C) - } + let raw: *mut dyn Context = Box::into_raw(self.0); + *Box::from_raw(raw as *mut C) } } @@ -105,7 +106,7 @@ impl IntoContext for AnyContext { None } fn inner_type_id(&self) -> TypeId { - self.0.type_id() + self.0.inner_type_id() } fn downcast(value: AnyContext) -> Result { Ok(value) diff --git a/rpc-toolkit/src/handler.rs b/rpc-toolkit/src/handler.rs index e93846a..6086342 100644 --- a/rpc-toolkit/src/handler.rs +++ b/rpc-toolkit/src/handler.rs @@ -14,6 +14,7 @@ use yajrc::RpcError; use crate::context::{AnyContext, IntoContext}; use crate::util::{combine, internal_error, invalid_params, Flat}; +use crate::{CallRemote, CallRemoteHandler}; pub(crate) struct HandleAnyArgs { pub(crate) context: AnyContext, @@ -24,7 +25,7 @@ pub(crate) struct HandleAnyArgs { impl HandleAnyArgs { fn downcast(self) -> Result, imbl_value::Error> where - H: Handler, + H: HandlerTypes, H::Params: DeserializeOwned, H::InheritedParams: DeserializeOwned, { @@ -49,7 +50,7 @@ impl HandleAnyArgs { } #[async_trait::async_trait] -pub(crate) trait HandleAny: Send + Sync { +pub(crate) trait HandleAny: std::fmt::Debug + Send + Sync { fn handle_sync(&self, handle_args: HandleAnyArgs) -> Result; async fn handle_async(&self, handle_args: HandleAnyArgs) -> Result; fn method_from_dots(&self, method: &str, ctx_ty: TypeId) -> Option>; @@ -77,7 +78,7 @@ pub(crate) trait CliBindingsAny { fn cli_display(&self, handle_args: HandleAnyArgs, result: Value) -> Result<(), RpcError>; } -pub trait CliBindings: Handler { +pub trait CliBindings: HandlerTypes { fn cli_command(&self, ctx_ty: TypeId) -> Command; fn cli_parse( &self, @@ -91,7 +92,7 @@ pub trait CliBindings: Handler { ) -> Result<(), Self::Err>; } -pub trait PrintCliResult: Handler { +pub trait PrintCliResult: HandlerTypes { fn print( &self, handle_args: HandleArgs, @@ -102,7 +103,7 @@ pub trait PrintCliResult: Handler { pub(crate) trait HandleAnyWithCli: HandleAny + CliBindingsAny {} impl HandleAnyWithCli for T {} -#[derive(Clone)] +#[derive(Debug, Clone)] pub(crate) enum DynHandler { WithoutCli(Arc), WithCli(Arc), @@ -130,7 +131,7 @@ impl HandleAny for DynHandler { } #[derive(Debug, Clone)] -pub struct HandleArgs + ?Sized> { +pub struct HandleArgs { pub context: Context, pub parent_method: Vec<&'static str>, pub method: VecDeque<&'static str>, @@ -139,12 +140,17 @@ pub struct HandleArgs + ?Sized> { pub raw_params: Value, } -#[async_trait::async_trait] -pub trait Handler: Clone + Send + Sync + 'static { +pub trait HandlerTypes { type Params: Send + Sync; type InheritedParams: Send + Sync; type Ok: Send + Sync; type Err: Send + Sync; +} + +#[async_trait::async_trait] +pub trait Handler: + HandlerTypes + std::fmt::Debug + Clone + Send + Sync + 'static +{ fn handle_sync(&self, handle_args: HandleArgs) -> Result { handle_args .context @@ -198,6 +204,11 @@ impl AnyHandler { } } } +impl std::fmt::Debug for AnyHandler { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("AnyHandler").field(&self.handler).finish() + } +} #[async_trait::async_trait] impl> HandleAny for AnyHandler @@ -271,7 +282,7 @@ impl<'a> std::borrow::Borrow> for Name { } } -#[derive(Clone)] +#[derive(Debug, Clone)] pub(crate) struct SubcommandMap(pub(crate) BTreeMap, DynHandler>>); impl SubcommandMap { fn insert( @@ -324,6 +335,13 @@ impl Clone for ParentHandler { } } } +impl std::fmt::Debug for ParentHandler { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ParentHandler") + .field(&self.subcommands) + .finish() + } +} struct InheritanceHandler { _phantom: PhantomData<(Context, Params, InheritedParams)>, @@ -341,6 +359,28 @@ impl Clone } } } +impl std::fmt::Debug + for InheritanceHandler +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("InheritanceHandler") + .field(&self.handler) + .finish() + } +} +impl HandlerTypes + for InheritanceHandler +where + Context: IntoContext, + H: HandlerTypes, + Params: Send + Sync, + InheritedParams: Send + Sync, +{ + type Params = H::Params; + type InheritedParams = Flat; + type Ok = H::Ok; + type Err = H::Err; +} #[async_trait::async_trait] impl Handler for InheritanceHandler @@ -351,10 +391,6 @@ where H: Handler, F: Fn(Params, InheritedParams) -> H::InheritedParams + Send + Sync + Clone + 'static, { - type Params = H::Params; - type InheritedParams = Flat; - type Ok = H::Ok; - type Err = H::Err; fn handle_sync( &self, HandleArgs { @@ -446,7 +482,10 @@ impl ParentHandler(mut self, name: &'static str, handler: H) -> Self where Context: IntoContext, - H: CliBindings + 'static, + H: HandlerTypes + + Handler + + CliBindings + + 'static, H::Params: DeserializeOwned, H::Ok: Serialize + DeserializeOwned, RpcError: From, @@ -454,10 +493,42 @@ impl ParentHandler( + mut self, + name: &'static str, + handler: H, + ) -> Self + where + ServerContext: IntoContext, + CliContext: IntoContext + CallRemote, + H: HandlerTypes + + Handler + + CliBindings + + 'static, + H::Params: Serialize + DeserializeOwned, + H::Ok: Serialize + DeserializeOwned, + RpcError: From, + H::Err: From, + CallRemoteHandler: Handler, + as HandlerTypes>::Ok: Serialize + DeserializeOwned, + as HandlerTypes>::Params: Serialize + DeserializeOwned, + as HandlerTypes>::InheritedParams: DeserializeOwned, + RpcError: From< as HandlerTypes>::Err>, + { + self.subcommands.insert( + handler.contexts(), + name.into(), + DynHandler::WithoutCli(Arc::new(AnyHandler::new(handler.clone()))), + ); + let call_remote = CallRemoteHandler::::new(handler); + self.subcommands.insert( + call_remote.contexts(), + name.into(), + DynHandler::WithCli(Arc::new(AnyHandler::new(call_remote))), ); self } @@ -472,10 +543,7 @@ impl ParentHandler Self where Context: IntoContext, - H: CliBindings + 'static, + H: Handler + CliBindings + 'static, H::Params: DeserializeOwned, H::Ok: Serialize + DeserializeOwned, RpcError: From, @@ -513,6 +581,62 @@ where ); self } + pub fn subcommand_with_inherited_remote_cli( + mut self, + name: &'static str, + handler: H, + inherit: F, + ) -> Self + where + ServerContext: IntoContext, + CliContext: IntoContext + CallRemote, + H: HandlerTypes + Handler + CliBindings + 'static, + H::Params: Serialize + DeserializeOwned, + H::Ok: Serialize + DeserializeOwned, + RpcError: From, + H::Err: From, + CallRemoteHandler: + Handler, + as HandlerTypes>::Ok: Serialize + DeserializeOwned, + as HandlerTypes>::Params: Serialize + DeserializeOwned, + as HandlerTypes>::InheritedParams: + Serialize + DeserializeOwned, + RpcError: From< as HandlerTypes>::Err>, + F: Fn(Params, InheritedParams) -> H::InheritedParams + Send + Sync + Clone + 'static, + { + self.subcommands.insert( + handler.contexts(), + name.into(), + DynHandler::WithoutCli(Arc::new(AnyHandler::new(InheritanceHandler::< + ServerContext, + Params, + InheritedParams, + H, + F, + > { + _phantom: PhantomData, + handler: handler.clone(), + inherit: inherit.clone(), + }))), + ); + let call_remote = CallRemoteHandler::::new(handler); + self.subcommands.insert( + call_remote.contexts(), + name.into(), + DynHandler::WithCli(Arc::new(AnyHandler::new(InheritanceHandler::< + CliContext, + Params, + InheritedParams, + CallRemoteHandler, + F, + > { + _phantom: PhantomData, + handler: call_remote, + inherit, + }))), + ); + self + } pub fn subcommand_with_inherited_no_cli( mut self, name: &'static str, @@ -544,7 +668,7 @@ where pub fn root_handler(mut self, handler: H, inherit: F) -> Self where Context: IntoContext, - H: CliBindings + 'static, + H: HandlerTypes + Handler + CliBindings + 'static, H::Params: DeserializeOwned, H::Ok: Serialize + DeserializeOwned, RpcError: From, @@ -589,17 +713,23 @@ where } } -#[async_trait::async_trait] -impl< - Context: IntoContext, - Params: Serialize + Send + Sync + 'static, - InheritedParams: Serialize + Send + Sync + 'static, - > Handler for ParentHandler +impl HandlerTypes for ParentHandler +where + Params: Send + Sync, + InheritedParams: Send + Sync, { type Params = Params; type InheritedParams = InheritedParams; type Ok = Value; type Err = RpcError; +} +#[async_trait::async_trait] +impl Handler for ParentHandler +where + Context: IntoContext, + Params: Serialize + Send + Sync + 'static, + InheritedParams: Serialize + Send + Sync + 'static, +{ fn handle_sync( &self, HandleArgs { @@ -614,7 +744,7 @@ impl< if let Some(cmd) = cmd { parent_method.push(cmd); } - if let Some((_, sub_handler)) = self.subcommands.get(context.inner_type_id(), cmd) { + if let Some((_, sub_handler)) = &self.subcommands.get(context.inner_type_id(), cmd) { sub_handler.handle_sync(HandleAnyArgs { context: context.upcast(), parent_method, @@ -776,11 +906,18 @@ impl Clone for FromFn { } } } +impl std::fmt::Debug for FromFn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FromFn") + .field("blocking", &self.blocking) + .finish() + } +} impl PrintCliResult for FromFn where Context: IntoContext, - Self: Handler, - >::Ok: Display, + Self: HandlerTypes, + ::Ok: Display, { fn print(&self, _: HandleArgs, result: Self::Ok) -> Result<(), Self::Err> { Ok(println!("{result}")) @@ -815,11 +952,16 @@ impl Clone for FromFnAsync { } } } +impl std::fmt::Debug for FromFnAsync { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FromFnAsync").finish() + } +} impl PrintCliResult for FromFnAsync where Context: IntoContext, - Self: Handler, - >::Ok: Display, + Self: HandlerTypes, + ::Ok: Display, { fn print(&self, _: HandleArgs, result: Self::Ok) -> Result<(), Self::Err> { Ok(println!("{result}")) @@ -833,10 +975,8 @@ pub fn from_fn_async(function: F) -> FromFnAsync Handler for FromFn +impl HandlerTypes for FromFn where - Context: IntoContext, F: Fn() -> Result + Send + Sync + Clone + 'static, T: Send + Sync + 'static, E: Send + Sync + 'static, @@ -845,6 +985,15 @@ where type InheritedParams = NoParams; type Ok = T; type Err = E; +} +#[async_trait::async_trait] +impl Handler for FromFn +where + Context: IntoContext, + F: Fn() -> Result + Send + Sync + Clone + 'static, + T: Send + Sync + 'static, + E: Send + Sync + 'static, +{ fn handle_sync(&self, _: HandleArgs) -> Result { (self.function)() } @@ -859,10 +1008,8 @@ where } } } -#[async_trait::async_trait] -impl Handler for FromFnAsync +impl HandlerTypes for FromFnAsync where - Context: IntoContext, F: Fn() -> Fut + Send + Sync + Clone + 'static, Fut: Future> + Send + Sync + 'static, T: Send + Sync + 'static, @@ -872,13 +1019,22 @@ where type InheritedParams = NoParams; type Ok = T; type Err = E; +} +#[async_trait::async_trait] +impl Handler for FromFnAsync +where + Context: IntoContext, + F: Fn() -> Fut + Send + Sync + Clone + 'static, + Fut: Future> + Send + Sync + 'static, + T: Send + Sync + 'static, + E: Send + Sync + 'static, +{ async fn handle_async(&self, _: HandleArgs) -> Result { (self.function)().await } } -#[async_trait::async_trait] -impl Handler for FromFn +impl HandlerTypes for FromFn where Context: IntoContext, F: Fn(Context) -> Result + Send + Sync + Clone + 'static, @@ -889,6 +1045,15 @@ where type InheritedParams = NoParams; type Ok = T; type Err = E; +} +#[async_trait::async_trait] +impl Handler for FromFn +where + Context: IntoContext, + F: Fn(Context) -> Result + Send + Sync + Clone + 'static, + T: Send + Sync + 'static, + E: Send + Sync + 'static, +{ fn handle_sync(&self, handle_args: HandleArgs) -> Result { (self.function)(handle_args.context) } @@ -903,8 +1068,7 @@ where } } } -#[async_trait::async_trait] -impl Handler for FromFnAsync +impl HandlerTypes for FromFnAsync where Context: IntoContext, F: Fn(Context) -> Fut + Send + Sync + Clone + 'static, @@ -916,6 +1080,16 @@ where type InheritedParams = NoParams; type Ok = T; type Err = E; +} +#[async_trait::async_trait] +impl Handler for FromFnAsync +where + Context: IntoContext, + F: Fn(Context) -> Fut + Send + Sync + Clone + 'static, + Fut: Future> + Send + Sync + 'static, + T: Send + Sync + 'static, + E: Send + Sync + 'static, +{ async fn handle_async( &self, handle_args: HandleArgs, @@ -924,8 +1098,7 @@ where } } -#[async_trait::async_trait] -impl Handler for FromFn +impl HandlerTypes for FromFn where Context: IntoContext, F: Fn(Context, Params) -> Result + Send + Sync + Clone + 'static, @@ -937,6 +1110,16 @@ where type InheritedParams = NoParams; type Ok = T; type Err = E; +} +#[async_trait::async_trait] +impl Handler for FromFn +where + Context: IntoContext, + F: Fn(Context, Params) -> Result + Send + Sync + Clone + 'static, + Params: DeserializeOwned + Send + Sync + 'static, + T: Send + Sync + 'static, + E: Send + Sync + 'static, +{ fn handle_sync(&self, handle_args: HandleArgs) -> Result { let HandleArgs { context, params, .. @@ -954,9 +1137,8 @@ where } } } -#[async_trait::async_trait] -impl Handler - for FromFnAsync + +impl HandlerTypes for FromFnAsync where Context: IntoContext, F: Fn(Context, Params) -> Fut + Send + Sync + Clone + 'static, @@ -969,6 +1151,18 @@ where type InheritedParams = NoParams; type Ok = T; type Err = E; +} +#[async_trait::async_trait] +impl Handler + for FromFnAsync +where + Context: IntoContext, + F: Fn(Context, Params) -> Fut + Send + Sync + Clone + 'static, + Fut: Future> + Send + Sync + 'static, + Params: DeserializeOwned + Send + Sync + 'static, + T: Send + Sync + 'static, + E: Send + Sync + 'static, +{ async fn handle_async( &self, handle_args: HandleArgs, @@ -980,8 +1174,7 @@ where } } -#[async_trait::async_trait] -impl Handler +impl HandlerTypes for FromFn where Context: IntoContext, @@ -995,6 +1188,18 @@ where type InheritedParams = InheritedParams; type Ok = T; type Err = E; +} +#[async_trait::async_trait] +impl Handler + for FromFn +where + Context: IntoContext, + F: Fn(Context, Params, InheritedParams) -> Result + Send + Sync + Clone + 'static, + Params: DeserializeOwned + Send + Sync + 'static, + InheritedParams: DeserializeOwned + Send + Sync + 'static, + T: Send + Sync + 'static, + E: Send + Sync + 'static, +{ fn handle_sync(&self, handle_args: HandleArgs) -> Result { let HandleArgs { context, @@ -1015,8 +1220,8 @@ where } } } -#[async_trait::async_trait] -impl Handler + +impl HandlerTypes for FromFnAsync where Context: IntoContext, @@ -1031,6 +1236,19 @@ where type InheritedParams = InheritedParams; type Ok = T; type Err = E; +} +#[async_trait::async_trait] +impl Handler + for FromFnAsync +where + Context: IntoContext, + F: Fn(Context, Params, InheritedParams) -> Fut + Send + Sync + Clone + 'static, + Fut: Future> + Send + Sync + 'static, + Params: DeserializeOwned + Send + Sync + 'static, + InheritedParams: DeserializeOwned + Send + Sync + 'static, + T: Send + Sync + 'static, + E: Send + Sync + 'static, +{ async fn handle_async( &self, handle_args: HandleArgs, @@ -1048,7 +1266,7 @@ where impl CliBindings for FromFn where Context: IntoContext, - Self: Handler, + Self: HandlerTypes, Self::Params: FromArgMatches + CommandFactory + Serialize, Self: PrintCliResult, { @@ -1097,7 +1315,7 @@ where impl CliBindings for FromFnAsync where Context: IntoContext, - Self: Handler, + Self: HandlerTypes, Self::Params: FromArgMatches + CommandFactory + Serialize, Self: PrintCliResult, { diff --git a/rpc-toolkit/src/lib.rs b/rpc-toolkit/src/lib.rs index bcbf617..d1a60c2 100644 --- a/rpc-toolkit/src/lib.rs +++ b/rpc-toolkit/src/lib.rs @@ -28,6 +28,7 @@ pub use server::*; pub use {clap, futures, hyper, reqwest, serde, serde_json, tokio, url, yajrc}; mod cli; +pub mod command_helpers; mod context; mod handler; mod server; diff --git a/rpc-toolkit/src/server/socket.rs b/rpc-toolkit/src/server/socket.rs index e42b42c..d51201b 100644 --- a/rpc-toolkit/src/server/socket.rs +++ b/rpc-toolkit/src/server/socket.rs @@ -5,17 +5,17 @@ use futures::{Future, Stream, StreamExt, TryStreamExt}; use imbl_value::Value; use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader}; use tokio::net::{TcpListener, ToSocketAddrs, UnixListener}; -use tokio::sync::OnceCell; +use tokio::sync::Notify; use yajrc::RpcError; -use crate::util::{parse_error, JobRunner}; +use crate::util::{parse_error, JobRunner, StreamUntil}; use crate::Server; #[derive(Clone)] -pub struct ShutdownHandle(Arc>); +pub struct ShutdownHandle(Arc); impl ShutdownHandle { pub fn shutdown(self) { - let _ = self.0.set(()); + self.0.notify_one(); } } @@ -25,10 +25,10 @@ impl Server { listener: impl Stream> + 'a, error_handler: impl Fn(std::io::Error) + Sync + 'a, ) -> (ShutdownHandle, impl Future + 'a) { - let shutdown = Arc::new(OnceCell::new()); + let shutdown = Arc::new(Notify::new()); (ShutdownHandle(shutdown.clone()), async move { let mut runner = JobRunner::>::new(); - let jobs = listener.map(|pipe| async { + let jobs = StreamUntil::new(listener, shutdown.notified()).map(|pipe| async { let pipe = pipe?; let (r, mut w) = tokio::io::split(pipe); let stream = self.stream( diff --git a/rpc-toolkit/src/util.rs b/rpc-toolkit/src/util.rs index 133b8cf..7667930 100644 --- a/rpc-toolkit/src/util.rs +++ b/rpc-toolkit/src/util.rs @@ -1,6 +1,7 @@ use std::fmt::Display; -use futures::future::BoxFuture; +use futures::future::{BoxFuture, FusedFuture}; +use futures::stream::FusedStream; use futures::{Future, FutureExt, Stream, StreamExt}; use imbl_value::Value; use serde::de::DeserializeOwned; @@ -148,6 +149,9 @@ impl<'a, T> JobRunner<'a, T> { self.running.push(job.boxed()); } else { self.closed = true; + if self.running.is_empty() { + return None; + } } } res = self.next() => { @@ -172,6 +176,52 @@ impl<'a, T> Stream for JobRunner<'a, T> { } } +#[pin_project::pin_project] +pub struct StreamUntil { + #[pin] + stream: S, + #[pin] + until: F, + done: bool, +} +impl StreamUntil { + pub fn new(stream: S, until: F) -> Self { + Self { + stream, + until, + done: false, + } + } +} +impl Stream for StreamUntil +where + S: Stream, + F: Future, +{ + type Item = S::Item; + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let this = self.project(); + *this.done = *this.done || this.until.poll(cx).is_ready(); + if *this.done { + std::task::Poll::Ready(None) + } else { + this.stream.poll_next(cx) + } + } +} +impl FusedStream for StreamUntil +where + S: FusedStream, + F: FusedFuture, +{ + fn is_terminated(&self) -> bool { + self.done || self.stream.is_terminated() || self.until.is_terminated() + } +} + // #[derive(Debug)] // pub enum Infallible {} // impl From for T { diff --git a/rpc-toolkit/tests/compat.rs b/rpc-toolkit/tests/compat.rs index a96d00c..cd97121 100644 --- a/rpc-toolkit/tests/compat.rs +++ b/rpc-toolkit/tests/compat.rs @@ -1,119 +1,83 @@ -// use std::fmt::Display; -// use std::str::FromStr; -// use std::sync::Arc; +use std::fmt::Display; +use std::str::FromStr; +use std::sync::Arc; -// use futures::FutureExt; -// use hyper::Request; -// use rpc_toolkit::clap::Arg; -// use rpc_toolkit::hyper::http::Error as HttpError; -// use rpc_toolkit::hyper::{Body, Response}; -// use rpc_toolkit::rpc_server_helpers::{ -// DynMiddlewareStage2, DynMiddlewareStage3, DynMiddlewareStage4, -// }; -// use rpc_toolkit::serde::{Deserialize, Serialize}; -// use rpc_toolkit::url::Host; -// use rpc_toolkit::yajrc::RpcError; -// use rpc_toolkit::{command, rpc_server, run_cli, Context, Metadata}; +use futures::FutureExt; +use hyper::Request; +use rpc_toolkit::clap::Arg; +use rpc_toolkit::hyper::http::Error as HttpError; +use rpc_toolkit::hyper::Response; +use rpc_toolkit::serde::{Deserialize, Serialize}; +use rpc_toolkit::url::Host; +use rpc_toolkit::yajrc::RpcError; +use rpc_toolkit::{command, Context}; -// #[derive(Debug, Clone)] -// pub struct AppState(Arc); -// impl From for () { -// fn from(_: AppState) -> Self { -// () -// } -// } +#[derive(Debug, Clone)] +pub struct AppState(Arc); +impl From for () { + fn from(_: AppState) -> Self { + () + } +} -// #[derive(Debug)] -// pub struct ConfigSeed { -// host: Host, -// port: u16, -// } +#[derive(Debug)] +pub struct ConfigSeed { + host: Host, + port: u16, +} -// impl Context for AppState { -// type Metadata = (); -// } +impl Context for AppState {} -// fn test_string() -> String { -// "test".to_owned() -// } +#[command( + about = "Does the thing", + subcommands("dothething2::", self(dothething_impl(async))) +)] +async fn dothething( + #[context] _ctx: AppState, + #[arg(short = 'a')] arg1: Option, + #[arg(short = 'b', default)] val: String, + #[arg(short = 'c', help = "I am the flag `c`!", default)] arg3: bool, + #[arg(stdin)] structured: U, +) -> Result<(Option, String, bool, U), RpcError> +where + U: Serialize + for<'a> Deserialize<'a> + FromStr + Clone + 'static, + U::Err: Display, +{ + Ok((arg1, val, arg3, structured)) +} -// #[command( -// about = "Does the thing", -// subcommands("dothething2::", self(dothething_impl(async))) -// )] -// async fn dothething< -// U: Serialize + for<'a> Deserialize<'a> + FromStr + Clone + 'static, -// E: Display, -// >( -// #[context] _ctx: AppState, -// #[arg(short = 'a')] arg1: Option, -// #[arg(short = 'b', default = "test_string")] val: String, -// #[arg(short = 'c', help = "I am the flag `c`!", default)] arg3: bool, -// #[arg(stdin)] structured: U, -// ) -> Result<(Option, String, bool, U), RpcError> { -// Ok((arg1, val, arg3, structured)) -// } +async fn dothething_impl( + ctx: AppState, + parent_data: (Option, String, bool, U), +) -> Result { + Ok(format!( + "{:?}, {:?}, {}, {}, {}", + ctx, + parent_data.0, + parent_data.1, + parent_data.2, + serde_json::to_string_pretty(&parent_data.3)? + )) +} -// async fn dothething_impl( -// ctx: AppState, -// parent_data: (Option, String, bool, U), -// ) -> Result { -// Ok(format!( -// "{:?}, {:?}, {}, {}, {}", -// ctx, -// parent_data.0, -// parent_data.1, -// parent_data.2, -// serde_json::to_string_pretty(&parent_data.3)? -// )) -// } - -// #[command(about = "Does the thing")] -// fn dothething2 Deserialize<'a> + FromStr, E: Display>( -// #[parent_data] parent_data: (Option, String, bool, U), -// #[arg(stdin)] structured2: U, -// ) -> Result { -// Ok(format!( -// "{:?}, {}, {}, {}, {}", -// parent_data.0, -// parent_data.1, -// parent_data.2, -// serde_json::to_string_pretty(&parent_data.3)?, -// serde_json::to_string_pretty(&structured2)?, -// )) -// } - -// async fn cors( -// req: &mut Request, -// _: M, -// ) -> Result>, HttpError> { -// if req.method() == hyper::Method::OPTIONS { -// Ok(Err(Response::builder() -// .header("Access-Control-Allow-Origin", "*") -// .body(Body::empty())?)) -// } else { -// Ok(Ok(Box::new(|_, _| { -// async move { -// let res: DynMiddlewareStage3 = Box::new(|_, _| { -// async move { -// let res: DynMiddlewareStage4 = Box::new(|res| { -// async move { -// res.headers_mut() -// .insert("Access-Control-Allow-Origin", "*".parse()?); -// Ok::<_, HttpError>(()) -// } -// .boxed() -// }); -// Ok::<_, HttpError>(Ok(res)) -// } -// .boxed() -// }); -// Ok::<_, HttpError>(Ok(res)) -// } -// .boxed() -// }))) -// } -// } +#[command(about = "Does the thing")] +fn dothething2( + #[parent_data] parent_data: (Option, String, bool, U), + #[arg(stdin)] structured2: U, +) -> Result +where + U: Serialize + for<'a> Deserialize<'a> + FromStr + Clone + 'static, + U::Err: Display, +{ + Ok(format!( + "{:?}, {}, {}, {}, {}", + parent_data.0, + parent_data.1, + parent_data.2, + serde_json::to_string_pretty(&parent_data.3)?, + serde_json::to_string_pretty(&structured2)?, + )) +} // #[tokio::test] // async fn test_rpc() { diff --git a/rpc-toolkit/tests/handler.rs b/rpc-toolkit/tests/handler.rs index 299100a..d478f17 100644 --- a/rpc-toolkit/tests/handler.rs +++ b/rpc-toolkit/tests/handler.rs @@ -1,13 +1,19 @@ +use std::any::TypeId; +use std::ffi::OsString; use std::fmt::Display; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::Arc; use clap::Parser; -use rpc_toolkit::{from_fn, from_fn_async, AnyContext, CliApp, Context, NoParams, ParentHandler}; +use futures::future::ready; +use imbl_value::Value; +use rpc_toolkit::{ + call_remote_socket, from_fn, from_fn_async, AnyContext, CallRemote, CliApp, Context, NoParams, + ParentHandler, Server, +}; use serde::{Deserialize, Serialize}; use tokio::runtime::{Handle, Runtime}; -use tokio::sync::OnceCell; -use url::Url; +use tokio::sync::{Mutex, OnceCell}; use yajrc::RpcError; #[derive(Parser, Deserialize)] @@ -18,7 +24,9 @@ use yajrc::RpcError; about = "This is a test cli application." )] struct CliConfig { - host: Option, + #[arg(long = "host")] + host: Option, + #[arg(short = 'c', long = "config")] config: Option, } impl CliConfig { @@ -40,7 +48,7 @@ impl CliConfig { } struct CliContextSeed { - host: Url, + host: PathBuf, rt: OnceCell, } #[derive(Clone)] @@ -53,6 +61,17 @@ impl Context for CliContext { self.0.rt.get().unwrap().handle().clone() } } +#[async_trait::async_trait] +impl CallRemote for CliContext { + async fn call_remote(&self, method: &str, params: Value) -> Result { + call_remote_socket( + tokio::net::UnixStream::connect(&self.0.host).await.unwrap(), + method, + params, + ) + .await + } +} fn make_cli() -> CliApp { CliApp::new( @@ -61,8 +80,7 @@ fn make_cli() -> CliApp { Ok(CliContext(Arc::new(CliContextSeed { host: config .host - .map(|h| h.parse().unwrap()) - .unwrap_or_else(|| "http://localhost:8080/rpc".parse().unwrap()), + .unwrap_or_else(|| Path::new("./rpc.sock").to_owned()), rt: OnceCell::new(), }))) }, @@ -70,16 +88,30 @@ fn make_cli() -> CliApp { ) } +struct ServerContextSeed { + state: Mutex, +} + +#[derive(Clone)] +struct ServerContext(Arc); +impl Context for ServerContext {} + +fn make_server() -> Server { + let ctx = ServerContext(Arc::new(ServerContextSeed { + state: Mutex::new(Value::Null), + })); + Server::new(move || ready(Ok(ctx.clone())), make_api()) +} + fn make_api() -> ParentHandler { - impl CliContext { - fn host(&self) -> &Url { - &self.0.host - } - } async fn a_hello(_: CliContext) -> Result { Ok::<_, RpcError>("Async Subcommand".to_string()) } #[derive(Debug, Clone, Deserialize, Serialize, Parser)] + struct EchoParams { + next: String, + } + #[derive(Debug, Clone, Deserialize, Serialize, Parser)] struct HelloParams { whom: String, } @@ -88,29 +120,20 @@ fn make_api() -> ParentHandler { donde: String, } ParentHandler::new() - .subcommand( + .subcommand_remote_cli::( "echo", - ParentHandler::::new() - .subcommand_no_cli( - "echo_no_cli", - from_fn(|c: CliContext| { - Ok::<_, RpcError>( - format!("Subcommand No Cli: Host {host}", host = c.host()).to_string(), - ) - }), - ) - .subcommand_no_cli( - "echo_cli", - from_fn(|c: CliContext| { - Ok::<_, RpcError>( - format!("Subcommand Cli: Host {host}", host = c.host()).to_string(), - ) - }), - ), + from_fn_async( + |c: ServerContext, EchoParams { next }: EchoParams| async move { + Ok::<_, RpcError>(std::mem::replace( + &mut *c.0.state.lock().await, + Value::String(Arc::new(next)), + )) + }, + ), ) .subcommand( "hello", - from_fn(|_: CliContext, HelloParams { whom }: HelloParams| { + from_fn(|_: AnyContext, HelloParams { whom }: HelloParams| { Ok::<_, RpcError>(format!("Hello {whom}").to_string()) }), ) @@ -123,7 +146,7 @@ fn make_api() -> ParentHandler { Ok::<_, RpcError>( format!( "Subcommand No Cli: Host {host} Donde = {donde}", - host = c.host() + host = c.0.host.display() ) .to_string(), ) @@ -137,8 +160,8 @@ fn make_api() -> ParentHandler { from_fn(|c: CliContext, _, InheritParams { donde }| { Ok::<_, RpcError>( format!( - "Subcommand No Cli: Host {host} Donde = {donde}", - host = c.host(), + "Root Command: Host {host} Donde = {donde}", + host = c.0.host.display(), ) .to_string(), ) @@ -167,3 +190,63 @@ pub fn internal_error(e: impl Display) -> RpcError { ..yajrc::INTERNAL_ERROR } } + +#[test] +fn test_cli() { + make_cli() + .run( + ["test-cli", "hello", "me"] + .into_iter() + .map(|s| OsString::from(s)), + ) + .unwrap(); + make_cli() + .run( + ["test-cli", "fizz", "buzz"] + .into_iter() + .map(|s| OsString::from(s)), + ) + .unwrap(); +} + +#[tokio::test] +async fn test_server() { + let path = Path::new(env!("CARGO_TARGET_TMPDIR")).join("rpc.sock"); + tokio::fs::remove_file(&path).await.unwrap_or_default(); + let server = make_server(); + let (shutdown, fut) = server + .run_unix(path.clone(), |err| eprintln!("IO Error: {err}")) + .unwrap(); + tokio::join!( + tokio::task::spawn_blocking(move || { + make_cli() + .run( + [ + "test-cli", + &format!("--host={}", path.display()), + "echo", + "foo", + ] + .into_iter() + .map(|s| OsString::from(s)), + ) + .unwrap(); + make_cli() + .run( + [ + "test-cli", + &format!("--host={}", path.display()), + "echo", + "bar", + ] + .into_iter() + .map(|s| OsString::from(s)), + ) + .unwrap(); + shutdown.shutdown() + }), + fut + ) + .0 + .unwrap(); +}