Skip to content

Commit

Permalink
feat: add parse_params! macro using new Params
Browse files Browse the repository at this point in the history
  • Loading branch information
icorderi committed Apr 4, 2017
1 parent 9981b30 commit f6a4cd4
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 12 deletions.
26 changes: 14 additions & 12 deletions examples/time_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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<Params>
let params2 = params.clone().map(|x| x.into_value());
let (s_params,) = jsonrpc_params!(&params2, "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();
Expand Down
191 changes: 191 additions & 0 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Result<X, RpcError>> {
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::<X>(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::<X>(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<String>, })
.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<String>, })
.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<String>, });
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<Params>` instead of an `Option<Value>`
/// Parses the parameters of an RPC or a notification.
Expand Down

0 comments on commit f6a4cd4

Please sign in to comment.