Navigation Menu

Skip to content

Commit

Permalink
v0.2.0 with lifetimes
Browse files Browse the repository at this point in the history
  • Loading branch information
vlopes11 committed Apr 22, 2019
1 parent ec69c95 commit 6f15176
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 274 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "futures-jsonrpc"
version = "0.1.3"
version = "0.2.0"
authors = ["Victor Lopez <vhrlopes@gmail.com>"]
edition = "2018"
description = "Futures implementation for JSON-RPC"
Expand Down
214 changes: 114 additions & 100 deletions README.md
Expand Up @@ -23,42 +23,140 @@ Add this to your `Cargo.toml`:

```toml
[dependencies]
futures_jsonrpc = "0.1"
futures_jsonrpc = "0.2"
```

## Examples
## Minimal example

```rust
use futures_jsonrpc::futures::prelude::*;
use futures_jsonrpc::*;
use serde_json::Number;
use std::marker::PhantomData;

// This macro will avoid some boilerplating, leaving only the `Future` implementation to be done
//
// Check for additional information in the detailed explanation below
generate_method_with_data_and_future!(
CopyParams,
'r,
(String, i32, PhantomData<&'r ()>),
impl<'r> Future for CopyParams<'r> {
type Item = Option<JrpcResponse>;
type Error = ErrorVariant;

fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> {
let request = self.get_request()?;
let (_text, _value, _) = self.get_data();
let params = request.get_params().clone().unwrap_or(JsonValue::Null);

let message = JrpcResponseParam::generate_result(params)
.and_then(|result| request.generate_response(result))?;

Ok(Async::Ready(Some(message)))
}
}
);

fn main() {
// `JrpcHandler` instance is responsible for registering the JSON-RPC methods and receiving the
// requests.
//
// This is full `Arc`/`RwLock` protected. Therefore, it can be freely copied/sent among
// threads.
let handler = JrpcHandler::new().unwrap();

handler
// `register_method` will tie the method signature to an instance, not a generic. This
// means we can freely mutate this instance across different signatures.
.register_method("some/copyParams", CopyParams::new((String::new(), 15, PhantomData)).unwrap())

.and_then(|h| {
// `handle_message` will receive a raw implementation of `ToString` and return the
// associated future. If no future is found, an instance of
// `Err(ErrorVariant::MethodSignatureNotFound(String))` is returned
h.handle_message(
r#"
{
"jsonrpc": "2.0",
"method": "some/copyParams",
"params": [42, 23],
"id": 531
}"#,
)
})

// Just waiting for the poll of future. Check futures documentation.
.and_then(|future| future.wait())
.and_then(|result| {
// The result is an instance of `JrpcResponse`
let result = result.unwrap();

assert_eq!(result.get_jsonrpc(), "2.0");
assert_eq!(
result.get_result(),
&Some(JsonValue::Array(vec![
JsonValue::Number(Number::from(42)),
JsonValue::Number(Number::from(23)),
]))
);
assert!(result.get_error().is_none());
assert_eq!(result.get_id(), &JsonValue::Number(Number::from(531)));
Ok(())
})
.unwrap();
}
```

## Detailed explanation

