Packages

An Elixir `plug` library for extending an HTTP server with JSONRPC 2.0 protocol services. It's HTTP transport level. For use both in the Phoenix application and pure `plug`-compatable server.

Current section

Files

Jump to
jsonrpc2_plug lib jsonrpc2_plug.ex
Raw

lib/jsonrpc2_plug.ex

defmodule JSONRPC2Plug do
@moduledoc "README.md" |> File.read!()
@behaviour Plug
require Logger
@impl true
@spec init(module()) :: module()
def init(handler),
do: handler
@doc """
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
"""
@impl true
@spec call(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()
def call(%{method: "POST", body_params: %Plug.Conn.Unfetched{}}, _handler),
do: raise "Plug the JSONRPC2Plug after Plug.Parsers"
@impl true
def call(%{method: "POST", body_params: body_params} = conn, handler) do
Logger.debug("JSONRPC2 Request: #{inspect(body_params)}")
response = execute_request(handler, body_params, conn)
Logger.debug("JSONRPC2 Response: #{response}")
conn
|> Plug.Conn.put_resp_header("content-type", "application/json")
|> Plug.Conn.resp(200, response)
end
@impl true
def call(conn, _),
do: Plug.Conn.resp(conn, 404, "")
@doc """
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
"""
@spec send_error(Plug.Conn.t(), atom(), struct()) :: Plug.Conn.t()
def send_error(conn, :error, %Plug.Parsers.ParseError{} = ex),
do: send_error_response(conn, :parse_error, Exception.message(ex))
def send_error(conn, kind, reason) do
details =
case kind do
:error -> Exception.message(reason)
any -> inspect(any)
end
send_error_response(conn, :internal_error, details)
end
defp send_error_response(conn, code, details) do
conn
|> Plug.Conn.put_resp_content_type("application/json")
|> Plug.Conn.send_resp(500, error(code, details))
end
defp error(code, details),
do: JSONRPC2Plug.Error.new(nil, code, details) |> Poison.encode!()
defp execute_request(handler, body_params, conn) do
case handler.handle(body_params, conn) do
[] -> ""
nil -> ""
resp -> Poison.encode!(resp)
end
end
end