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
# Functions
## Callback functions
@impl Generic
@spec handle_method(
%Generic.RPC{halted: true} |
%Generic.RPC{
halted: false,
request: %{
decoded: %Request{
method: binary,
params: map
}
}
},
Calcinator.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
}
}
},
calcinator
) do
put_rendered_or_error(
rpc,
Calcinator.create(calcinator, params)
)
end
### delete
def handle_method(
rpc = %Generic.RPC{
request: %{
decoded: %Request{
method: "destroy",
params: params
}
}
},
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 ->
put_calcinator_error(rpc, error)
end
end
### index
def handle_method(
rpc = %Generic.RPC{
request: %{
decoded: %Request{
method: "index",
params: params
}
}
},
calcinator
) do
put_rendered_or_error(
rpc,
Calcinator.index(calcinator, params, %{base_uri: %URI{}})
)
end
### show
def handle_method(
rpc = %Generic.RPC{
request: %{
decoded: %Request{
method: "show",
params: params
}
}
},
calcinator
) do
put_rendered_or_error(
rpc,
Calcinator.show(calcinator, params)
)
end
### update
def handle_method(
rpc = %Generic.RPC{
request: %{
decoded: %Request{
method: "update",
params: params
}
}
},
calcinator
) do
put_rendered_or_error(
rpc,
Calcinator.update(calcinator, params)
)
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
"""
@impl Generic
@spec init(Generic.t, Calcinator.t) :: {:ok, Calcinator.t}
def init(%Generic{}, state = %Calcinator{}), do: {:ok, state}
## Private Functions
defp put_error_changeset(rpc, changeset), do: Generic.RPC.halt(rpc, &Response.error(&1, changeset))
defp put_error_document(rpc, error_document), do: Generic.RPC.halt(rpc, &Response.error(&1, error_document))
defp put_error_reason(rpc, reason), do: put_error_document(rpc, Calcinator.Alembic.Document.error_reason(reason))
defp forbidden(rpc), do: put_error_document(rpc, Calcinator.Alembic.Document.forbidden())
defp gateway_timeout(rpc), do: put_error_document(rpc, Calcinator.Alembic.Document.gateway_timeout())
defp ownership_error(rpc), do: put_error_document(rpc, Calcinator.Alembic.Document.ownership_error())
defp put_calcinator_error(rpc, {:error, reason}) do
case reason do
{:not_found, _parameter} ->
Generic.RPC.halt rpc, &Response.error(&1, "Invalid params")
:ownership ->
ownership_error(rpc)
:sandbox_access_disallowed ->
sandbox_access_disallowed(rpc)
:sandbox_token_missing ->
sandbox_token_missing(rpc)
:timeout ->
gateway_timeout(rpc)
:unauthorized ->
forbidden(rpc)
document = %Document{} ->
put_error_document(rpc, document)
changeset = %Ecto.Changeset{} ->
put_error_changeset(rpc, changeset)
_ ->
put_error_reason(rpc, reason)
end
end
defp put_rendered_or_error(rpc = %Generic.RPC{}, rendered_or_error) do
case rendered_or_error do
{:ok, rendered} ->
put_in(rpc.response.decoded.result, rendered)
error ->
put_calcinator_error(rpc, error)
end
end
defp sandbox_access_disallowed(rpc) do
put_error_document(rpc, Calcinator.Alembic.Document.sandbox_access_disallowed())
end
defp sandbox_token_missing(rpc), do: put_error_document(rpc, Calcinator.Alembic.Document.sandbox_token_missing())
end