Packages

A series of macros, and utilities to help hadling your elixir tonics.

Current section

Files

Jump to
rubbergloves lib mapper structure.ex
Raw

lib/mapper/structure.ex

defmodule Rubbergloves.Structure do
defmacro __using__(opts \\ []) do
validator = Keyword.get(opts, :validator, Rubbergloves.Validator.Justify)
quote do
require Rubbergloves.Structure
import Rubbergloves.Structure
@properties []
@validator unquote(validator)
@before_compile {unquote(__MODULE__), :__before_compile__}
end
end
defmacro defstructure(block) do
quote do
unquote(block)
end
end
defmacro field(name, type, opts \\ []) do
quote do
Module.put_attribute(
__MODULE__,
:properties,
@properties ++
[
{unquote(name), unquote(type), unquote(opts)}
]
)
end
end
defmacro one(name, type, opts \\ []) do
quote do
Module.put_attribute(
__MODULE__,
:properties,
@properties ++
[
{unquote(name), {:one, unquote(type)}, unquote(opts)}
]
)
end
end
defmacro many(name, type, opts \\ []) do
quote do
Module.put_attribute(
__MODULE__,
:properties,
@properties ++
[
{unquote(name), {:many, unquote(type)}, unquote(opts)}
]
)
end
end
defmacro __before_compile__(_env) do
quote do
@properties
|> Enum.map(&struct_key/1)
|> defstruct
def validate(struct = %__MODULE__{}) do
Enum.reduce(@properties, struct, &validations(@validator, &1, &2))
end
end
end
def validations(validator, {key, type, options}, struct) do
result =
case options do
required: true -> validator.required(struct, key)
_ -> struct
end
|> validator.type(key, type)
case Rubbergloves.Validation.valid?(result) do
true ->
data = Map.get(struct, :data, %{})
nested = Map.get(struct, key, Map.get(data, key))
case {type, nested} do
{_, nil} -> result
{{:one, type}, _} ->
maybe_put_nested_errors(result, key, type.validate(nested))
{{:many, type}, _} ->
maybe_put_nested_errors(result, key, Enum.map(nested, fn item -> type.validate(item) end))
_ ->
result
end
false ->
result
end
end
defp maybe_put_nested_errors(result, key, nested) when is_list(nested) do
errors = nested
|> Enum.filter(fn item -> !Rubbergloves.Validation.valid?(item) end)
case length(errors) do
0 -> result
_ -> Map.put(result, :errors, [{key, Enum.map(errors, &Rubbergloves.Validation.errors/1)}])
end
end
defp maybe_put_nested_errors(result, key, nested) do
case Rubbergloves.Validation.valid?(nested) do
true -> result
false -> Map.put(result, :errors, [{key, Rubbergloves.Validation.errors(nested)}])
end
end
def struct_key({name, _, default: default}), do: {name, default}
def struct_key({name, _, _}), do: {name, nil}
end