Current section
Files
Jump to
Current section
Files
lib/core/checker.ex
defmodule Argx.Checker do
@moduledoc false
import Argx.Error
alias Argx.{Const, Parser}
@allowed_fun_types Const.allowed_fun_types()
@configs_keyword Const.configs_keyword()
@should_drop_flag Const.should_drop_flag()
###
def lacked(
{arg_name, arg_value},
{arg_name2, %Argx.Config{optional: false}},
path,
errors,
path_handler
)
when arg_name == arg_name2 and (is_nil(arg_value) or arg_value == @should_drop_flag) do
reduce_errors(errors, arg_name, path, path_handler, :lacked)
end
def lacked(
{arg_name, arg_value} = arg,
{arg_name2, %Argx.Config{optional: false, type: type, empty: true}} = config,
path,
errors,
path_handler
)
when arg_name == arg_name2 do
if empty?(arg_value, type) do
reduce_errors(errors, arg_name, path, path_handler, :lacked)
else
{errors, arg, config}
end
end
def lacked(
{arg_name, _} = arg,
{arg_name2, _} = config,
_path,
errors,
_path_handler
)
when arg_name == arg_name2 do
{errors, arg, config}
end
###
def error_type({errors, nil, nil}, _path, _path_handler) do
{errors, nil, nil}
end
def error_type({errors, args, configs}, path, path_handler) do
error_type(errors, args, configs, path, path_handler)
end
defp error_type(
errors,
{arg_name, arg_value},
{arg_name2, %Argx.Config{optional: true}},
_path,
_path_handler
)
when arg_name == arg_name2 and (is_nil(arg_value) or arg_value == @should_drop_flag) do
{errors, nil, nil}
end
defp error_type(
errors,
{arg_name, arg_value} = arg,
{arg_name2, %Argx.Config{type: type}} = config,
path,
path_handler
)
when arg_name == arg_name2 do
if some_type?(arg_value, type) do
{errors, arg, config}
else
reduce_errors(errors, arg_name, path, path_handler, :error_type)
end
end
###
def out_of_range({errors, nil, nil}, _path, _path_handler) do
errors
end
def out_of_range({errors, args, configs}, path, path_handler) do
out_of_range(errors, args, configs, path, path_handler)
end
defp out_of_range(
errors,
{arg_name, arg_value},
{arg_name2, %Argx.Config{optional: true}},
_path,
_path_handler
)
when arg_name == arg_name2 and (is_nil(arg_value) or arg_value == @should_drop_flag) do
errors
end
defp out_of_range(
errors,
{arg_name, _},
{arg_name2, %Argx.Config{range: nil}},
_path,
_path_handler
)
when arg_name == arg_name2 do
errors
end
defp out_of_range(
errors,
{arg_name, arg_value},
{arg_name2, %Argx.Config{type: type, range: range}},
path,
path_handler
)
when arg_name == arg_name2 do
if in_range?(arg_value, Parser.parse_range(range), type) do
errors
else
errors
|> reduce_errors(arg_name, path, path_handler, :out_of_range)
|> out_of_range(path, path_handler)
end
end
### Argx
def check_args!(%{} = _args), do: :ignore
def check_args!(args) when is_list(args) do
args
|> Keyword.keyword?()
|> if(
do: :ignore,
else: raise(Argx.Error, "args must be map or keyword")
)
end
def check_args!(_other_args), do: raise(Argx.Error, "args must be map or keyword")
def check_config_names!(config_names) do
case config_names do
[_ | _] -> :ignore
_ -> raise Argx.Error, "config names must be list & not empty"
end
end
### with check macro
def check!(configs, block) do
with :ok <- check_configs(configs),
:ok = ok <- check_block(block) do
ok
else
:configs_error -> raise Argx.Error, "not found #{@configs_keyword} keyword"
:block_error -> raise Argx.Error, "unknown function type"
:block_empty_error -> raise Argx.Error, "required one function at least"
_ -> :ok
end
end
defp check_configs({configs_keyword, _, _}) when configs_keyword == @configs_keyword, do: :ok
defp check_configs(_), do: :configs_error
defp check_block({:__block__, _, [expr | _]}), do: check_block(expr)
defp check_block({fun_type, _, _}) when fun_type in @allowed_fun_types, do: :ok
defp check_block({:__block__, [], []}), do: :block_empty_error
defp check_block(_), do: :block_error
### defconfig macro
def check_defconfig!(_name, [_ | _] = configs), do: check_defconfig!(configs)
def check_defconfig!(_name, {_, _, _} = config), do: check_defconfig!([config])
def check_defconfig!(_name, []), do: raise(Argx.Error, "configs is empty")
def check_defconfig!([{:||, _, [{_, _, _} = config, _]} | _]), do: check_defconfig!(config)
def check_defconfig!([{_, _, _} = config | _]), do: check_defconfig!(config)
def check_defconfig!({_, _, [_ | _]}), do: :ok
def check_defconfig!({_, _, []}), do: raise(Argx.Error, "at least config type")
###
def some_type?(v, :integer), do: is_integer(v)
def some_type?(v, :float), do: is_float(v)
def some_type?(v, :string), do: is_bitstring(v)
def some_type?(v, :list), do: is_list(v)
def some_type?(v, :map), do: is_map(v)
def some_type?(v, :boolean), do: is_boolean(v)
def some_type?(_other_v, _other_type), do: false
def in_range?(v, [l, r], :integer) when is_integer(v) do
(v >= l and v <= r) or (v == l and v == r)
end
def in_range?(v, [l, r], :float) when is_float(v) do
(v >= l and v <= r) or (v == l and v == r)
end
def in_range?(v, [l, r], :string) when is_bitstring(v) do
len = String.length(v)
(len >= l and len <= r) or (len == l and len == r)
end
def in_range?(v, [l, r], :list) when is_list(v) do
len = length(v)
(len >= l and len <= r) or (len == l and len == r)
end
def in_range?(v, [l, r], :map) when is_map(v) do
len = map_size(v)
(len >= l and len <= r) or (len == l and len == r)
end
def in_range?(v, [_l, _r], :boolean) when is_boolean(v) do
true
end
def in_range?(_other_v, _range, _other_type), do: false
def empty?(0, :integer), do: true
def empty?(0.0, :float), do: true
def empty?("", :string), do: true
def empty?([], :list), do: true
def empty?(%{} = v, :map), do: Enum.empty?(v)
def empty?(_other_v, _other_type), do: false
def are_keys_equal!(
f_name,
arg_names,
configs
)
when is_atom(f_name) and is_list(arg_names) and is_list(configs) do
arg_names2 = Keyword.keys(configs)
arg_names
|> Kernel.==(arg_names2)
|> if(
do: :ok,
else:
(
diff_names = (arg_names -- arg_names2) ++ (arg_names2 -- arg_names)
msg = "
>> #{f_name} function:
>> there are some args that not found configs.
>> have a try to check #{inspect(diff_names)} args."
raise Argx.Error, msg
)
)
end
def are_keys_equal!(_f_name, _arg_names, _configs), do: raise(Argx.Error, "data type error")
end