Current section
Files
Jump to
Current section
Files
lib/calcinator/controller/error.ex
if Code.ensure_loaded?(Phoenix.Controller) do
defmodule Calcinator.Controller.Error do
@moduledoc """
Errors returned by `Calcinator.Controller`. Public, so that other controllers not using `Calcinator.Controller` can
have same format for errors.
"""
alias Calcinator.Alembic.Document
alias JaSerializer.Formatter.Utils
alias Plug.Conn
require Logger
import Conn, only: [halt: 1, put_resp_content_type: 2, put_status: 2, send_resp: 3]
import Phoenix.Controller, only: [json: 2]
@doc """
Retort returned a 500 JSONAPI error inside a 422 JSONRPC error.
"""
@spec bad_gateway(Conn.t) :: Conn.t
def bad_gateway(conn), do: render_json(conn, Document.bad_gateway(), :bad_gateway)
@doc """
The resource was deleted
"""
@spec deleted(Conn.t) :: Conn.t
def deleted(conn) do
conn
|> put_resp_content_type()
|> send_resp(:no_content, "")
end
@doc """
The current resource or action is forbidden to the authenticated user
"""
@spec forbidden(Conn.t) :: Conn.t
def forbidden(conn), do: render_json(conn, Document.forbidden(), :forbidden)
@doc """
Puts 504 Gateway Timeout JSONAPI error in `conn`.
"""
@spec gateway_timeout(Conn.t) :: Conn.t
def gateway_timeout(conn), do: render_json(conn, Document.gateway_timeout(), :gateway_timeout)
@doc """
Puts 404 Resource Not Found JSONAPI error in `conn` with `parameter` as the source parameter.
"""
@spec not_found(Conn.t, String.t) :: Conn.t
def not_found(conn, parameter), do: render_json(conn, Document.not_found(parameter), :not_found)
@doc """
Puts 500 Internal Server Error JSONAPI error in `conn` with title `"Ownership Error"`.
"""
@spec ownership_error(Conn.t) :: Conn.t
def ownership_error(conn) do
render_json conn,
Document.ownership_error(),
# DBConnection.OwnershipError raised when the connection was checked out from the pool too long and
# the lease was # revoked. This could be a 504 Gateway Timeout, but that pool is inside Elixir and
# not part of the Database # itself, so keeping as 500 Internal Server Error. 504 Gateway Timeout is
# also not accurate, because the # "gateway" is responding, it's just saying you can't do it.
#
# See 5XX section of http://racksburg.com/choosing-an-http-status-code/
:internal_server_error
end
@doc """
Converts an `{:error, _}` tuple from `Calcinator` into a JSONAPI document and encodes it as the `conn` response.
"""
def put_calcinator_error(conn, {:error, :bad_gateway}), do: bad_gateway(conn)
def put_calcinator_error(conn, {:error, {:not_found, parameter}}), do: not_found(conn, parameter)
def put_calcinator_error(conn, {:error, :ownership}), do: ownership_error(conn)
def put_calcinator_error(conn, {:error, :sandbox_access_disallowed}), do: sandbox_access_disallowed(conn)
def put_calcinator_error(conn, {:error, :sandbox_token_missing}), do: sandbox_token_missing(conn)
def put_calcinator_error(conn, {:error, :timeout}), do: gateway_timeout(conn)
def put_calcinator_error(conn, {:error, :unauthorized}), do: forbidden(conn)
def put_calcinator_error(conn, {:error, document = %Alembic.Document{}}) do
render_json(conn, document, :unprocessable_entity)
end
def put_calcinator_error(conn, {:error, changeset = %Ecto.Changeset{}}) do
render_changeset_error(conn, changeset)
end
def put_calcinator_error(conn, {:error, reason}), do: render_error_reason(conn, reason)
@doc """
Puts JSONAPI Content Type in the Response of the `conn`
"""
def put_resp_content_type(conn), do: put_resp_content_type(conn, "application/vnd.api+json")
@doc """
Converts an error `reason` from that isn't a standard format (such as those from the backing store) to a
500 Internal Server Error JSONAPI error, but with `id` set to `id` that is also used in `Logger.error`, so that
`reason`, which should remain private to limit implementation disclosures that could lead to security issues.
## Log Messages
```
id=UUIDv4 reason=inspect(reason)
```
## Returns
* `Plug.Conn.t` - The `Plug.Conn.t` is halted with `Plug.Conn.halt/1`
"""
@spec render_error_reason(Conn.t, reason :: term) :: Conn.t
def render_error_reason(conn, reason) do
render_json(conn, Document.error_reason(reason), :internal_server_error)
end
@doc """
Renders `changeset` as an error object using the `Alembic.Document.from_ecto_changeset/1`.
"""
@spec render_changeset_error(Conn.t, Ecto.Changeset.t) :: Conn.t
def render_changeset_error(conn, changeset) do
conn
|> put_status(:unprocessable_entity)
|> put_resp_content_type()
|> json(Alembic.Document.from_ecto_changeset(changeset, %{format_key: &Utils.format_key/1}))
|> halt()
end
@doc """
Renders `encodable` as JSON after `put_jsonapi_and_status` on the `conn`.
"""
@spec render_json(Conn.t, term, atom) :: Conn.t
def render_json(conn, encodable, status) do
conn
|> put_status(status)
|> put_resp_content_type()
|> json(encodable)
|> halt()
end
@doc """
Puts 422 Unprocessable Entity JSONAPI error in `conn` with title `"Sandbox Access Disallowed"`.
"""
@spec sandbox_access_disallowed(Conn.t) :: Conn.t
def sandbox_access_disallowed(conn) do
render_json conn, Document.sandbox_access_disallowed(), :unprocessable_entity
end
@doc """
Puts 422 Unrpcessable Entity JSONAPI error in `conn` with title `"Child missing"`.
"""
@spec sandbox_token_missing(Conn.t) :: Conn.t
def sandbox_token_missing(conn) do
render_json conn, Document.sandbox_token_missing(), :unprocessable_entity
end
end
end