defmodule Warlock.Handler do
alias Warlock.{Handler, Siren}
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
defmacro json_payload(code) do
quote do
def unquote(:"send_#{code}")(conn, payload) do
Handler.json(conn, unquote(code), payload)
end
end
end
defmacro json_error(code, default_error) do
quote do
def unquote(:"send_#{code}")(conn) do
Handler.message(conn, unquote(code), unquote(default_error))
end
def unquote(:"send_#{code}")(conn, error) do
Handler.json(conn, unquote(code), error)
end
end
end
defmacro siren_payload(code) do
quote do
def unquote(:"send_#{code}")(conn, payload) do
Handler.siren(conn, unquote(code), Siren.encode(conn, payload))
end
def unquote(:"send_#{code}")(conn, payload, count) do
Handler.siren(conn, unquote(code), Siren.encode(conn, payload, count))
end
end
end
defmacro siren_error(code, default_error) do
quote do
def unquote(:"send_#{code}")(conn) do
error =
Siren.error(unquote(code), [],
class: ["error", unquote(default_error)],
summary: unquote(default_error)
)
Handler.siren(conn, unquote(code), error)
end
def unquote(:"send_#{code}")(conn, error) do
siren_error =
Siren.error(unquote(code), error,
class: ["error", unquote(default_error)],
summary: unquote(default_error)
)
Handler.siren(conn, unquote(code), siren_error)
end
def unquote(:"send_#{code}")(conn, error, opts) do
siren_error = Siren.error(unquote(code), error, opts)
Handler.siren(conn, unquote(code), siren_error)
end
end
end
defmacro payload(code, response_type) do
quote do
if unquote(response_type) == :siren do
siren_payload(unquote(code))
else
json_payload(unquote(code))
end
end
end
defmacro error(code, default_error, response_type) do
quote do
if unquote(response_type) == :siren do
siren_error(unquote(code), unquote(default_error))
else
json_error(unquote(code), unquote(default_error))
end
end
end
def json(conn, status, payload) do
conn
|> Conn.put_resp_content_type("application/json")
|> Conn.send_resp(status, Jason.encode!(payload))
end
def siren(conn, status, payload) do
conn
|> Conn.put_resp_content_type("application/vnd.siren+json")
|> Conn.send_resp(status, Jason.encode!(payload))
end
def message(conn, status, message) do
Handler.json(conn, status, %{:message => message})
end
defmacro __using__(opts \\ []) do
quote do
name = unquote(Utils.name_or_option(__CALLER__.module, opts[:name]))
alias Plug.Conn
alias Warlock.Siren
alias unquote(Utils.replace_at(__CALLER__.module, "Controllers"))
import Warlock.Handler
@behaviour Warlock.Handler
@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
def send_202(conn) do
Handler.siren(conn, 202, Siren.message(202, @accepted))
end
def send_401(conn) do
error =
Siren.error(401, [],
class: ["error", @unauthorized],
summary: @unauthorized
)
conn
|> unquote(__CALLER__.module).authenticate()
|> Handler.siren(401, error)
end
def send_401(conn, custom_error) do
error =
Siren.error(401, custom_error,
class: ["error", @unauthorized],
summary: @unauthorized
)
conn
|> unquote(__CALLER__.module).authenticate()
|> Handler.siren(401, error)
end
else
def send_202(conn), do: Handler.message(conn, 202, @accepted)
def send_401(conn) do
conn
|> unquote(__CALLER__.module).authenticate()
|> Handler.message(401, @unauthorized)
end
def send_401(conn, error) do
conn
|> unquote(__CALLER__.module).authenticate()
|> Handler.json(401, error)
end
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