diff --git a/Cargo.lock b/Cargo.lock index 73b4576..0aba0e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -712,7 +712,7 @@ dependencies = [ [[package]] name = "rpc-toolkit" -version = "0.2.1" +version = "0.2.2" dependencies = [ "clap", "futures", @@ -732,7 +732,7 @@ dependencies = [ [[package]] name = "rpc-toolkit-macro" -version = "0.2.1" +version = "0.2.2" dependencies = [ "proc-macro2", "rpc-toolkit-macro-internals", @@ -741,7 +741,7 @@ dependencies = [ [[package]] name = "rpc-toolkit-macro-internals" -version = "0.2.1" +version = "0.2.2" dependencies = [ "proc-macro2", "quote", diff --git a/rpc-toolkit-macro-internals/Cargo.toml b/rpc-toolkit-macro-internals/Cargo.toml index e54b4f0..87552e5 100644 --- a/rpc-toolkit-macro-internals/Cargo.toml +++ b/rpc-toolkit-macro-internals/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Aiden McClelland "] edition = "2018" name = "rpc-toolkit-macro-internals" -version = "0.2.1" +version = "0.2.2" description = "internals for macros for rpc-toolkit" license = "MIT" diff --git a/rpc-toolkit-macro-internals/src/lib.rs b/rpc-toolkit-macro-internals/src/lib.rs index 79597f0..c8535d7 100644 --- a/rpc-toolkit-macro-internals/src/lib.rs +++ b/rpc-toolkit-macro-internals/src/lib.rs @@ -8,10 +8,13 @@ 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; diff --git a/rpc-toolkit-macro-internals/src/rpc_handler/build.rs b/rpc-toolkit-macro-internals/src/rpc_handler/build.rs new file mode 100644 index 0000000..eb7d101 --- /dev/null +++ b/rpc-toolkit-macro-internals/src/rpc_handler/build.rs @@ -0,0 +1,129 @@ +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 new file mode 100644 index 0000000..2683733 --- /dev/null +++ b/rpc-toolkit-macro-internals/src/rpc_handler/mod.rs @@ -0,0 +1,12 @@ +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_server/parse.rs b/rpc-toolkit-macro-internals/src/rpc_handler/parse.rs similarity index 96% rename from rpc-toolkit-macro-internals/src/rpc_server/parse.rs rename to rpc-toolkit-macro-internals/src/rpc_handler/parse.rs index 4530f6e..e9879d8 100644 --- a/rpc-toolkit-macro-internals/src/rpc_server/parse.rs +++ b/rpc-toolkit-macro-internals/src/rpc_handler/parse.rs @@ -3,7 +3,7 @@ use syn::punctuated::Punctuated; use super::*; -impl Parse for RpcServerArgs { +impl Parse for RpcHandlerArgs { fn parse(input: ParseStream) -> Result { let args; braced!(args in input); @@ -39,7 +39,7 @@ impl Parse for RpcServerArgs { let _: token::Comma = args.parse()?; } } - Ok(RpcServerArgs { + Ok(RpcHandlerArgs { command: command.expect("`command` is required"), ctx: ctx.expect("`context` is required"), parent_data, diff --git a/rpc-toolkit-macro-internals/src/rpc_server/build.rs b/rpc-toolkit-macro-internals/src/rpc_server/build.rs index f2a4ae0..e195999 100644 --- a/rpc-toolkit-macro-internals/src/rpc_server/build.rs +++ b/rpc-toolkit-macro-internals/src/rpc_server/build.rs @@ -1,134 +1,23 @@ -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::quote; -use syn::spanned::Spanned; use super::*; -pub fn build(args: RpcServerArgs) -> TokenStream { - let mut command = args.command; - let mut arguments = std::mem::replace( - &mut command.segments.last_mut().unwrap().arguments, - PathArguments::None, +pub fn build(mut args: RpcServerArgs) -> TokenStream { + let ctx = std::mem::replace( + &mut args.ctx, + parse2(quote! { __rpc_toolkit__rpc_server__context }).unwrap(), ); - 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!("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_clone5 = middleware_name_clone.clone(); - let middleware_name_clone6 = 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 handler = crate::rpc_handler::build::build(args); let res = quote! { { - let ctx = #ctx; - let parent_data = #parent_data; - let status_fn = #status_fn; - let builder = ::rpc_toolkit::rpc_server_helpers::make_builder(&ctx); - #( - let #middleware_name_clone = ::std::sync::Arc::new(#middleware); - )* - let make_svc = ::rpc_toolkit::hyper::service::make_service_fn(move |_| { - let ctx = ctx.clone(); - let parent_data = parent_data.clone(); - #( - let #middleware_name_clone3 = #middleware_name_clone2.clone(); - )* - async move { - Ok::<_, ::rpc_toolkit::hyper::Error>(::rpc_toolkit::hyper::service::service_fn(move |mut req| { - let ctx = ctx.clone(); - let parent_data = parent_data.clone(); - let metadata = #command_module::Metadata::default(); - #( - let #middleware_name_clone5 = #middleware_name_clone4.clone(); - )* - async move { - #( - let #middleware_name_pre = match ::rpc_toolkit::rpc_server_helpers::constrain_middleware(&*#middleware_name_clone6)(&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, - )), - 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), - status_fn, - ), - } - } - })) - } - }); - builder.serve(make_svc) + 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); diff --git a/rpc-toolkit-macro-internals/src/rpc_server/mod.rs b/rpc-toolkit-macro-internals/src/rpc_server/mod.rs index 7016ce6..0f6ca0c 100644 --- a/rpc-toolkit-macro-internals/src/rpc_server/mod.rs +++ b/rpc-toolkit-macro-internals/src/rpc_server/mod.rs @@ -1,12 +1,5 @@ use syn::*; -pub struct RpcServerArgs { - command: Path, - ctx: Expr, - parent_data: Option, - status_fn: Option, - middleware: punctuated::Punctuated, -} +pub type RpcServerArgs = crate::RpcHandlerArgs; pub mod build; -mod parse; diff --git a/rpc-toolkit-macro/Cargo.toml b/rpc-toolkit-macro/Cargo.toml index e6faabe..4be093c 100644 --- a/rpc-toolkit-macro/Cargo.toml +++ b/rpc-toolkit-macro/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Aiden McClelland "] edition = "2018" name = "rpc-toolkit-macro" -version = "0.2.1" +version = "0.2.2" description = "macros for rpc-toolkit" license = "MIT" @@ -13,5 +13,5 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" -rpc-toolkit-macro-internals = { version = "0.2.1", path = "../rpc-toolkit-macro-internals" } +rpc-toolkit-macro-internals = { version = "=0.2.2", path = "../rpc-toolkit-macro-internals" } syn = "1.0" diff --git a/rpc-toolkit-macro/src/lib.rs b/rpc-toolkit-macro/src/lib.rs index d84a3c3..83d3682 100644 --- a/rpc-toolkit-macro/src/lib.rs +++ b/rpc-toolkit-macro/src/lib.rs @@ -43,6 +43,12 @@ pub fn context(_: TokenStream, _: TokenStream) -> TokenStream { .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); diff --git a/rpc-toolkit/Cargo.toml b/rpc-toolkit/Cargo.toml index c41f97a..106f781 100644 --- a/rpc-toolkit/Cargo.toml +++ b/rpc-toolkit/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Aiden McClelland "] edition = "2018" name = "rpc-toolkit" -version = "0.2.1" +version = "0.2.2" description = "A toolkit for creating JSON-RPC 2.0 servers with automatic cli bindings" license = "MIT" documentation = "https://docs.rs/rpc-toolkit" @@ -29,7 +29,7 @@ hyper = { version = "0.14", features = [ lazy_static = "1.4" openssl = { version = "0.10", features = ["vendored"] } reqwest = { version = "0.11" } -rpc-toolkit-macro = { version = "0.2.1", path = "../rpc-toolkit-macro" } +rpc-toolkit-macro = { version = "=0.2.2", path = "../rpc-toolkit-macro" } serde = { version = "1.0", features = ["derive"] } serde_cbor = { version = "0.11", optional = true } serde_json = "1.0" diff --git a/rpc-toolkit/src/lib.rs b/rpc-toolkit/src/lib.rs index 894cfad..861eab7 100644 --- a/rpc-toolkit/src/lib.rs +++ b/rpc-toolkit/src/lib.rs @@ -20,6 +20,13 @@ /// /// See also: [arg](rpc_toolkit_macro::arg), [context](rpc_toolkit_macro::context) pub use rpc_toolkit_macro::command; +/// `rpc_handler!(command, context, status_fn)` +/// - returns: [RpcHandler](rpc_toolkit::RpcHandler) +/// - `command`: path to an rpc command (with the `#[command]` attribute) +/// - `context`: The [Context] for `command`. Must implement [Clone](std::clone::Clone). +/// - `status_fn` (optional): a function that takes a JSON RPC error code (`i32`) and returns a [StatusCode](hyper::StatusCode) +/// - default: `|_| StatusCode::OK` +pub use rpc_toolkit_macro::rpc_handler; /// `rpc_server!(command, context, status_fn)` /// - returns: [Server](hyper::Server) /// - `command`: path to an rpc command (with the `#[command]` attribute) @@ -39,10 +46,11 @@ pub use rpc_toolkit_macro::rpc_server; /// - `exit_fn` (optional): a function that takes a JSON RPC error code (`i32`) and returns an Exit code (`i32`) /// - default: `|code| code` pub use rpc_toolkit_macro::run_cli; -pub use {clap, hyper, reqwest, serde, serde_json, tokio, url, yajrc}; +pub use {clap, futures, hyper, reqwest, serde, serde_json, tokio, url, yajrc}; pub use crate::context::Context; pub use crate::metadata::Metadata; +pub use crate::rpc_server_helpers::RpcHandler; pub mod command_helpers; mod context; diff --git a/rpc-toolkit/src/rpc_server_helpers.rs b/rpc-toolkit/src/rpc_server_helpers.rs index d1642c1..02ecef4 100644 --- a/rpc-toolkit/src/rpc_server_helpers.rs +++ b/rpc-toolkit/src/rpc_server_helpers.rs @@ -1,4 +1,5 @@ use std::future::Future; +use std::sync::Arc; use futures::future::BoxFuture; use futures::FutureExt; @@ -109,6 +110,10 @@ pub fn to_response StatusCode>( 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(