defmodule Parameter.Enum do
@moduledoc """
Enum type that represents a group of constants that have a value with a associated key.
## Example
defmodule MyApp.UserParam do
use Parameter.Schema
enum Status do
value "userOnline", as: :user_online
value "userOffline", as: :user_offline
end
param do
field :first_name, :string, key: "firstName"
field :status, MyApp.UserParam.Status
end
end
The `Status` enum should automatically translate the `userOnline` and `userOffline` values when loading
to the respective atom values.
iex> Parameter.load(MyApp.UserParam, %{"firstName" => "John", "status" => "userOnline"})
{:ok, %{first_name: "John", status: :user_online}}
...> # It also uses the key for dumping:
...> Parameter.dump(MyApp.UserParam, %{first_name: "John", status: :user_online})
{:ok, %{"firstName" => "John", "status" => "userOnline"}}
Enum also supports a shorter version if the key and value are already the same:
defmodule MyApp.UserParam do
...
enum Status, values: [:user_online, :user_offline]
...
end
iex> Parameter.load(MyApp.UserParam, %{"firstName" => "John", "status" => "user_online"})
{:ok, %{first_name: "John", status: :user_online}}
It's also possible to create enums in different modules by using the
`enum/1` macro:
defmodule MyApp.Status do
import Parameter.Enum
enum do
value "userOnline", as: :user_online
value "userOffline", as: :user_offline
end
end
defmodule MyApp.UserParam do
use Parameter.Schema
alias MyApp.Status
param do
field :first_name, :string, key: "firstName"
field :status, Status
end
end
And it's short version:
enum values: [:user_online, :user_offline]
"""
defmacro enum(do: block) do
module_block = create_module_block(block)
quote do
unquote(module_block)
end
end
defmacro enum(values: values) do
block =
quote do
Enum.map(unquote(values), fn val ->
value(to_string(val), as: val)
end)
end
quote do
enum(do: unquote(block))
end
end
defmacro enum(module_name, do: block) do
module_block = create_module_block(block) |> Macro.escape()
quote bind_quoted: [module_name: module_name, module_block: module_block] do
module_name = Module.concat(__ENV__.module, module_name)
Module.create(module_name, module_block, __ENV__)
end
end
defmacro enum(module_name, values: values) when is_list(values) do
block =
quote do
Enum.map(unquote(values), fn val ->
value(to_string(val), as: val)
end)
end
quote do
enum(unquote(module_name), do: unquote(block))
end
end
defmacro value(key, as: value) do
quote bind_quoted: [key: key, value: value] do
Module.put_attribute(__MODULE__, :enum_values, {key, value})
end
end
defp create_module_block(block) do
quote do
@moduledoc """
Enum parameter type
"""
use Parameter.Parametrizable
Module.register_attribute(__MODULE__, :enum_values, accumulate: true)
unquote(block)
@impl true
def load(value) do
@enum_values
|> Enum.find(fn {key, enum_value} ->
key == value
end)
|> case do
nil -> error_tuple()
{_key, enum_value} -> {:ok, enum_value}
end
end
@impl true
def dump(value) do
@enum_values
|> Enum.find(fn {_key, enum_value} ->
value == enum_value
end)
|> case do
nil -> error_tuple()
{key, _value} -> {:ok, key}
end
end
@impl true
def validate(value) do
case dump(value) do
{:error, reason} -> {:error, reason}
{:ok, _key} -> :ok
end
end
defp error_tuple, do: {:error, "invalid enum type"}
end
end
end