Futures implementation for JSON-RPC

Overview

futures-jsonrpc

Crate Documentation Travis Status Join the chat at https://gitter.im/futures-jsonrpc/community

Futures + JSON-RPC

A lightweight remote procedure call protocol. It is designed to be simple! And, with futures, even more flexible!

This crate will associate Futures with method signatures via register_method, and parse/handle JSON-RPC messages via handle_message.

It is fully compliant with JSON-RPC 2.0 Specification.

Contributing

The crate is not test covered yet! Any PR with a test coverage will be much appreciated :)

Installation

Add this to your Cargo.toml:

[dependencies]
futures-jsonrpc = "0.2"

Minimal example

use futures_jsonrpc::futures::prelude::*;
use futures_jsonrpc::*;
use serde_json::Number;

// This macro will avoid some boilerplating, leaving only the `Future` implementation to be done
//
// Check for additional information in the detailed explanation below
//
// Also, check `generate_method_with_data_and_future` and `generate_method_with_lifetime_data_and_future`
generate_method!(
    CopyParams,
    impl Future for CopyParams {
        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)))
        }
    }
);

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().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

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

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

// 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 = 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)
    }
}

// `JrpcHandler` will just return a pollable associated future.
//
// The main implementation will go here
//
// Tokio provides very good documentation on futures. Check it: https://tokio.rs/
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>;
    type Error = ErrorVariant;

    fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> {
        // We fetch the provided request to copy the data
        let request = self.get_request()?;

        // 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
        // with either an error or success.
        let message = JrpcResponseParam::generate_result(params)
            .and_then(|result| request.generate_response(result))?;

        // Then, our reply is ready
        Ok(Async::Ready(Some(message)))
    }
}

// The handler will call this trait to spawn a new future and process it when a registered method
// is requested.
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(
        &self,
        request: JrpcRequest,
    ) -> Result<Box<'r + Future<Item = Option<JrpcResponse>, Error = ErrorVariant>>, ErrorVariant>
    {
        Ok(Box::new(self.clone_with_request(request)?))
    }
}
You might also like...
A pure Rust implementation of WebRTC API
A pure Rust implementation of WebRTC API

A pure Rust implementation of WebRTC API

Backroll is a pure Rust implementation of GGPO rollback networking library.

backroll-rs Backroll is a pure Rust implementation of GGPO rollback networking library. Development Status This is still in an untested alpha stage. A

A working demo of RustDesk server implementation

A working demo of RustDesk server implementation This is a super simple working demo implementation with only one relay connection allowed, without NA

axum-server is a hyper server implementation designed to be used with axum framework.

axum-server axum-server is a hyper server implementation designed to be used with axum framework. Features Conveniently bind to any number of addresse

Official Implementation of Findora Network.

Findora Platform Wiki Contribution Guide Licensing The primary license for Platform is the Business Source License 1.1 (BUSL-1.1), see LICENSE. Except

A small holepunching implementation written in Rust (UDP)

rust-udp-holepunch A small holepunching implementation written in Rust (UDP) Prerequisites Your rendezvous server must lay in a network which doesn't

An implementation of the CESS network supported by CESS LAB.
An implementation of the CESS network supported by CESS LAB.

--------- 🌌 ---------An infrastructure of decentralized cloud data network built with Substrate-------- 🌌 -------- ---------------- 🌌 -------------

Rust implementation of PRECIS Framework: Preparation, Enforcement, and Comparison of Internationalized Strings in Application Protocols

Rust PRECIS Framework libray PRECIS Framework: Preparation, Enforcement, and Comparison of Internationalized Strings in Application Protocols as descr

Implementation of algorithms for Domain Name System (DNS) Cookies construction

DNS Cookie RFC7873 left the construction of Server Cookies to the discretion of the DNS Server (implementer) which has resulted in a gallimaufry of di

