defmodule Warlock.Handler do
alias Warlock.Handler
alias Warlock.ModuleUtils, as: Utils
alias Plug.Conn
@callback get(conn :: Conn.t()) :: Conn.t() | no_return
@callback new(conn :: Conn.t()) :: Conn.t() | no_return
@callback show(conn :: Conn.t(), id :: String.t()) :: Conn.t() | no_return
@callback edit(conn :: Conn.t(), id :: String.t()) :: Conn.t() | no_return
@callback delete(conn :: Conn.t(), id :: String.t()) :: Conn.t() | no_return
@optional_callbacks get: 1, new: 1, show: 2, edit: 2, delete: 2
def get_user_kind(conn, auth_kind) do
if auth_kind == :anyone, do: :anyone, else: conn.private[auth_kind]
end
defmacro new_item(controller, auth_kind) do
quote do
@impl true
def new(conn) do
user = Handler.get_user_kind(conn, unquote(auth_kind))
case unquote(controller).new(conn.body_params, user) do
{:ok, item} ->
unquote(__CALLER__.module).send_201(conn, item)
{:error, :forbidden} ->
unquote(__CALLER__.module).send_403(conn)
{:error, %Ecto.Changeset{} = changeset} ->
unquote(__CALLER__.module).send_422(conn, changeset,
class: "input-error"
)
{:error, _error} ->
unquote(__CALLER__.module).send_500(conn)
end
end
end
end
defmacro get_items(controller, auth_kind) do
quote do
@impl true
def get(conn) do
user = Handler.get_user_kind(conn, unquote(auth_kind))
count = unquote(controller).get_count(conn.query_params, user)
items = unquote(controller).get(conn.query_params, user)
send_200(conn, items, count)
end
end
end
defmacro show_item(controller, auth_kind) do
quote do
@impl true
def show(conn, id) do
user = Handler.get_user_kind(conn, unquote(auth_kind))
case unquote(controller).show(id, user) do
[item] -> unquote(__CALLER__.module).send_200(conn, item)
[] -> unquote(__CALLER__.module).send_404(conn)
end
end
end
end
defmacro edit_item(controller, auth_kind) do
quote do
@impl true
def edit(conn, id) do
user = Handler.get_user_kind(conn, unquote(auth_kind))
case unquote(controller).edit(conn.body_params, id, user) do
{:ok, item} ->
unquote(__CALLER__.module).send_200(conn, item)
{:error, %Ecto.Changeset{} = changeset} ->
unquote(__CALLER__.module).send_422(conn, changeset,
class: "input-error"
)
{:error, :forbidden} ->
unquote(__CALLER__.module).send_403(conn)
{:error, :not_found} ->
unquote(__CALLER__.module).send_404(conn)
{:error, _} ->
unquote(__CALLER__.module).send_500(conn)
end
end
end
end
defmacro delete_item(controller, auth_kind) do
quote do
@impl true
def delete(conn, id) do
user = Handler.get_user_kind(conn, unquote(auth_kind))
case unquote(controller).delete(id, user) do
{0, nil} -> unquote(__CALLER__.module).send_404(conn)
{_, nil} -> unquote(__CALLER__.module).send_204(conn)
{:error, :not_found} -> unquote(__CALLER__.module).send_404(conn)
{:error, _} -> unquote(__CALLER__.module).send_500(conn)
end
end
end
end
defmacro __using__(opts \\ []) do
quote do
name = unquote(Utils.name_or_option(__CALLER__.module, opts[:name]))
alias Plug.Conn
alias unquote(Utils.replace_at(__CALLER__.module, "Controllers"))
import Warlock.Handler
import Warlock.Handler.Builder
@behaviour Warlock.Handler
@controller unquote(Utils.replace_at(__CALLER__.module, "Controllers"))
@auth_challenge Application.compile_env(name, :auth_challenge, "bearer")
@auth_error Application.compile_env(
name,
:auth_error,
"invalid_credentials"
)
@auth_realm Application.compile_env(name, :auth_realm, name)
@response_type unquote(opts[:response_type]) ||
Application.compile_env(name, :response_type, :json)
@messages Application.compile_env(name, :default_messages, [])
@accepted Keyword.get(@messages, :accepted, "accepted")
@bad_request Keyword.get(@messages, :bad_request, "bad request")
@unauthorized Keyword.get(@messages, :unauthorized, "unauthorized")
@forbidden Keyword.get(@messages, :forbidden, "forbidden")
@not_found Keyword.get(@messages, :not_found, "not found")
@not_allowed Keyword.get(@messages, :not_allowed, "not allowed")
@conflict Keyword.get(@messages, :conflict, "conflict")
@gone Keyword.get(@messages, :gone, "gone")
@unsupported Keyword.get(@messages, :unsupported, "unsupported")
@unprocessable Keyword.get(@messages, :unprocessable, "unprocessable")
@too_many Keyword.get(@messages, :too_many, "too many requests")
@server_error Keyword.get(@messages, :internal_error, "unknown")
@not_implemented Keyword.get(
@messages,
:not_implemented,
"not implemented"
)
payload(200, @response_type)
payload(201, @response_type)
payload(202, @response_type)
error(400, @bad_request, @response_type)
error(403, @forbidden, @response_type)
error(404, @not_found, @response_type)
error(405, @not_allowed, @response_type)
error(409, @conflict, @response_type)
error(410, @gone, @response_type)
error(415, @unsupported, @response_type)
error(422, @unprocessable, @response_type)
error(429, @too_many, @response_type)
error(500, @server_error, @response_type)
error(501, @not_implemented, @response_type)
if @response_type == :siren do
build_siren_handler()
else
build_json_handler()
end
def authenticate(conn) do
Conn.put_resp_header(
conn,
"www-authenticate",
"#{@auth_challenge} realm=\"#{@auth_realm}\", error=\"#{@auth_error}\""
)
end
def send_204(conn), do: Conn.send_resp(conn, 204, [])
end
end
end