Current section
Files
Jump to
Current section
Files
lib/mayo.ex
defmodule Mayo do
@doc """
Validates data with a schema.
iex> Mayo.validate "test", Mayo.String.min(4) |> String.capitalize
"Test"
iex> Mayo.validate 42, Mayo.Number.max(23)
{:error, %Mayo.Error{type: "number.max"}}
iex> Mayo.validate %{username: "johndoe"}, %{
...> username: Mayo.Any.string |> Mayo.String.min(6)
...> }
%{username: "johndoe"}
iex> Mayo.validate %{username: "test"}, %{
...> username: Mayo.Any.string |> Mayo.String.min(6)
...> }
{:error, %Mayo.Error{type: "string.min", path: :username}}
"""
defmacro validate(value, schema) do
compile(schema, value)
end
defp compile(schema, value) do
pipes = unpipe_schema(schema)
Enum.reduce(pipes, value, &reduce_pipes/2)
end
defp unpipe_schema({op, _, _} = schema) when op == :|> do
Macro.unpipe(schema)
end
defp unpipe_schema(schema) do
[{schema, 0}]
end
defp reduce_pipes({pipe, _}, acc) do
pipe = compile_pipe(pipe, acc)
quote do
case unquote(acc) do
{:error, _} -> unquote(acc)
_ -> unquote(pipe)
end
end
end
defp compile_pipe({:%{}, _, args}, value) do
Enum.reduce(args, value, &reduce_map_pipes/2)
end
defp compile_pipe({op, meta, args}, value) do
{op, meta, [value | args]}
end
defp reduce_map_pipes({key, pipes}, acc) do
item = quote do
case unquote(acc) do
{:error, _} -> unquote(acc)
_ -> Map.get(unquote(acc), unquote(key))
end
end
pipe = compile(pipes, item)
quote do
case unquote(acc) do
{:error, _} -> unquote(acc)
_ ->
case unquote(pipe) do
{:error, err} -> {:error, %{err | path: unquote(key)}}
result -> Map.put(unquote(acc), unquote(key), result)
end
end
end
end
end