Current section

Files

Jump to
argx lib matcher.ex
Raw

lib/matcher.ex

defmodule Argx.Matcher do
@moduledoc false
import Argx.{Checker, Converter, Defaulter, Parser, Util}
def match(m, f, [_ | _] = args_kw, %{} = configs) when is_atom(m) and is_atom(f) do
are_keys_equal!(f, args_kw, configs)
configs =
args_kw
|> Keyword.keys()
|> sort_by_keys(configs)
args_kw =
args_kw
|> set_default(configs, m)
|> convert(configs, m)
{[], args_kw, configs}
|> lacked()
|> drop_checked_keys(args_kw, configs)
|> error_type()
|> drop_checked_keys(args_kw, configs)
|> out_of_range()
|> post_match(args_kw)
end
def match(_, _, _, _) do
:match_error
end
###
defp lacked({acc_errors, args_kw, configs}) do
do_lacked(acc_errors, args_kw, configs)
end
defp do_lacked([], [], []) do
[]
end
defp do_lacked(acc_errors, [], []) do
{:error, acc_errors}
end
defp do_lacked(
acc_errors,
[{k1, nil} | rest1],
[{k2, %Argx.Config{optional: false}} | rest2]
)
when k1 == k2 do
acc_errors
|> do_acc_errors(:lacked, k1)
|> do_lacked(rest1, rest2)
end
defp do_lacked(
acc_errors,
[{k1, _} | rest1],
[{k2, _} | rest2]
)
when k1 == k2 do
do_lacked(acc_errors, rest1, rest2)
end
###
defp error_type({acc_errors, args_kw, configs}) do
do_error_type(acc_errors, args_kw, configs)
end
defp do_error_type([], [], []) do
[]
end
defp do_error_type(acc_errors, [], []) do
{:error, acc_errors}
end
defp do_error_type(
acc_errors,
[{k1, nil} | rest1],
[{k2, %Argx.Config{optional: true}} | rest2]
)
when k1 == k2 do
do_error_type(acc_errors, rest1, rest2)
end
defp do_error_type(
acc_errors,
[{k1, v1} | rest1],
[{k2, %Argx.Config{type: type}} | rest2]
)
when k1 == k2 do
if some_type?(v1, type) do
acc_errors
else
do_acc_errors(acc_errors, :error_type, k1)
end
|> do_error_type(rest1, rest2)
end
###
defp out_of_range({acc_errors, args_kw, configs}) do
do_out_of_range(acc_errors, args_kw, configs)
end
defp do_out_of_range([], [], []) do
[]
end
defp do_out_of_range(acc_errors, [], []) do
{:error, acc_errors}
end
defp do_out_of_range(
acc_errors,
[{k1, nil} | rest1],
[{k2, %Argx.Config{optional: true}} | rest2]
)
when k1 == k2 do
do_out_of_range(acc_errors, rest1, rest2)
end
defp do_out_of_range(
acc_errors,
[{k1, _} | rest1],
[{k2, %Argx.Config{range: nil}} | rest2]
)
when k1 == k2 do
do_out_of_range(acc_errors, rest1, rest2)
end
defp do_out_of_range(
acc_errors,
[{k1, v1} | rest1],
[{k2, %Argx.Config{type: type, range: range}} | rest2]
)
when k1 == k2 do
if in_range?(v1, parse_range(range), type) do
acc_errors
else
do_acc_errors(acc_errors, :out_of_range, k1)
end
|> do_out_of_range(rest1, rest2)
end
###
defp do_acc_errors(acc, check_type, field) do
{nil, acc} =
Keyword.get_and_update(acc, check_type, fn current ->
new_value = (current && [field | current]) || [field]
{nil, new_value}
end)
acc
end
defp drop_checked_keys([], args_kw, configs) do
{[], args_kw, configs}
end
defp drop_checked_keys({:error, errors}, args_kw, configs) do
errors |> drop_checked_keys(errors, args_kw, configs)
end
defp drop_checked_keys([], errors, args_kw, configs) do
{errors, args_kw, configs}
end
defp drop_checked_keys([{_check_type, keys} | rest], errors, args_kw, configs) do
args_kw = Keyword.drop(args_kw, keys)
configs = Keyword.drop(configs, keys)
rest |> drop_checked_keys(errors, args_kw, configs)
end
defp post_match({:error, errors}, _) do
errors =
errors
|> Enum.reverse()
|> Enum.map(fn {type, fields} ->
{type, Enum.reverse(fields)}
end)
{:error, errors}
end
defp post_match([], args_kw) do
Keyword.values(args_kw)
end
end