Current section

Files

Jump to
retort lib retort response error.ex
Raw

lib/retort/response/error.ex

defmodule Retort.Response.Error do
@moduledoc """
A JSON-RPC [Response Error object](http://www.jsonrpc.org/specification#error_object)
"""
alias Alembic.{Document, Source}
alias JaSerializer.Formatter.Utils
# Struct
defstruct code: nil,
message: nil,
data: nil
# Types
@type t :: %__MODULE__{
code: integer,
message: String.t(),
data: term
}
# Functions
@doc """
Uses rendered `changset` as data for error.
"""
@spec from_changeset(Ecto.Changeset.t()) :: t
def from_changeset(changeset = %Ecto.Changeset{}) do
"Changeset error"
|> predefined
|> struct(data: Document.from_ecto_changeset(changeset, %{format_key: &Utils.format_key/1}))
end
@doc """
Uses JSON API `document` as data for error. Only use when the `document` is a JSON API errors document.
"""
@spec from_document(Document.t()) :: t
def from_document(document = %Document{errors: errors}) when is_list(errors) do
"JSONAPI error"
|> predefined
|> struct(data: document)
end
@doc """
Converts JSON decoded map with `String.t` keys to `t`.
# From error with data
iex> Retort.Response.Error.from_json(%{"code" => 0, "data" => "Data", "message" => "Zero"})
%Retort.Response.Error{code: 0, data: "Data", message: "Zero"}
# From error without data
iex> Retort.Response.Error.from_json(%{"code" => 0, "message" => "Zero"})
%Retort.Response.Error{code: 0, data: nil, message: "Zero"}
"""
@spec from_json(%{required(binary) => integer, required(binary) => binary, optional(term) => term}) :: t
def from_json(json = %{"code" => code, "message" => message})
when is_integer(code) and is_binary(message) do
%__MODULE__{
code: code,
# data is optional
data: data_from_json(json["data"]),
message: message
}
end
@code_by_message %{
"Changeset error" => 422,
"Internal error" => -32_603,
"Invalid params" => -32_602,
"Invalid Request" => -32_600,
"JSONAPI error" => 422,
"Method not found" => -32_601,
"Parse error" => -32_700
}
@doc """
Looks up [predefined](http://www.jsonrpc.org/specification#error_object) `t`.
"""
@spec predefined(String.t()) :: t
for {message, code} <- @code_by_message do
def predefined(unquote(message)) do
%__MODULE__{
code: unquote(code),
message: unquote(message)
}
end
end
@doc """
Converts `t` to errors returned by `Calcinator` and `Calcinator.Resources` callbacks.
"""
@spec to_calcinator_error(t, maybe_changeset :: Ecto.Changeset.t() | nil) ::
{:error, :bad_gateway}
| {:error, :not_found}
| {:error, :sandbox_access_disallowed}
| {:error, Ecto.Changeset.t()}
def to_calcinator_error(
%__MODULE__{
code: 422,
data: %Document{
errors: errors
}
},
changeset
) do
case {errors, changeset} do
{[%Alembic.Error{code: "500"}], _} ->
{:error, :bad_gateway}
{[%Alembic.Error{status: "403"}], _} ->
{:error, :unauthorized}
{[%Alembic.Error{status: "404"}], _} ->
{:error, :not_found}
{
[
%Alembic.Error{
source: %Alembic.Source{
pointer: "/meta/beam"
},
status: "422",
title: "Sandbox Access Disallowed"
}
],
_
} ->
{:error, :sandbox_access_disallowed}
# length is at least 1 and there is a changeset to put errors into
{[_ | _], %Ecto.Changeset{}} ->
changeset_with_errors = Retort.Response.Error.Ecto.Changeset.add_alembic_errors(changeset, errors)
{:error, changeset_with_errors}
end
end
## Private Functions
defp data_from_json(json) do
case Document.from_json(
json,
%Alembic.Error{
source: %Source{
pointer: ""
}
}
) do
{:ok, document} ->
document
_ ->
json
end
end
end