JSONRPC2Plug (jsonrpc2_plug v0.1.0)

JSON-RPC 2.0 plug

JSONRPC2Plug is an Elixir library for a JSON-RPC 2.0 server. Can be used as the plug middleware or as a standalone transport-agnostic server handler.

installation

Installation

The package can be installed by adding jsonrpc2_plug to your list of dependencies in mix.exs:

def deps do
  [
    {:jsonrpc2_plug, "~> 0.1.0"}
  ]
end

usage

Usage

defining-services

Defining Services

Services should use JSONRPC2Plug.Service, which allows describing methods of service. Each method is a module which use JSONRPC2Plug.Method.

Examples:

defmodule CalculatorService do
  use JSONRPC2Plug.Service

  method "add", AddMethod
  method "subtract", SubtractMethod
  method "multiply", MultiplyMethod
  method "divide", DivideMethod
end

defmodule AddMethod do
  use JSONRPC2Plug.Method
end
# and so on...

defining-methods

Defining Methods

There are two possible ways to execute a request: call and cast. The first assumes the response which the service will return, the second does not. The module should implement at least one handle_call or handle_cast callback function to handle requests.

defmodule AddMethod do
  use JSONRPC2Plug.Method

  # It handles requests like this:
  # {"id": "123", "method": "add", "params": {"x": 10, "y": 20}, "jsonrpc": "2.0"}
  def handle_call(%{"x" = > x, "y" => y}, _conn) do
    {:ok, x + y}
  end
end

The first argument is the "params" data comes from request JSON. According to JSONRPC2 spec, it must be either object or an array of arguments.

The second argument is the Plug.Conn struct. Sometimes it could be useful to access the Plug connection.

The module implements behaviour JSONRPC2Plug.Method which consists of five callbacks: handle_call, handle_cast, validate, handle_error and, handle_exception.

handle_call and handle_cast

TODO: Add description

validate

This function is for the validation of the input dataset.

import JSONRPC2Plug.Validator, only: [type: 1, required: 0]

def validate(params) do
  params
  |> Validator.validate("x", [type(:integer), required()])
  |> Validator.validate("y", [type(:integer), required()])
  |> Validator.unwrap()
end

The library has its own validator. It has 8 built-in validations: type, required, not_empty, exclude, include, len, number and format. However, you can write custom validations and extend existing ones.

Moreover, you can use any preferred validator (eg. valdi), but you should respect the following requirements: the validate function should return either {:ok, params} or {:invalid, errors}. Where errors could be any type that can be safely encoded to JSON and params is params to pass into handle_call or handle_cast functions.

handle_error and handle_exception

TODO: Add description

add-as-a-plug-to-the-router

Add as a plug to the router

TODO: Add description

using-as-standalone-module

Using as standalone module

TODO: Add description

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/jsonrpc2_plug.

Link to this section Summary

Functions

HTTP entry point to JSONRPC 2.0 services. It's usual plug and accepts service handler module as a param.

Send an error encoded according to JSONRPC 2.0 spec. It can be useful for global error handler in the router.

Link to this section Functions

Link to this function

call(conn, handler)

Specs

HTTP entry point to JSONRPC 2.0 services. It's usual plug and accepts service handler module as a param.

Example:

use Plug.Router

forward "/jsonrpc", to: JSONRPC2Plug, init_opts: CalculatorService
Link to this function

send_error(conn, kind, ex)

Specs

send_error(Plug.Conn.t(), atom(), struct()) :: Plug.Conn.t()

Send an error encoded according to JSONRPC 2.0 spec. It can be useful for global error handler in the router.

Example:

forward "/jsonrpc", to: JSONRPC2Plug, init_opts: CalculatorService

@impl Plug.ErrorHandler
def handle_errors(conn, %{kind: kind, reason: reason, stack: stacktrace}) do
  Logger.error(Exception.format(kind, reason, stacktrace))

  case conn do
    %{request_path: "/jsonrpc"} ->
      JSONRPC2Plug.send_error(conn, kind, reason)

    _ ->
      send_resp(conn, 500, "Someting went wrong")
  end
end