Current section
Files
Jump to
Current section
Files
lib/retort/server/generic/resources.ex
defmodule Retort.Server.Generic.Resources do
@moduledoc """
Exposes an `Ecto.Schema.t` as a JSONAPI resource over JSON RPC on a RabbitMQ queue.
Any code with side-effects, such as `Logger` or `Repo` calls should stay in this module. Any pure functions that
write back to the `Retort.Server.Generic.RPC.t` can be in `Retort.Server.Generic.RPC`.
"""
alias Alembic.Document
alias Retort.{Request, Response, Server.Generic}
require Ecto.Query
require Logger
# Behaviors
@behaviour Generic
# Struct
defstruct ~w(calcinator
changeset_render)a
# Types
@typedoc """
* `calcinator` - the state of `Calcinator`
* `changeset_render` - render changeset that has errors
"""
@type t :: %__MODULE__{
calcinator: Calcinator.t,
changeset_render: (Ecto.Changeset.t -> map)
}
# Functions
## Callback functions
@spec handle_method(%Generic.RPC{halted: true} |
%Generic.RPC{halted: false, request: %{decoded: %Request{method: binary, params: map}}},
t) :: Generic.RPC.t
def handle_method(rpc = %Generic.RPC{halted: true}, _), do: rpc
### create
def handle_method(
rpc = %Generic.RPC{request: %{decoded: %Request{method: "create", params: params}}},
%__MODULE__{
calcinator: calcinator,
changeset_render: changeset_render
}
) do
case Calcinator.create(calcinator, params) do
{:ok, rendered} ->
put_in rpc.response.decoded.result, rendered
{:error, :ownership} ->
ownership_error(rpc)
{:error, changeset = %Ecto.Changeset{}} ->
Generic.RPC.halt(rpc, &Response.error(&1, changeset, changeset_render))
{:error, document = %Document{}} ->
Generic.RPC.halt rpc, &Response.error(&1, document)
end
end
### delete
def handle_method(
rpc = %Generic.RPC{request: %{decoded: %Request{method: "destroy", params: params}}},
%__MODULE__{
calcinator: calcinator
}
) do
case Calcinator.delete(calcinator, params) do
:ok ->
# See http://jsonapi.org/format/#crud-deleting
# "A server MUST return a 200 OK status code if a deletion request is successful and the server responds
# with only top-level meta data." But, we have no meta, so just ship blank `%{}` since we can't send `nil`
# for `:result` in JSON RPC. JSON API shows it's HTTP-centric nature here unforunately.
put_in rpc.response.decoded.result, %{}
{:error, {:not_found, _parameter}} ->
Generic.RPC.halt rpc, &Response.error(&1, "Invalid params")
{:error, :ownership} ->
ownership_error(rpc)
end
end
### index
def handle_method(
rpc = %Generic.RPC{
request: %{
decoded: %Request{
method: "index",
params: params
}
}
},
%__MODULE__{
calcinator: calcinator,
}
) do
case Calcinator.index(calcinator, params, %{base_uri: %URI{}}) do
{:ok, rendered} ->
put_in rpc.response.decoded.result, rendered
{:error, document = %Document{}} ->
Generic.RPC.halt rpc, &Response.error(&1, document)
{:error, :ownership} ->
ownership_error(rpc)
end
end
### show
def handle_method(
rpc = %Generic.RPC{
request: %{
decoded: %Request{
method: "show",
params: params
}
}
},
%__MODULE__{
calcinator: calcinator,
}
) do
case Calcinator.show(calcinator, params) do
{:ok, rendered} ->
put_in rpc.response.decoded.result, rendered
{:error, {:not_found, _parameter}} ->
Generic.RPC.halt rpc, &Response.error(&1, "Invalid params")
{:error, :ownership} ->
ownership_error(rpc)
{:error, document = %Document{}} ->
Generic.RPC.halt rpc, &Response.error(&1, document)
end
end
### update
def handle_method(
rpc = %Generic.RPC{
request: %{
decoded: %Request{
method: "update",
params: params
}
}
},
%__MODULE__{
calcinator: calcinator,
changeset_render: changeset_render
}
) do
case Calcinator.update(calcinator, params) do
{:ok, rendered} ->
put_in rpc.response.decoded.result, rendered
{:error, {:not_found, _parameter}} ->
Generic.RPC.halt rpc, &Response.error(&1, "Invalid params")
{:error, :ownership} ->
ownership_error(rpc)
{:error, changeset = %Ecto.Changeset{}} ->
Generic.RPC.halt(rpc, &Response.error(&1, changeset, changeset_render))
{:error, document = %Document{}} ->
Generic.RPC.halt rpc, &Response.error(&1, document)
end
end
### invalid params
def handle_method(rpc = %Generic.RPC{request: %{decoded: %Request{method: method}}}, _)
when method in ~w(create destroy show update) do
Generic.RPC.halt(rpc, &Response.error(&1, "Invalid params"))
end
### method not found
def handle_method(rpc = %Generic.RPC{request: %{decoded: %Request{method: method}}}, _) do
Generic.RPC.halt rpc, fn decoded ->
%Response{
decoded | error: %Response.Error{Response.Error.predefined("Method not found") | data: %{"method" => method}}
}
end
end
@doc """
Sets connection, channel, and queue to Rabbit
"""
@spec init(Generic.t, t) :: {:ok, t}
def init(
%Generic{},
state = %__MODULE__{
calcinator: %Calcinator{},
changeset_render: changeset_render
}
) when is_function(changeset_render, 1) do
{:ok, state}
end
## Private Functions
defp ownership_error(rpc) do
Generic.RPC.halt rpc,
&Response.error(
&1,
%Document{
errors: [
%Alembic.Error{
code: 422,
detail: "DBConnection ownership is incorrect. See log for details.",
source: "/meta/beam"
}
]
}
)
end
end