From 68c8881c0b016adbead732075d401fce71a9b81f Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Wed, 13 Dec 2023 23:33:51 -0700 Subject: [PATCH] wip --- Cargo.lock | 7 + rpc-toolkit/Cargo.toml | 1 + rpc-toolkit/src/cli.rs | 112 ++-- rpc-toolkit/src/command_helpers.rs | 130 ----- rpc-toolkit/src/handler.rs | 769 ++++++++++++++++++++++++++ rpc-toolkit/src/lib.rs | 4 +- rpc-toolkit/src/rpc_server_helpers.rs | 186 ------- rpc-toolkit/src/util.rs | 56 +- 8 files changed, 885 insertions(+), 380 deletions(-) delete mode 100644 rpc-toolkit/src/command_helpers.rs create mode 100644 rpc-toolkit/src/handler.rs delete mode 100644 rpc-toolkit/src/rpc_server_helpers.rs diff --git a/Cargo.lock b/Cargo.lock index cafdfe2..a2d76e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,6 +690,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_format" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e479e99b287d578ed5f6cd4c92cdf48db219088adb9c5b14f7c155b71dfba792" + [[package]] name = "lazy_static" version = "1.4.0" @@ -992,6 +998,7 @@ dependencies = [ "http-body-util", "hyper 1.0.1", "imbl-value", + "lazy_format", "lazy_static", "openssl", "reqwest", diff --git a/rpc-toolkit/Cargo.toml b/rpc-toolkit/Cargo.toml index cc97b2a..6b225b2 100644 --- a/rpc-toolkit/Cargo.toml +++ b/rpc-toolkit/Cargo.toml @@ -24,6 +24,7 @@ http = "1" http-body-util = "0.1" hyper = { version = "1", features = ["server", "http1", "http2", "client"] } imbl-value = "0.1" +lazy_format = "2" lazy_static = "1.4" openssl = { version = "0.10", features = ["vendored"] } reqwest = { version = "0.11" } diff --git a/rpc-toolkit/src/cli.rs b/rpc-toolkit/src/cli.rs index a696105..4502d5b 100644 --- a/rpc-toolkit/src/cli.rs +++ b/rpc-toolkit/src/cli.rs @@ -11,58 +11,56 @@ use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader use url::Url; use yajrc::{Id, RpcError}; -use crate::command::{AsyncCommand, DynCommand, LeafCommand, ParentInfo}; +use crate::command::ParentCommand; +// use crate::command::{AsyncCommand, DynCommand, LeafCommand, ParentInfo}; use crate::util::{combine, internal_error, invalid_params, parse_error}; -use crate::{CliBindings, SyncCommand}; +// use crate::{CliBindings, SyncCommand}; type GenericRpcMethod<'a> = yajrc::GenericRpcMethod<&'a str, Value, Value>; type RpcRequest<'a> = yajrc::RpcRequest>; type RpcResponse<'a> = yajrc::RpcResponse>; -impl DynCommand { - fn cli_app(&self) -> Option { - if let Some(cli) = &self.cli { - Some( - cli.cmd - .clone() - .name(self.name) - .subcommands(self.subcommands.iter().filter_map(|c| c.cli_app())), - ) - } else { - None - } - } - fn cmd_from_cli_matches( - &self, - matches: &ArgMatches, - parent: ParentInfo, - ) -> Result<(Vec<&'static str>, Value, &DynCommand), RpcError> { - let params = combine( - parent.params, - (self - .cli - .as_ref() - .ok_or(yajrc::METHOD_NOT_FOUND_ERROR)? - .parser)(matches)?, - )?; - if let Some((cmd, matches)) = matches.subcommand() { - let mut method = parent.method; - method.push(self.name); - self.subcommands - .iter() - .find(|c| c.name == cmd) - .ok_or(yajrc::METHOD_NOT_FOUND_ERROR)? - .cmd_from_cli_matches(matches, ParentInfo { method, params }) - } else { - Ok((parent.method, params, self)) - } - } -} +// impl DynCommand { +// fn cli_app(&self) -> Option { +// if let Some(cli) = &self.cli { +// Some( +// cli.cmd +// .clone() +// .name(self.name) +// .subcommands(self.subcommands.iter().filter_map(|c| c.cli_app())), +// ) +// } else { +// None +// } +// } +// fn cmd_from_cli_matches( +// &self, +// matches: &ArgMatches, +// parent: ParentInfo, +// ) -> Result<(Vec<&'static str>, Value, &DynCommand), RpcError> { +// let params = combine( +// parent.params, +// (self +// .cli +// .as_ref() +// .ok_or(yajrc::METHOD_NOT_FOUND_ERROR)? +// .parser)(matches)?, +// )?; +// if let Some((cmd, matches)) = matches.subcommand() { +// let mut method = parent.method; +// method.push(self.name); +// self.subcommands +// .iter() +// .find(|c| c.name == cmd) +// .ok_or(yajrc::METHOD_NOT_FOUND_ERROR)? +// .cmd_from_cli_matches(matches, ParentInfo { method, params }) +// } else { +// Ok((parent.method, params, self)) +// } +// } +// } -struct CliApp { - cli: CliBindings, - commands: Vec>, -} +struct CliApp(ParentCommand); impl CliApp { pub fn new( commands: Vec>, @@ -306,7 +304,8 @@ where combine( imbl_value::to_value(&self).map_err(invalid_params)?, imbl_value::to_value(&parent.params).map_err(invalid_params)?, - )?, + ) + .map_err(invalid_params)?, ) .await?, ) @@ -330,15 +329,18 @@ where ) -> Result { let mut method = parent.method; method.push(Self::NAME); - Ok( - imbl_value::from_value(ctx.runtime().block_on(ctx.call_remote( - &method.join("."), - combine( - imbl_value::to_value(&self).map_err(invalid_params)?, - imbl_value::to_value(&parent.params).map_err(invalid_params)?, - )?, - ))?) - .map_err(parse_error)?, + Ok(imbl_value::from_value( + ctx.runtime().block_on( + ctx.call_remote( + &method.join("."), + combine( + imbl_value::to_value(&self).map_err(invalid_params)?, + imbl_value::to_value(&parent.params).map_err(invalid_params)?, + ) + .map_err(invalid_params)?, + ), + )?, ) + .map_err(parse_error)?) } } diff --git a/rpc-toolkit/src/command_helpers.rs b/rpc-toolkit/src/command_helpers.rs deleted file mode 100644 index c077ad5..0000000 --- a/rpc-toolkit/src/command_helpers.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::fmt::Display; -use std::io::Stdin; -use std::marker::PhantomData; -use std::str::FromStr; - -use clap::ArgMatches; -use hyper::Method; -use serde::{Deserialize, Serialize}; -use thiserror::Error; -use yajrc::{GenericRpcMethod, Id, RpcError, RpcRequest, RpcResponse}; - -use crate::Context; - -pub mod prelude { - pub use std::borrow::Cow; - pub use std::marker::PhantomData; - - pub use clap::{App, AppSettings, Arg, ArgMatches}; - pub use hyper::http::request::Parts as RequestParts; - pub use hyper::http::response::Parts as ResponseParts; - pub use serde::{Deserialize, Serialize}; - pub use serde_json::{from_value, to_value, Value}; - pub use tokio::runtime::Runtime; - pub use tokio::task::spawn_blocking; - pub use yajrc::{self, RpcError}; - - pub use super::{ - call_remote, default_arg_parser, default_display, default_stdin_parser, make_phantom, - match_types, - }; - pub use crate::Context; -} - -#[derive(Debug, Error)] -pub enum RequestError { - #[error("JSON Error: {0}")] - JSON(#[from] serde_json::Error), - #[cfg(feature = "cbor")] - #[error("CBOR Error: {0}")] - CBOR(#[from] serde_cbor::Error), - #[error("HTTP Error: {0}")] - HTTP(#[from] reqwest::Error), - #[error("Missing Content-Type")] - MissingContentType, -} - -pub fn make_phantom(_actual: T) -> PhantomData { - PhantomData -} - -pub fn match_types(_: &T, _: &T) {} - -pub async fn call_remote Deserialize<'de>>( - ctx: Ctx, - method: &str, - params: Params, - _return_ty: PhantomData, -) -> Result>, RequestError> { - let rpc_req: RpcRequest> = RpcRequest { - id: Some(Id::Number(0.into())), - method: GenericRpcMethod::new(method), - params, - }; - let mut req = ctx.client().request(Method::POST, ctx.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?; - Ok( - match res - .headers() - .get("content-type") - .and_then(|v| v.to_str().ok()) - { - Some("application/json") => serde_json::from_slice(&*res.bytes().await?)?, - #[cfg(feature = "cbor")] - Some("application/cbor") => serde_cbor::from_slice(&*res.bytes().await?)?, - _ => return Err(RequestError::MissingContentType), - }, - ) -} - -pub fn default_arg_parser, E: Display>( - arg: &str, - _: &ArgMatches, -) -> Result { - arg.parse().map_err(|e| RpcError { - data: Some(format!("{}", e).into()), - ..yajrc::INVALID_PARAMS_ERROR - }) -} - -pub fn default_stdin_parser, E: Display>( - stdin: &mut Stdin, - _: &ArgMatches, -) -> Result { - let mut s = String::new(); - stdin.read_line(&mut s).map_err(|e| RpcError { - data: Some(format!("{}", e).into()), - ..yajrc::INVALID_PARAMS_ERROR - })?; - if let Some(s) = s.strip_suffix("\n") { - s - } else { - &s - } - .parse() - .map_err(|e| RpcError { - data: Some(format!("{}", e).into()), - ..yajrc::INVALID_PARAMS_ERROR - }) -} - -pub fn default_display(t: T, _: &ArgMatches) { - println!("{}", t) -} diff --git a/rpc-toolkit/src/handler.rs b/rpc-toolkit/src/handler.rs new file mode 100644 index 0000000..91756f3 --- /dev/null +++ b/rpc-toolkit/src/handler.rs @@ -0,0 +1,769 @@ +use std::collections::{BTreeMap, VecDeque}; +use std::marker::PhantomData; + +use clap::{ArgMatches, Command, CommandFactory, FromArgMatches, Parser}; +use imbl_value::Value; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use yajrc::RpcError; + +use crate::util::{combine, internal_error, invalid_params, Flat}; + +struct HandleAnyArgs { + context: Context, + parent_method: Vec<&'static str>, + method: VecDeque<&'static str>, + params: Value, +} +impl HandleAnyArgs { + fn downcast(self) -> Result, imbl_value::Error> + where + H: Handler, + H::Params: DeserializeOwned, + H::InheritedParams: DeserializeOwned, + { + let Self { + context, + parent_method, + method, + params, + } = self; + Ok(HandleArgs { + context, + parent_method, + method, + params: imbl_value::from_value(params.clone())?, + inherited_params: imbl_value::from_value(params.clone())?, + }) + } +} + +#[async_trait::async_trait] +trait HandleAny { + fn handle_sync(&self, handle_args: HandleAnyArgs) -> Result; + // async fn handle_async(&self, handle_args: HandleAnyArgs) -> Result; +} + +trait CliBindingsAny { + fn cli_command(&self) -> Command; + fn cli_parse( + &self, + matches: &ArgMatches, + ) -> Result<(VecDeque<&'static str>, Value), clap::Error>; + fn cli_display( + &self, + handle_args: HandleAnyArgs, + result: Value, + ) -> Result<(), RpcError>; +} + +pub trait CliBindings: Handler { + fn cli_command(&self) -> Command; + fn cli_parse( + &self, + matches: &ArgMatches, + ) -> Result<(VecDeque<&'static str>, Value), clap::Error>; + fn cli_display( + &self, + handle_args: HandleArgs, + result: Self::Ok, + ) -> Result<(), Self::Err>; +} + +pub trait PrintCliResult: Handler { + fn print( + &self, + handle_args: HandleArgs, + result: Self::Ok, + ) -> Result<(), Self::Err>; +} + +// impl PrintCliResult for H +// where +// Context: crate::Context, +// H: Handler, +// H::Ok: Display, +// { +// fn print( +// &self, +// handle_args: HandleArgs, +// result: Self::Ok, +// ) -> Result<(), Self::Err> { +// Ok(println!("{result}")) +// } +// } + +struct WithCliBindings { + _ctx: PhantomData, + handler: H, +} + +impl Handler for WithCliBindings +where + Context: crate::Context, + H: Handler, +{ + type Params = H::Params; + type InheritedParams = H::InheritedParams; + type Ok = H::Ok; + type Err = H::Err; + fn handle_sync( + &self, + HandleArgs { + context, + parent_method, + method, + params, + inherited_params, + }: HandleArgs, + ) -> Result { + self.handler.handle_sync(HandleArgs { + context, + parent_method, + method, + params, + inherited_params, + }) + } +} + +impl CliBindings for WithCliBindings +where + Context: crate::Context, + H: Handler, + H::Params: FromArgMatches + CommandFactory + Serialize, + H: PrintCliResult, +{ + fn cli_command(&self) -> Command { + H::Params::command() + } + fn cli_parse( + &self, + matches: &ArgMatches, + ) -> Result<(VecDeque<&'static str>, Value), clap::Error> { + H::Params::from_arg_matches(matches).and_then(|a| { + Ok(( + VecDeque::new(), + imbl_value::to_value(&a) + .map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e))?, + )) + }) + } + fn cli_display( + &self, + HandleArgs { + context, + parent_method, + method, + params, + inherited_params, + }: HandleArgs, + result: Self::Ok, + ) -> Result<(), Self::Err> { + self.handler.print( + HandleArgs { + context, + parent_method, + method, + params, + inherited_params, + }, + result, + ) + } +} + +trait HandleAnyWithCli: HandleAny + CliBindingsAny {} +impl + CliBindingsAny> + HandleAnyWithCli for T +{ +} + +enum DynHandler { + WithoutCli(Box>), + WithCli(Box>), +} +impl HandleAny for DynHandler { + fn handle_sync(&self, handle_args: HandleAnyArgs) -> Result { + match self { + DynHandler::WithoutCli(h) => h.handle_sync(handle_args), + DynHandler::WithCli(h) => h.handle_sync(handle_args), + } + } +} + +pub struct HandleArgs + ?Sized> { + context: Context, + parent_method: Vec<&'static str>, + method: VecDeque<&'static str>, + params: H::Params, + inherited_params: H::InheritedParams, +} +impl HandleArgs +where + Context: crate::Context, + H: Handler, + H::Params: Serialize, + H::InheritedParams: Serialize, +{ + fn upcast( + Self { + context, + parent_method, + method, + params, + inherited_params, + }: Self, + ) -> Result, imbl_value::Error> { + Ok(HandleAnyArgs { + context, + parent_method, + method, + params: combine( + imbl_value::to_value(¶ms)?, + imbl_value::to_value(&inherited_params)?, + )?, + }) + } +} + +pub trait Handler { + type Params; + type InheritedParams; + type Ok; + type Err; + fn handle_sync(&self, handle_args: HandleArgs) -> Result; +} + +struct AnyHandler { + _ctx: PhantomData, + handler: H, +} + +impl> HandleAny for AnyHandler +where + H::Params: DeserializeOwned, + H::InheritedParams: DeserializeOwned, + H::Ok: Serialize, + RpcError: From, +{ + fn handle_sync(&self, handle_args: HandleAnyArgs) -> Result { + imbl_value::to_value( + &self + .handler + .handle_sync(handle_args.downcast().map_err(invalid_params)?)?, + ) + .map_err(internal_error) + } +} + +impl> CliBindingsAny + for AnyHandler +where + H::Params: FromArgMatches + CommandFactory + Serialize + DeserializeOwned, + H::InheritedParams: DeserializeOwned, + H::Ok: Serialize + DeserializeOwned, + RpcError: From, +{ + fn cli_command(&self) -> Command { + self.handler.cli_command() + } + fn cli_parse( + &self, + matches: &ArgMatches, + ) -> Result<(VecDeque<&'static str>, Value), clap::Error> { + self.handler.cli_parse(matches) + } + fn cli_display( + &self, + handle_args: HandleAnyArgs, + result: Value, + ) -> Result<(), RpcError> { + self.handler + .cli_display( + handle_args.downcast().map_err(invalid_params)?, + imbl_value::from_value(result).map_err(internal_error)?, + ) + .map_err(RpcError::from) + } +} + +#[derive(Debug, Clone, Copy, Deserialize, Serialize, Parser)] +pub struct NoParams {} + +#[derive(Debug, Clone, Copy, Deserialize, Serialize, Parser)] +enum Never {} + +struct EmptyHandler(PhantomData<(Params, InheritedParams)>); +impl Handler + for EmptyHandler +{ + type Params = Params; + type InheritedParams = InheritedParams; + type Ok = Never; + type Err = RpcError; + fn handle_sync(&self, _: HandleArgs) -> Result { + Err(yajrc::METHOD_NOT_FOUND_ERROR) + } +} + +pub struct ParentHandler> { + handler: H, + subcommands: BTreeMap<&'static str, DynHandler>, +} +impl + ParentHandler> +where + EmptyHandler: CliBindings, +{ + pub fn new() -> Self { + Self { + handler: WithCliBindings { + _ctx: PhantomData, + handler: EmptyHandler(PhantomData).into(), + }, + subcommands: BTreeMap::new(), + } + } +} + +impl + ParentHandler> +{ + pub fn new_no_cli() -> Self { + Self { + handler: EmptyHandler(PhantomData).into(), + subcommands: BTreeMap::new(), + } + } +} + +impl> From for ParentHandler { + fn from(value: H) -> Self { + Self { + handler: value.into(), + subcommands: BTreeMap::new(), + } + } +} + +struct InheritanceHandler< + Context: crate::Context, + H: Handler, + SubH: Handler, + F: Fn(H::Params, H::InheritedParams) -> SubH::InheritedParams, +> { + _phantom: PhantomData<(Context, H)>, + handler: SubH, + inherit: F, +} +impl< + Context: crate::Context, + H: Handler, + SubH: Handler, + F: Fn(H::Params, H::InheritedParams) -> SubH::InheritedParams, + > Handler for InheritanceHandler +{ + type Params = SubH::Params; + type InheritedParams = Flat; + type Ok = SubH::Ok; + type Err = SubH::Err; + fn handle_sync( + &self, + HandleArgs { + context, + parent_method, + method, + params, + inherited_params, + }: HandleArgs, + ) -> Result { + self.handler.handle_sync(HandleArgs { + context, + parent_method, + method, + params, + inherited_params: (self.inherit)(inherited_params.0, inherited_params.1), + }) + } +} + +impl PrintCliResult for InheritanceHandler +where + Context: crate::Context, + H: Handler, + SubH: Handler + PrintCliResult, + F: Fn(H::Params, H::InheritedParams) -> SubH::InheritedParams, +{ + fn print( + &self, + HandleArgs { + context, + parent_method, + method, + params, + inherited_params, + }: HandleArgs, + result: Self::Ok, + ) -> Result<(), Self::Err> { + self.handler.print( + HandleArgs { + context, + parent_method, + method, + params, + inherited_params: (self.inherit)(inherited_params.0, inherited_params.1), + }, + result, + ) + } +} + +impl> ParentHandler { + pub fn subcommand(mut self, method: &'static str, handler: SubH) -> Self + where + SubH: Handler + PrintCliResult + 'static, + SubH::Params: FromArgMatches + CommandFactory + Serialize + DeserializeOwned, + SubH::Ok: Serialize + DeserializeOwned, + RpcError: From, + { + self.subcommands.insert( + method, + DynHandler::WithCli(Box::new(AnyHandler { + _ctx: PhantomData, + handler: WithCliBindings { + _ctx: PhantomData, + handler, + }, + })), + ); + self + } + pub fn subcommand_with_inherited( + mut self, + method: &'static str, + handler: SubH, + inherit: F, + ) -> Self + where + SubH: Handler + PrintCliResult + 'static, + SubH::Params: FromArgMatches + CommandFactory + Serialize + DeserializeOwned, + SubH::Ok: Serialize + DeserializeOwned, + H: 'static, + H::Params: DeserializeOwned, + H::InheritedParams: DeserializeOwned, + RpcError: From, + F: Fn(H::Params, H::InheritedParams) -> SubH::InheritedParams + 'static, + { + self.subcommands.insert( + method, + DynHandler::WithCli(Box::new(AnyHandler { + _ctx: PhantomData, + handler: WithCliBindings { + _ctx: PhantomData, + handler: InheritanceHandler:: { + _phantom: PhantomData, + handler, + inherit, + }, + }, + })), + ); + self + } + pub fn subcommand_no_cli(mut self, method: &'static str, handler: SubH) -> Self + where + SubH: Handler + 'static, + SubH::Params: DeserializeOwned, + SubH::Ok: Serialize, + RpcError: From, + { + self.subcommands.insert( + method, + DynHandler::WithoutCli(Box::new(AnyHandler { + _ctx: PhantomData, + handler, + })), + ); + self + } + pub fn subcommand_with_inherited_no_cli( + mut self, + method: &'static str, + handler: SubH, + inherit: F, + ) -> Self + where + SubH: Handler + 'static, + SubH::Params: DeserializeOwned, + SubH::Ok: Serialize, + H: 'static, + H::Params: DeserializeOwned, + H::InheritedParams: DeserializeOwned, + RpcError: From, + F: Fn(H::Params, H::InheritedParams) -> SubH::InheritedParams + 'static, + { + self.subcommands.insert( + method, + DynHandler::WithoutCli(Box::new(AnyHandler { + _ctx: PhantomData, + handler: InheritanceHandler:: { + _phantom: PhantomData, + handler, + inherit, + }, + })), + ); + self + } +} + +impl Handler for ParentHandler +where + Context: crate::Context, + H: Handler, + H::Params: Serialize, + H::InheritedParams: Serialize, + H::Ok: Serialize + DeserializeOwned, + RpcError: From, +{ + type Params = H::Params; + type InheritedParams = H::InheritedParams; + type Ok = Value; + type Err = RpcError; + fn handle_sync( + &self, + HandleArgs { + context, + mut parent_method, + mut method, + params, + inherited_params, + }: HandleArgs, + ) -> Result { + if let Some(cmd) = method.pop_front() { + parent_method.push(cmd); + if let Some(sub_handler) = self.subcommands.get(cmd) { + sub_handler.handle_sync(HandleAnyArgs { + context, + parent_method, + method, + params: imbl_value::to_value(&Flat(params, inherited_params)) + .map_err(invalid_params)?, + }) + } else { + Err(yajrc::METHOD_NOT_FOUND_ERROR) + } + } else { + self.handler + .handle_sync(HandleArgs { + context, + parent_method, + method, + params, + inherited_params, + }) + .map_err(RpcError::from) + .and_then(|r| imbl_value::to_value(&r).map_err(internal_error)) + } + } +} + +impl CliBindings for ParentHandler +where + Context: crate::Context, + H: CliBindings, + H::Params: FromArgMatches + CommandFactory + Serialize, + H::InheritedParams: Serialize, + H::Ok: PrintCliResult + Serialize + DeserializeOwned, + RpcError: From, +{ + fn cli_command(&self) -> Command { + H::Params::command().subcommands(self.subcommands.iter().filter_map(|(method, handler)| { + match handler { + DynHandler::WithCli(h) => Some(h.cli_command().name(method)), + DynHandler::WithoutCli(_) => None, + } + })) + } + fn cli_parse( + &self, + matches: &ArgMatches, + ) -> Result<(VecDeque<&'static str>, Value), clap::Error> { + let (_, root_params) = self.handler.cli_parse(matches)?; + if let Some((sub, matches)) = matches.subcommand() { + if let Some((sub, DynHandler::WithCli(h))) = self.subcommands.get_key_value(sub) { + let (mut method, params) = h.cli_parse(matches)?; + method.push_front(*sub); + return Ok(( + method, + combine(root_params, params).map_err(|e| { + clap::Error::raw(clap::error::ErrorKind::ArgumentConflict, e) + })?, + )); + } + } + Ok((VecDeque::new(), root_params)) + } + fn cli_display( + &self, + HandleArgs { + context, + mut parent_method, + mut method, + params, + inherited_params, + }: HandleArgs, + result: Self::Ok, + ) -> Result<(), Self::Err> { + if let Some(cmd) = method.pop_front() { + parent_method.push(cmd); + if let Some(DynHandler::WithCli(sub_handler)) = self.subcommands.get(cmd) { + sub_handler.cli_display( + HandleAnyArgs { + context, + parent_method, + method, + params: imbl_value::to_value(&Flat(params, inherited_params)) + .map_err(invalid_params)?, + }, + result, + ) + } else { + Err(yajrc::METHOD_NOT_FOUND_ERROR) + } + } else { + self.handler + .cli_display( + HandleArgs { + context, + parent_method, + method, + params, + inherited_params, + }, + imbl_value::from_value(result).map_err(internal_error)?, + ) + .map_err(RpcError::from) + } + } +} + +pub struct FromFn { + _phantom: PhantomData<(T, E, Args)>, + function: F, +} + +pub fn from_fn(function: F) -> FromFn { + FromFn { + function, + _phantom: PhantomData, + } +} + +impl Handler for FromFn +where + Context: crate::Context, + F: Fn() -> Result, +{ + type Params = NoParams; + type InheritedParams = NoParams; + type Ok = T; + type Err = E; + fn handle_sync(&self, _: HandleArgs) -> Result { + (self.function)() + } +} + +impl Handler for FromFn +where + Context: crate::Context, + F: Fn(Context) -> Result, +{ + type Params = NoParams; + type InheritedParams = NoParams; + type Ok = T; + type Err = E; + fn handle_sync(&self, handle_args: HandleArgs) -> Result { + (self.function)(handle_args.context) + } +} +impl Handler for FromFn +where + Context: crate::Context, + F: Fn(Context, Params) -> Result, + Params: DeserializeOwned, +{ + type Params = Params; + type InheritedParams = NoParams; + type Ok = T; + type Err = E; + fn handle_sync(&self, handle_args: HandleArgs) -> Result { + let HandleArgs { + context, params, .. + } = handle_args; + (self.function)(context, params) + } +} +impl Handler + for FromFn +where + Context: crate::Context, + F: Fn(Context, Params, InheritedParams) -> Result, + Params: DeserializeOwned, + InheritedParams: DeserializeOwned, +{ + type Params = Params; + type InheritedParams = InheritedParams; + type Ok = T; + type Err = E; + fn handle_sync(&self, handle_args: HandleArgs) -> Result { + let HandleArgs { + context, + params, + inherited_params, + .. + } = handle_args; + (self.function)(context, params, inherited_params) + } +} + +#[derive(Parser)] +#[command(about = "this is db stuff")] +struct DbParams {} + +// Server::new( +// ParentCommand::new() +// .subcommand("foo", from_fn(foo)) +// .subcommand("db", +// ParentCommand::new::() +// .subcommand("dump", from_fn(dump)) +// ) +// ) + +// Server::new() +// .handle( +// "db", +// with_description("Description maybe?") +// .handle("dump", from_fn(dump_route)) +// ) +// .handle( +// "server", +// no_description() +// .handle("version", from_fn(version)) +// ) + +// #[derive(clap::Parser)] +// struct DumpParams { +// test: Option +// } + +// fn dump_route(context: Context, param: Param) -> Result { +// Ok(json!({ +// "db": {} +// })) +// } + +// fn version() -> &'static str { +// "1.0.0" +// } diff --git a/rpc-toolkit/src/lib.rs b/rpc-toolkit/src/lib.rs index 7c5807e..e69e045 100644 --- a/rpc-toolkit/src/lib.rs +++ b/rpc-toolkit/src/lib.rs @@ -1,6 +1,7 @@ pub use cli::*; -pub use command::*; +// pub use command::*; pub use context::Context; +pub use handler::*; /// `#[command(...)]` /// - `#[command(cli_only)]` -> executed by CLI instead of RPC server (leaf commands only) /// - `#[command(rpc_only)]` -> no CLI bindings (leaf commands only) @@ -28,6 +29,7 @@ pub use {clap, futures, hyper, reqwest, serde, serde_json, tokio, url, yajrc}; mod cli; mod command; +mod handler; // pub mod command_helpers; mod context; // mod metadata; diff --git a/rpc-toolkit/src/rpc_server_helpers.rs b/rpc-toolkit/src/rpc_server_helpers.rs deleted file mode 100644 index 02ecef4..0000000 --- a/rpc-toolkit/src/rpc_server_helpers.rs +++ /dev/null @@ -1,186 +0,0 @@ -use std::future::Future; -use std::sync::Arc; - -use futures::future::BoxFuture; -use futures::FutureExt; -use hyper::body::Buf; -use hyper::header::HeaderValue; -use hyper::http::request::Parts as RequestParts; -use hyper::http::response::Parts as ResponseParts; -use hyper::http::Error as HttpError; -use hyper::server::conn::AddrIncoming; -use hyper::server::{Builder, Server}; -use hyper::{Body, HeaderMap, Request, Response, StatusCode}; -use lazy_static::lazy_static; -use serde_json::{Map, Value}; -use url::Host; -use yajrc::{AnyRpcMethod, GenericRpcMethod, Id, RpcError, RpcRequest, RpcResponse}; - -use crate::{Context, Metadata}; - -lazy_static! { - #[cfg(feature = "cbor")] - static ref CBOR_INTERNAL_ERROR: Vec = - serde_cbor::to_vec(&RpcResponse::>::from(yajrc::INTERNAL_ERROR)).unwrap(); - static ref JSON_INTERNAL_ERROR: Vec = - serde_json::to_vec(&RpcResponse::>::from(yajrc::INTERNAL_ERROR)).unwrap(); -} - -pub fn make_builder(ctx: &Ctx) -> Builder { - let addr = match ctx.host() { - Host::Ipv4(ip) => (ip, ctx.port()).into(), - Host::Ipv6(ip) => (ip, ctx.port()).into(), - Host::Domain(localhost) if localhost == "localhost" => ([127, 0, 0, 1], ctx.port()).into(), - _ => ([0, 0, 0, 0], ctx.port()).into(), - }; - Server::bind(&addr) -} - -pub async fn make_request( - req_parts: &RequestParts, - req_body: Body, -) -> Result>>, RpcError> { - let body = hyper::body::aggregate(req_body).await?.reader(); - let rpc_req: RpcRequest>>; - #[cfg(feature = "cbor")] - if req_parts - .headers - .get("content-type") - .and_then(|h| h.to_str().ok()) - == Some("application/cbor") - { - rpc_req = serde_cbor::from_reader(body)?; - } else { - rpc_req = serde_json::from_reader(body)?; - } - #[cfg(not(feature = "cbor"))] - { - rpc_req = serde_json::from_reader(body)?; - } - - Ok(rpc_req) -} - -pub fn to_response StatusCode>( - req_headers: &HeaderMap, - mut res_parts: ResponseParts, - res: Result<(Option, Result), RpcError>, - status_code_fn: F, -) -> Result, HttpError> { - let rpc_res: RpcResponse = match res { - Ok((id, result)) => RpcResponse { id, result }, - Err(e) => e.into(), - }; - let body; - #[cfg(feature = "cbor")] - if req_headers - .get("accept") - .and_then(|h| h.to_str().ok()) - .iter() - .flat_map(|s| s.split(",")) - .map(|s| s.trim()) - .any(|s| s == "application/cbor") - // prefer cbor if accepted - { - res_parts - .headers - .insert("content-type", HeaderValue::from_static("application/cbor")); - body = serde_cbor::to_vec(&rpc_res).unwrap_or_else(|_| CBOR_INTERNAL_ERROR.clone()); - } else { - res_parts - .headers - .insert("content-type", HeaderValue::from_static("application/json")); - body = serde_json::to_vec(&rpc_res).unwrap_or_else(|_| JSON_INTERNAL_ERROR.clone()); - } - #[cfg(not(feature = "cbor"))] - { - res_parts - .headers - .insert("content-type", HeaderValue::from_static("application/json")); - body = serde_json::to_vec(&rpc_res).unwrap_or_else(|_| JSON_INTERNAL_ERROR.clone()); - } - res_parts.headers.insert( - "content-length", - HeaderValue::from_str(&format!("{}", body.len()))?, - ); - res_parts.status = match &rpc_res.result { - Ok(_) => StatusCode::OK, - Err(e) => status_code_fn(e.code), - }; - Ok(Response::from_parts(res_parts, body.into())) -} - -pub type RpcHandler = Arc< - dyn Fn(Request) -> BoxFuture<'static, Result, HttpError>> + Send + Sync, ->; - -// &mut Request -> Result -> Future -> Future>, Response>, HttpError>>>, Response>, HttpError> -pub type DynMiddleware = Box< - dyn for<'a> Fn( - &'a mut Request, - Metadata, - ) - -> BoxFuture<'a, Result>, HttpError>> - + Send - + Sync, ->; -pub fn noop() -> DynMiddleware { - Box::new(|_, _| async { Ok(Ok(noop2())) }.boxed()) -} -pub type DynMiddlewareStage2 = Box< - dyn for<'a> FnOnce( - &'a mut RequestParts, - &'a mut RpcRequest>>, - ) -> BoxFuture< - 'a, - Result>, HttpError>, - > + Send - + Sync, ->; -pub fn noop2() -> DynMiddlewareStage2 { - Box::new(|_, _| async { Ok(Ok(noop3())) }.boxed()) -} -pub type DynMiddlewareStage3 = Box< - dyn for<'a> FnOnce( - &'a mut ResponseParts, - &'a mut Result, - ) -> BoxFuture< - 'a, - Result>, HttpError>, - > + Send - + Sync, ->; -pub fn noop3() -> DynMiddlewareStage3 { - Box::new(|_, _| async { Ok(Ok(noop4())) }.boxed()) -} -pub type DynMiddlewareStage4 = Box< - dyn for<'a> FnOnce(&'a mut Response) -> BoxFuture<'a, Result<(), HttpError>> - + Send - + Sync, ->; -pub fn noop4() -> DynMiddlewareStage4 { - Box::new(|_| async { Ok(()) }.boxed()) -} - -pub fn constrain_middleware< - 'a, - 'b, - 'c, - 'd, - M: Metadata, - ReqFn: Fn(&'a mut Request, M) -> ReqFut + Clone, - ReqFut: Future>, HttpError>> + 'a, - RpcReqFn: FnOnce( - &'b mut RequestParts, - &'b mut RpcRequest>>, - ) -> RpcReqFut, - RpcReqFut: Future>, HttpError>> + 'b, - RpcResFn: FnOnce(&'c mut ResponseParts, &'c mut Result) -> RpcResFut, - RpcResFut: Future>, HttpError>> + 'c, - ResFn: FnOnce(&'d mut Response) -> ResFut, - ResFut: Future> + 'd, ->( - f: ReqFn, -) -> ReqFn { - f -} diff --git a/rpc-toolkit/src/util.rs b/rpc-toolkit/src/util.rs index 00c3540..133b8cf 100644 --- a/rpc-toolkit/src/util.rs +++ b/rpc-toolkit/src/util.rs @@ -4,7 +4,8 @@ use futures::future::BoxFuture; use futures::{Future, FutureExt, Stream, StreamExt}; use imbl_value::Value; use serde::de::DeserializeOwned; -use serde::Deserialize; +use serde::ser::Error; +use serde::{Deserialize, Serialize}; use yajrc::RpcError; pub fn extract(value: &Value) -> Result { @@ -14,18 +15,20 @@ pub fn extract(value: &Value) -> Result { }) } -pub fn combine(v1: Value, v2: Value) -> Result { +pub fn combine(v1: Value, v2: Value) -> Result { let (Value::Object(mut v1), Value::Object(v2)) = (v1, v2) else { - return Err(RpcError { - data: Some("params must be object".into()), - ..yajrc::INVALID_PARAMS_ERROR + return Err(imbl_value::Error { + kind: imbl_value::ErrorKind::Serialization, + source: serde_json::Error::custom("params must be object"), }); }; for (key, value) in v2 { if v1.insert(key.clone(), value).is_some() { - return Err(RpcError { - data: Some(format!("duplicate key: {key}").into()), - ..yajrc::INVALID_PARAMS_ERROR + return Err(imbl_value::Error { + kind: imbl_value::ErrorKind::Serialization, + source: serde_json::Error::custom(lazy_format::lazy_format!( + "duplicate key: {key}" + )), }); } } @@ -76,6 +79,29 @@ where Ok(Flat(a, b)) } } +impl Serialize for Flat +where + A: Serialize, + B: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + #[derive(serde::Serialize)] + struct FlatStruct<'a, A, B> { + #[serde(flatten)] + a: &'a A, + #[serde(flatten)] + b: &'a B, + } + FlatStruct { + a: &self.0, + b: &self.1, + } + .serialize(serializer) + } +} pub fn poll_select_all<'a, T>( futs: &mut Vec>, @@ -145,3 +171,17 @@ impl<'a, T> Stream for JobRunner<'a, T> { } } } + +// #[derive(Debug)] +// pub enum Infallible {} +// impl From for T { +// fn from(value: Infallible) -> Self { +// match value {} +// } +// } +// impl std::fmt::Display for Infallible { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// match *self {} +// } +// } +// impl std::error::Error for Infallible {}