Current section
Files
Jump to
Current section
Files
lib/retort/response.ex
defmodule Retort.Response do
@moduledoc """
A JSON-RPC [Response object](http://www.jsonrpc.org/specification#response_object)
"""
alias Alembic.Document
alias Retort.Response.Error
defstruct error: nil,
jsonrpc: "2.0",
id: nil,
result: nil
@type t :: %__MODULE__{
error: Error.t | nil,
jsonrpc: String.t,
id: integer | String.t | nil,
result: String.t | false | true | integer | float | [...] | %{} | nil
}
@doc """
Generates error response with the given message and
[corresponding code](http://www.jsonrpc.org/specification#error_object)
"""
@spec error(String.t) :: t
def error(message) when is_binary(message) do
%__MODULE__{
error: Error.predefined(message)
}
end
@spec error(t, Ecto.Changeset.t | Document.t | String.t) :: t
def error(response = %__MODULE__{}, message) when is_binary(message) do
%__MODULE__{response | error: Error.predefined(message)}
end
def error(response = %__MODULE__{}, document = %Document{}) do
%__MODULE__{response | error: Error.from_document(document)}
end
def error(response = %__MODULE__{}, changeset = %Ecto.Changeset{}) do
%__MODULE__{response | error: Error.from_changeset(changeset)}
end
@doc """
Converts JSON decoded map with `String.t` keys to `t`.
"""
@spec from_json(%{required(binary) => any}) :: t | no_return
def from_json(%{"error" => _, "result" => _}) do
raise "JSON-RPC 2.0 says error and result are mutually-exclusive."
end
def from_json(%{"error" => json_error, "id" => id, "jsonrpc" => jsonrpc}) do
%__MODULE__{
error: Error.from_json(json_error),
id: id,
jsonrpc: jsonrpc
}
end
def from_json(%{"id" => id, "jsonrpc" => jsonrpc, "result" => result}) do
%__MODULE__{
id: id,
jsonrpc: jsonrpc,
result: result
}
end
@doc """
Decodes a JSON-encoded string back to a `t`.
# Decode a successful response
iex> Retort.Response.from_string(~S|{"id": 1, "jsonrpc": "2.0", "result": true}|)
%Retort.Response{error: nil, id: 1, jsonrpc: "2.0", result: true}
# Decode an error response with data
iex> Retort.Response.from_string(
...> ~S|{"error": {"code": -32602, "data": ["foo"], "message": "Invalid params"}, "id": 2, "jsonrpc": "2.0"}|
...> )
%Retort.Response{
error: %Retort.Response.Error{code: -32602, data: ["foo"], message: "Invalid params"},
id: 2,
jsonrpc: "2.0",
result: nil
}
# Decode an error response without data
iex> Retort.Response.from_string(
...> ~S|{"error": {"code": -32600, "message": "Invalid Request"}, "id": 3, "jsonrpc": "2.0"}|
...> )
%Retort.Response{
error: %Retort.Response.Error{code: -32600, message: "Invalid Request"},
id: 3,
jsonrpc: "2.0",
result: nil
}
"""
@spec from_string(binary) :: t | no_return
def from_string(string) when is_binary(string) do
string
|> Poison.decode!
|> from_json
end
defimpl Poison.Encoder do
@doc """
## Without `error`
Strip out the `:error` field when `nil` as JSON-RPC 2.0 says,
"This member MUST NOT exist if there was no error triggered during invocation."
## With `error`
Strip out the `:result` field when `:error` is given as JSON-RPC 2.0 says,
"This member MUST NOT exist if there was an error invoking the method."
"""
@spec encode(Retort.Response.t, any) :: iodata
def encode(%@for{error: nil, id: id, jsonrpc: jsonrpc, result: result}, options) do
%{id: id, jsonrpc: jsonrpc, result: result}
|> Poison.Encoder.Map.encode(options)
end
def encode(
%@for{
error: response_error = %Retort.Response.Error{},
id: id,
jsonrpc: jsonrpc,
result: nil
},
options
) do
%{error: response_error, id: id, jsonrpc: jsonrpc}
|> Poison.Encoder.Map.encode(options)
end
end
end