Current section

Files

Jump to
json_rpc_toolkit lib json_rpc plug.ex
Raw

lib/json_rpc/plug.ex

if Code.ensure_loaded?(Plug.Conn) do
defmodule JSONRPC.Plug do
import Plug.Conn
alias JSONRPC.{Request, Response, Error, Parser, Serializer}
@type options :: [mount: String.t(), registry: module()]
@spec init(keyword()) :: options()
def init(options) do
mount =
options
|> Keyword.get(:mount, "/rpc")
|> String.split("/", trim: true)
registry = Keyword.get(options, :registry)
[mount: mount, registry: registry]
end
@spec call(Plug.Conn.t(), options()) :: Plug.Conn.t()
def call(%{method: "POST", path_info: mount} = conn, mount: mount, registry: registry) do
{body, conn} = get_body(conn)
body
|> Parser.parse()
|> case do
requests when is_list(requests) ->
Enum.map(requests, fn request ->
Request.to_response(apply(registry, :call, [request, [conn: conn]]))
end)
|> Serializer.serialize()
|> send_response(conn)
%Request{} = request ->
registry
|> apply(:call, [request, [conn: conn]])
|> Request.to_response()
|> send_response(conn)
%Error{} = error ->
%Response{error: error}
|> Serializer.serialize()
|> send_error(conn)
end
end
def call(conn, _opts), do: conn
defp send_response(%{error: nil} = response, conn) do
response = Serializer.serialize(response)
conn
|> put_resp_content_type("application/json")
|> send_resp(200, response)
|> halt()
end
defp send_response(error, conn) do
error = Serializer.serialize(error)
conn
|> put_resp_content_type("application/json")
|> send_resp(400, error)
|> halt()
end
defp send_error(error, conn) do
conn
|> put_resp_content_type("application/json")
|> send_resp(400, error)
|> halt()
end
defp get_body(conn, partial \\ []) do
case read_body(conn) do
{:ok, body, conn} ->
{IO.iodata_to_binary([partial | body]), conn}
{:more, body, conn} ->
get_body(conn, [partial | body])
{:error, _error} ->
{"", conn}
end
end
end
end