From f6a4cd4663a137f87189784c05f04876cdf766f7 Mon Sep 17 00:00:00 2001 From: Ignacio Corderi Date: Mon, 3 Apr 2017 22:10:57 -0700 Subject: [PATCH] feat: add `parse_params!` macro using new `Params` --- examples/time_server.rs | 26 +++--- src/server.rs | 191 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+), 12 deletions(-) diff --git a/examples/time_server.rs b/examples/time_server.rs index 1734c3d..39b4e3f 100644 --- a/examples/time_server.rs +++ b/examples/time_server.rs @@ -37,14 +37,6 @@ use slog_term::{FullFormat, PlainSyncDecorator}; use tokio_jsonrpc::{Endpoint, LineCodec, Params, RpcError, Server, ServerCtl}; -/// A helper struct to deserialize the parameters -#[derive(Deserialize)] -struct SubscribeParams { - secs: u64, - #[serde(default)] - nsecs: u32, -} - /// Number of seconds since epoch fn now() -> u64 { SystemTime::now() @@ -79,15 +71,25 @@ impl Server for TimeServer { "subscribe" => { debug!(self.1, "Subscribing"); // Some parsing and bailing out on errors - // XXX: hack until we get jsonrpc_params changed to handle &Option - let params2 = params.clone().map(|x| x.into_value()); - let (s_params,) = jsonrpc_params!(¶ms2, "s_params" => SubscribeParams); + // XXX: why is params borrowed? + let params2 = params.clone(); + let params = parse_params!(params2, { secs: u64, + #[serde(default)] + nsecs: u32, }); + // XXX: this is not happy code + // `impl Carrier` might make it nicer, if/when it lands on stable + let params = match params { + /// XXX: this should not be here, we should not return option by default + None => return Some(Err(RpcError::invalid_params(None))), + Some(Err(err)) => return Some(Err(err)), + Some(Ok(params)) => params, + }; // We need to have a client to be able to send notifications let client = ctl.client(); let handle = self.0.clone(); let logger = self.1.clone(); // Get a stream that „ticks“ - let result = Interval::new(Duration::new(s_params.secs, s_params.nsecs), &self.0) + let result = Interval::new(Duration::new(params.secs, params.nsecs), &self.0) .or_else(|e| Err(RpcError::server_error(Some(format!("Interval: {}", e))))) .map(move |interval| { let logger_cloned = logger.clone(); diff --git a/src/server.rs b/src/server.rs index 0611ba5..b7e4100 100644 --- a/src/server.rs +++ b/src/server.rs @@ -207,6 +207,197 @@ impl Server for ServerChain { } } + +// TODO: add a tokio-jsonrpc-derive that will do similar code for an already +// Review: why are we returning Options? +// TODO: have the user opt-in on an optional result +// existing struct +#[macro_export] +macro_rules! parse_params { + // + // internal + // + + // terminal rule + (@parse [$params:expr, $(#[$attr:meta])*] + $members:tt + [$($field_name:ident),*] + { $(,)* } + ) => { + { + $(#[$attr])* + #[derive(Deserialize)] + struct X $members + + fn __parse(params: Option<$crate::Params>) -> Option> { + match params { + Some($crate::Params::Positional(mut xs)) => { + // Turn the positions into a partial object + // We can leverage any #[serde(default)] by letting serde do the work + let mut object = $crate::macro_exports::Map::new(); + + // Review: do we need to construct an array or can we just use the macro loop? + let fields = vec![$( stringify!($field_name)),*]; + for f in fields { + if ! xs.is_empty() { + object.insert(f.into(), xs.remove(0)); + } + } + + let value = $crate::macro_exports::Value::Object(object); + Some($crate::macro_exports + ::from_value::(value) + .map_err(|err| + $crate::message::RpcError::parse_error(format!("{}", err)))) + }, + Some($crate::Params::Named(x)) => { + let value = $crate::macro_exports::Value::Object(x); + Some($crate::macro_exports + ::from_value::(value) + .map_err(|err| + $crate::message::RpcError::parse_error(format!("{}", err)))) + } + None => { + // Review: should we try to parse an object where all fields are missing? + None + } + } + } + + __parse($params) + } + }; + // Parse metadata + (@parse $t_:tt + { $($members:tt)* } + $field_names_:tt + { $(#[$attr:meta])+ $($body:tt)* } + ) => { + parse_params! { @parse $t_ + { $($members)* $(#[$attr])+ } + $field_names_ + { $($body)* } + } + }; + // Parse member + (@parse $t_:tt + { $($members:tt)* } + [$($field_name:ident),*] + { $n:ident: $t:ty, $($body:tt)* } + ) => { + parse_params! { @parse $t_ + { $($members)* pub $n: $t, } + [$($field_name,)* $n] + { $($body)* } + } + }; + // Parse last member + (@parse $t_:tt + { $($members:tt)* } + [$($field_name:ident),*] + { $n:ident: $t:ty } + ) => { + parse_params! { @parse $t_ + { $($members)* pub $n: $t, } + [$($field_name,)* $n] + { } + } + }; + + // + // entry points + // + + // macro entry point, must come last + ($params:expr, $(#[$attr:meta])* { $($body:tt)* }) => { + parse_params!(@parse [$params, $(#[$attr])*] {} [] { $($body)* }); + }; +} + +// XXX: do NOT merge this in +// Trying out some usage cases +// TODO: turn this usage cases into propper test cases +#[cfg(test)] +#[test] +pub fn foobar_anonymous_structs() { + // By position + let params = params!([7, "hello world"]); + let x = parse_params!(params, { x: usize, name: String, }).unwrap().unwrap(); + assert_eq!(x.x, 7); + assert_eq!(x.name, "hello world"); + + // By name + let params = params!({"x": 7, "name": "hello world"}); + let x = parse_params!(params, { x: usize, name: String, }).unwrap().unwrap(); + assert_eq!(x.x, 7); + assert_eq!(x.name, "hello world"); + + println!("cp::1"); + + // Missing optional args (named) + let params = params!({"x":7}); + let x = parse_params!(params, #[derive(Debug)] { x: usize, name: Option, }) + .unwrap() + .unwrap(); + println!("{:?}", x); + assert_eq!(x.x, 7); + assert_eq!(x.name, None); + + println!("cp::2"); + + // Missing optional args (positional) + let params = params!([7]); + let x = parse_params!(params, #[derive(Debug)] { x: usize, name: Option, }) + .unwrap() + .unwrap(); + println!("{:?}", x); + assert_eq!(x.x, 7); + assert_eq!(x.name, None); + + println!("cp::3"); + + // Nada + let params = params!(); + let x = parse_params!(params, { x: usize, name: Option, }); + assert!(x.is_none()); + + println!("cp::4"); + + #[derive(Debug, Deserialize, PartialEq)] + enum Kind { + Foo, + Bar, + } + + impl Default for Kind { + fn default() -> Self { + Kind::Bar + } + } + + // Complex parsing with metadata (named) + let params = params!({ "x": 8 }); + let x = parse_params!(params, { x: usize, + #[serde(default)] + kind: Kind, }) + .unwrap() + .unwrap(); + assert_eq!(x.x, 8); + assert_eq!(x.kind, Kind::Bar); + + println!("cp::5"); + + // Complex parsing with metadata (positional) + let params = params!([8]); + let x = parse_params!(params, { x: usize, + #[serde(default)] + kind: Kind, }) + .unwrap() + .unwrap(); + assert_eq!(x.x, 8); + assert_eq!(x.kind, Kind::Bar); +} + // TODO: rename to parse_params / params_parse / from_params // TODO: make it expect a `Option` instead of an `Option` /// Parses the parameters of an RPC or a notification.