defmodule Kvasir.Type.Enum do
defmacro __using__(_opts \\ []) do
quote do
@before_compile unquote(__MODULE__)
import unquote(__MODULE__), only: [option: 1, option: 2]
Module.register_attribute(__MODULE__, :options, accumulate: true)
end
end
defmacro option(value, opts \\ []) do
quote do
Module.put_attribute(
__MODULE__,
:options,
{unquote(value),
unquote(opts)
|> Keyword.put_new_lazy(:doc, fn ->
case Module.delete_attribute(__MODULE__, :doc) do
{_, doc} -> doc
_ -> nil
end
end)}
)
end
end
defmacro __before_compile__(env) do
{parse, dump, all} =
env.module
|> Module.get_attribute(:options)
|> Enum.reduce({nil, nil, []}, fn {value, opts}, {parse, dump, acc} ->
encoded = Keyword.get_lazy(opts, :encoded, fn -> to_string(value) end)
{quote do
unquote(parse)
def parse(unquote(value), _opts), do: {:ok, unquote(value)}
def parse(unquote(encoded), _opts), do: {:ok, unquote(value)}
end,
quote do
unquote(dump)
def dump(unquote(value), _opts), do: {:ok, unquote(encoded)}
end, [{value, encoded, Keyword.take(opts, ~w(doc)a)} | acc]}
end)
quote do
import unquote(__MODULE__), only: []
use Kvasir.Type
@impl Kvasir.Type
def parse(value, opts \\ [])
unquote(parse)
def parse(_, _), do: {:error, :invalid_enum_value}
@impl Kvasir.Type
def dump(value, opts \\ [])
unquote(dump)
@impl Kvasir.Type
def describe(value), do: to_string(value)
@doc """
List all possible values for the given enum.
## Example
```elixir
iex> values()
#{unquote(inspect(Enum.map(all, &elem(&1, 0))))}
```
"""
@spec values :: [atom]
def values, do: unquote(Enum.map(all, &elem(&1, 0)))
@doc false
@spec __enum__ :: [{term, term, Keyword.t()}]
def __enum__, do: unquote(Macro.escape(all))
end
end
end