```rust
use futures_jsonrpc::futures::prelude::*;
use futures_jsonrpc::*;
use std::marker::PhantomData;

// `JrpcHandler` use foreign structures as controllers
#[derive(Debug, Clone)]
struct SomeNotification {
pub struct CopyParams<'r> {
request: Option<JrpcRequest>,
data: (String, i32, PhantomData<&'r ()>),
}

// Here is some boilerplate to instantiate a new Notification, and handle the received request
impl SomeNotification {
pub fn new() -> Result<Self, ErrorVariant> {
// This implementation is essentially some boilerplate to hold the data that may be used by the
// future poll
impl<'r> CopyParams<'r> {
// The `new` method will always receive a n-tuple as parameter to store data
//
// It is recommended to use atomic types, or `Arc` protected for heavy data. At every request,
// we `Clone` this struct to send it to the responsible thread
pub fn new(data: (String, i32, PhantomData<&'r ()>)) -> Result<Self, ErrorVariant> {
let request = None;
let some_notification = SomeNotification { request };
let some_notification = CopyParams { request, data };
Ok(some_notification)
}

// The `get_data` will support the future poll with additional information that will not be
// available in the JsonRpc request
pub fn get_data(&self) -> &(String, i32, PhantomData<&'r ()>) {
&self.data
}

// The `get_request` method will return the JsonRpc request to the future poll
pub fn get_request(&self) -> Result<JrpcRequest, ErrorVariant> {
let request = self.request.clone();
request
.map(|r| Ok(r.clone()))
.unwrap_or(Err(ErrorVariant::NoRequestProvided))
}

// This method is of internal usage to receive the request from `JrpcHandler`
pub fn set_request(mut self, request: JrpcRequest) -> Result<Self, ErrorVariant> {
self.request = Some(request);
Ok(self)
}

// This "fork" will be performed every time a new request is received, allowing async
// processing
pub fn clone_with_request(&self, request: JrpcRequest) -> Result<Self, ErrorVariant> {
self.clone().set_request(request)
}
Expand All @@ -69,7 +167,7 @@ impl SomeNotification {
// The main implementation will go here
//
// Tokio provides very good documentation on futures. Check it: https://tokio.rs/
impl Future for SomeNotification {
impl<'r> Future for CopyParams<'r> {
// Optimally, we want to use JrpcResponse, for it is guaranteed to respect the JSON-RPC
// specification. But, we can change the response here to something else, if required.
type Item = Option<JrpcResponse>;
Expand All @@ -79,7 +177,11 @@ impl Future for SomeNotification {
// We fetch the provided request to copy the data
let request = self.get_request()?;

// The params, in this case, will be only a reflection on what was sent
// Here we can receive additional that that's not available in the request
let (_text, _value, _) = self.get_data();

// Do something with the request
// In this example, we are copying the parameters
let params = request.get_params().clone().unwrap_or(JsonValue::Null);

// `generate_response` will receive an enum `JrpcResponseParam` and reply
Expand All @@ -94,106 +196,18 @@ impl Future for SomeNotification {

// The handler will call this trait to spawn a new future and process it when a registered method
// is requested.
impl JrpcMethodTrait for SomeNotification {
impl<'r> JrpcMethodTrait<'r> for CopyParams<'r> {
// `generate_future` can generate any `Future` that respects the trait signature. This can be a
// foreign structure, or just a copy of `self`, in case it implements `Future`. This can also
// be a decision based on the received `JrpcRequest`.
//
// Since its not a reference, there are no restrictions.
fn generate_future<'a>(
fn generate_future(
&self,
request: JrpcRequest,
) -> Result<Box<'a + Future<Item = Option<JrpcResponse>, Error = ErrorVariant>>, ErrorVariant>
) -> Result<Box<'r + Future<Item = Option<JrpcResponse>, Error = ErrorVariant>>, ErrorVariant>
{
Ok(Box::new(self.clone_with_request(request)?))
}
}

// Or, alternitavely, we can use the `generate_method_with_future` macro to only implement the
// `Future`
generate_method_with_future!(InitializeRequest, impl Future for InitializeRequest {
type Item = Option<JrpcResponse>;
type Error = ErrorVariant;

fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> {
let request = self.get_request()?;

let params = request.get_params().clone().unwrap_or(JsonValue::Null);

let message = JrpcResponseParam::generate_result(params)
.and_then(|result| request.generate_response(result))?;

Ok(Async::Ready(Some(message)))
}
});

// Also, we can use the `generate_method_with_data_and_future` macro to only implement the
// `Future`
generate_method_with_data_and_future!(SomeOtherRequest, (String, i32), impl Future for
SomeOtherRequest {
type Item = Option<JrpcResponse>;
type Error = ErrorVariant;

fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> {
let request = self.get_request()?;
let (text, value) = self.get_data();

let params = json!({
"text": text,
"value": value,
});

let message = JrpcResponseParam::generate_result(params)
.and_then(|result| request.generate_response(result))?;

Ok(Async::Ready(Some(message)))
}
});

fn main() {
// `JrpcHanlder` instance is responsible for registering the JSON-RPC methods and receiving the
// requests.
//
// This is full `Arc`/`RwLock` protected. Therefore, it can be freely copied/sent among
// threads.
let handler = JrpcHandler::new().unwrap();

handler
// `register_method` will tie the method signature to an instance, not a generic. This
// means we can freely mutate this instance across different signatures.
.register_method("some/copyParams", SomeNotification::new().unwrap())
.and_then(|h| h.register_method("initialize", InitializeRequest::new().unwrap()))
.and_then(|h| {
// `handle_message` will receive a raw implementation of `ToString` and return the
// associated future. If no future is found, an instance of
// `Err(ErrorVariant::MethodSignatureNotFound(String))` is returned
h.handle_message(
r#"
{
"jsonrpc": "2.0",
"method": "some/copyParams",
"params": [42, 23],
"id": 531
}"#,
)
})
// Just waiting for the poll of future. Check futures documentation.
.and_then(|future| future.wait())
.and_then(|result| {
// The result is an instance of `JrpcResponse`
let result = result.unwrap();
assert_eq!(result.get_jsonrpc(), "2.0");
assert_eq!(
result.get_result(),
&Some(JsonValue::Array(vec![
JsonValue::Number(Number::from(42)),
JsonValue::Number(Number::from(23)),
]))
);
assert!(result.get_error().is_none());
assert_eq!(result.get_id(), &JsonValue::Number(Number::from(531)));
Ok(())
})
.unwrap();
}
```
8 changes: 4 additions & 4 deletions src/handler.rs
Expand Up @@ -4,7 +4,7 @@ use std::collections::HashMap;
use std::sync::{Arc, RwLock};

pub struct JrpcHandler<'a> {
hm_methods: Arc<RwLock<HashMap<String, Box<dyn JrpcMethodTrait + 'a>>>>,
hm_methods: Arc<RwLock<HashMap<String, Box<dyn JrpcMethodTrait<'a> + 'a>>>>,
}

impl<'a> JrpcHandler<'a> {
Expand All @@ -14,7 +14,7 @@ impl<'a> JrpcHandler<'a> {
Ok(handler)
}

pub fn register_method<T: ToString, F: JrpcMethodTrait + 'a>(
pub fn register_method<T: ToString, F: JrpcMethodTrait<'a> + 'a>(
&self,
signature: T,
jrpc_method: F,
Expand All @@ -37,10 +37,10 @@ impl<'a> JrpcHandler<'a> {
Ok(self)
}

pub fn handle_message<'m, T: ToString>(
pub fn handle_message<T: ToString>(
&self,
message: T,
) -> Result<Box<'m + Future<Item = Option<JrpcResponse>, Error = ErrorVariant>>, ErrorVariant>
) -> Result<Box<'a + Future<Item = Option<JrpcResponse>, Error = ErrorVariant>>, ErrorVariant>
{
let message = message.to_string();
let log_message = format!("Message {}", &message);
Expand Down

0 comments on commit 6f15176

Please sign in to comment.