Comments
  • Add tests

    Add tests

    I omit some tests including rpc call with invalid Request object in spec because it returns "Parse error" instead of "Invalid Request".

    rpc call with invalid Request object

    --> {"jsonrpc": "2.0", "method": 1, "params": "bar"}
    <-- {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
    

    #1

    opened by alfredoyang 0
  • Handler track request/reply

    Handler track request/reply

    Minimal solution

    1. Create a new attribute hm_requests of JrpcHandler as HashMap<String, Implementation of JrpcMethodTrait>

      • Use hm_methods as reference
    2. Create a new method register_request of JrpcHandler that will receive an implementation of ToString and a future, and will register this information to hm_requests

      • Use register_method as reference
      • The identifier of the request, most likely as Uuid, will be the key for the HashMap
    3. Create a new method wait_for_request_reply of JrpcHandler that will receive one implementation of JrpcRequest and a future

      • This method will insert a new track in hm_requests via the created register_request method using the get_id method of JrpcRequest
      • If get_id is None, this is not a valid request, according to the JsonRpc specification. Therefore, we need a new ErrorVariant for that
    4. Change handle_message method of JrpcHandler to check if the parsed message Json contains method attribute.

      1. If yes, then use current logic of fetching the future from hm_methods.
      2. If no, then implement a new logic to fetch the future from hm_requests, and delete the HashMap associated register after the retrieval of the generated future

    Optimal solution

    • Test cases
    • Mechanism to timeout old requests in hm_requests
    enhancement help wanted good first issue 
    opened by vlopes11 6
  • Test coverage

    Test coverage

    Create a basic test coverage, considering the examples in JSON-RPC official specification https://www.jsonrpc.org/specification

    rpc call with positional parameters:

    --> {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
    <-- {"jsonrpc": "2.0", "result": 19, "id": 1}
    
    --> {"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}
    <-- {"jsonrpc": "2.0", "result": -19, "id": 2}
    

    rpc call with named parameters:

    --> {"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}
    <-- {"jsonrpc": "2.0", "result": 19, "id": 3}
    
    --> {"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 4}
    <-- {"jsonrpc": "2.0", "result": 19, "id": 4}
    

    a Notification:

    --> {"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}
    --> {"jsonrpc": "2.0", "method": "foobar"}
    

    rpc call of non-existent method:

    --> {"jsonrpc": "2.0", "method": "foobar", "id": "1"}
    <-- {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"}
    

    rpc call with invalid JSON:

    --> {"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]
    <-- {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}
    

    rpc call with invalid Request object:

    --> {"jsonrpc": "2.0", "method": 1, "params": "bar"}
    <-- {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
    

    rpc call Batch, invalid JSON:

    --> [
      {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
      {"jsonrpc": "2.0", "method"
    ]
    <-- {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}
    

    rpc call with an empty Array:

    --> []
    <-- {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
    

    rpc call with an invalid Batch (but not empty):

    --> [1]
    <-- [
      {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
    ]
    

    rpc call with invalid Batch:

    --> [1,2,3]
    <-- [
      {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
      {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
      {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
    ]
    

    rpc call Batch:

    --> [
            {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
            {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
            {"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
            {"foo": "boo"},
            {"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
            {"jsonrpc": "2.0", "method": "get_data", "id": "9"} 
        ]
    <-- [
            {"jsonrpc": "2.0", "result": 7, "id": "1"},
            {"jsonrpc": "2.0", "result": 19, "id": "2"},
            {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
            {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "5"},
            {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
        ]
    

    rpc call Batch (all notifications):

    --> [
            {"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]},
            {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}
        ]
    <-- //Nothing is returned for all notification batches
    
    help wanted good first issue 
    opened by vlopes11 5
Releases(v0.1.0)
Owner
Victor Lopes
Victor Lopes
Futures-based QUIC implementation in Rust

Pure-rust QUIC protocol implementation Quinn is a pure-rust, future-based implementation of the QUIC transport protocol undergoing standardization by

null 2.6k Jan 8, 2023
The gRPC library for Rust built on C Core library and futures

gRPC-rs gRPC-rs is a Rust wrapper of gRPC Core. gRPC is a high performance, open source universal RPC framework that puts mobile and HTTP/2 first. Sta

TiKV Project 1.6k Jan 7, 2023
Crate extending futures stream combinators, that is adding precise rate limiter

stream-rate-limiter Stream combinator .rate_limiter(opt: RateLimitOptions) Provides way to limit stream element rate with constant intervals. It adds

null 3 Jul 23, 2023
A simple configuration-based module for inter-network RPC in Holochain hApps.

DNA Auth Resolver A simple configuration-based module for inter-network RPC in Holochain hApps. About Usage In the origin zome In the destination DNA

Shadman Baig 0 Feb 4, 2022
Fast Discord RPC Client written in Rust

Discord RPC Client Examples Big image, small image, details and one button discordrpc -c 942151169185316874 -d 'untypeable nickname' --button-1-text '

Oskar 10 Jan 1, 2023
Volo is a high-performance and strong-extensibility Rust RPC framework that helps developers build microservices.

Volo is a high-performance and strong-extensibility Rust RPC framework that helps developers build microservices.

CloudWeGo 1.3k Jan 2, 2023
This Intelligent Transportation Systems (ITS) MQTT client based on the JSon ETSI specification transcription provides a ready to connect project for the mobility

This Intelligent Transportation Systems (ITS) MQTT client based on the JSon ETSI specification transcription provides a ready to connect project for the mobility (connected and autonomous vehicles, road side units, vulnerable road users,...). Let's connect your device or application to our Intelligent Transport Systems (ITS) platform!

Orange 4 Nov 29, 2022
🥧 Savoury implementation of the QUIC transport protocol and HTTP/3

quiche is an implementation of the QUIC transport protocol and HTTP/3 as specified by the IETF. It provides a low level API for processing QUIC packet

Cloudflare 7.1k Jan 8, 2023
neqo — an Implementation of QUIC written in Rust

Neqo, an Implementation of QUIC written in Rust To run test HTTP/3 programs (neqo-client and neqo-server): cargo build ./target/debug/neqo-server [::]

Mozilla 1.6k Jan 7, 2023
The Rust Implementation of libp2p networking stack.

Central repository for work on libp2p This repository is the central place for Rust development of the libp2p spec. Warning: While we are trying our b

libp2p 3k Jan 4, 2023