defmodule Camarero.Tapas do
@moduledoc """
This behaviour is high-level abstraction of the container begind handlers.
Aññ handlers are supposed to implement this behaviour. The simplest way
is to `use Camarero.Plato` in the handler module; that will inject
the default boilerlate using `%{binary() => any()}` map as a container behind.
Default implementation uses `Camarero.Tapas` as low-level container implementation.
"""
@typedoc "`Camarero.Tapas` allows anything implementing `Access` behaviour as a container"
@type t() :: Access.t()
@doc """
Returns the empty container to be used to store the data.
The returned term must implement `Access` behaviour since it’d be used from CR[U]D
operations (`c:tapas_get/2`, `c:tapas_put/3`, and `c:tapas_delete/2`).
"""
@callback tapas_into() :: t()
@doc "Retrieves from the container and returns the value for the key specified"
@callback tapas_get(bag :: t(), key :: binary() | atom()) :: {:ok, any()} | :error
@doc "Deletes the key-value pair for the key specified from the container"
@callback tapas_delete(bag :: t(), key :: binary() | atom()) :: {any(), t()}
@doc "Sets the value for the key specified (intended to be used from the application)"
@callback tapas_put(bag :: t(), key :: binary() | atom(), value :: any()) :: {any(), t()}
@doc false
defmacro __using__(opts \\ []) do
into = Keyword.get(opts, :into, {:%{}, [], []})
uri_decode = Keyword.get(opts, :uri_decode, false)
quote do
@behaviour Camarero.Tapas
@impl Camarero.Tapas
def tapas_into, do: unquote(into)
@impl Camarero.Tapas
def tapas_get(bag, key) when is_atom(key), do: tapas_get(bag, to_string(key))
@impl Camarero.Tapas
def tapas_get(bag, key) when is_binary(key) do
Access.fetch(bag, if(unquote(uri_decode), do: URI.decode(key), else: key))
end
@impl Camarero.Tapas
def tapas_put(bag, key, value) when is_atom(key),
do: tapas_put(bag, to_string(key), value)
@impl Camarero.Tapas
def tapas_put(bag, key, value) when is_binary(key),
do: Access.get_and_update(bag, key, &{&1, value})
@impl Camarero.Tapas
def tapas_delete(bag, key) when is_atom(key),
do: tapas_delete(bag, to_string(key))
@impl Camarero.Tapas
def tapas_delete(bag, key) when is_binary(key),
do: Access.get_and_update(bag, key, fn _ -> :pop end)
defoverridable Camarero.Tapas
end
end
end