JSONRPC2.Service

Provides the ability to create services in accordance with the JSONRPC 2.0 specification. There is no transport layer, but only tools for creating transport-independent JSONRPC 2.0 services.

Installation

If available in Hex, the package can be installed by adding jsonrpc2_service to your list of dependencies in mix.exs:

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

How to use

Defining Services

Services should use JSONRPC2.Service, which allows to describe methods of service. Each method is a module which uses JSONRPC2.Service.Method.

Examples:

defmodule CalculatorService do
  use JSONRPC2.Service

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

defmodule AddMethod do
  use JSONRPC2.Service.Method
end
# and so on...

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 JSONRPC 2.0 request. According to JSONRPC 2.0 spec, it must be either object or an array of arguments.

The second argument is the map with context data. Sometimes it could be useful to send some additional data into the service, eg. HTTP connection data (Plug.Conn)

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

handle_call and handle_cast callbacks

This callbacks should return {:ok, term()}, {:jsonrpc2_error, term()} or {:error, term()} tuples. There are two helpers for errors: error and error! functions.

use error function to return error:

def handle_call([a, b], _) do
  if b != 0 do
    {:ok, a / b}
  else
    error(12345, "divided by zero")
  end
end

use error! function to abort function execution and return error:

def handle_call([a, b], _) do
  if(b == 0, do: error!(12345, "divided by zero"))

  {:ok, a / b}
end

validate callback

This function is for the validation of the input dataset.

import JSONRPC2.Service.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 parameters to pass into handle_call or handle_cast functions.

handle_error and handle_exception callbacks

This callbacks are used to define way how to deal with errors and exceptions. The method handler catches all errors, throws, exits and exceptions and invoke error or exception handler.

def handle_error(request, {:exit, reason}, stacktrace) do
  ...
  error(...)
end

def handle_error(request, {:throw, reason}, stacktrace) do
  ...
  error(...)
end

def handle_exception(request, %ArithmeticError{} = ex, stacktrace) do
  ...
  error(...)
end

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