Current section
Files
Jump to
Current section
Files
lib/validator.ex
defmodule Magik.Validator do
@moduledoc """
Validation module that support
- Validate type
"""
@doc """
Validate value type that support
- boolean
- integer
- float
- number
- string
- tuple
- map
- list
- atom
- function
- keyword
- struct
"""
def validate_type(value, :boolean) when is_boolean(value), do: :ok
def validate_type(value, :integer) when is_integer(value), do: :ok
def validate_type(value, :float) when is_float(value), do: :ok
def validate_type(value, :number) when is_number(value), do: :ok
def validate_type(value, :string) when is_binary(value), do: :ok
def validate_type(value, :tuple) when is_tuple(value), do: :ok
def validate_type(value, :map) when is_map(value), do: :ok
def validate_type(value, :array) when is_list(value), do: :ok
def validate_type(value, :list) when is_list(value), do: :ok
def validate_type(value, :atom) when is_atom(value), do: :ok
def validate_type(value, :function) when is_function(value), do: :ok
def validate_type([] = _check_item, :keyword), do: :ok
def validate_type([{atom, _} | _] = _check_item, :keyword) when is_atom(atom), do: :ok
def validate_type(value, struct_name) when is_struct(value, struct_name), do: :ok
def validate_type(_, type) when is_tuple(type), do: {:error, "is not an array"}
def validate_type(_, type), do: {:error, "is not a #{type}"}
def validate_number(value, checks) when is_list(checks) do
checks
|> Enum.map(&validate_number(value, &1))
|> collect_result()
end
@spec validate_number(number, {atom, number}) :: boolean
def validate_number(number, {:equal_to, check_value}) do
if number == check_value do
:ok
else
{:error, "must be equal to #{check_value}; got: #{inspect(number)}"}
end
end
def validate_number(number, {:greater_than, check_value}) do
if number > check_value do
:ok
else
{:error, "must be greater than #{check_value}; got: #{inspect(number)}"}
end
end
def validate_number(number, {:greater_than_or_equal_to, check_value}) do
if number >= check_value do
:ok
else
{:error, "must be greater than or equal to #{check_value}; got: #{inspect(number)}"}
end
end
def validate_number(number, {:min, check_value}) do
validate_number(number, {:greater_than_or_equal_to, check_value})
end
def validate_number(number, {:less_than, check_value}) do
if number < check_value do
:ok
else
{:error, "must be less than #{check_value}; got: #{inspect(number)}"}
end
end
def validate_number(number, {:less_than_or_equal_to, check_value}) do
if number <= check_value do
:ok
else
{:error, "must be less than or equal to #{check_value}; got: #{inspect(number)}"}
end
end
def validate_number(number, {:max, check_value}) do
validate_number(number, {:less_than_or_equal_to, check_value})
end
def validate_number(_number, {check, _check_value}) do
{:error, "unknown check '#{check}'"}
end
@doc """
Checks whether an item_name conforms the given format.
## Examples
iex> Exop.ValidationChecks.validate_regex(%{a: "bar"}, :a, ~r/bar/)
:ok
"""
@spec validate_format(String.t(), Regex.t()) ::
:ok | {:error, String.t()}
def validate_format(value, check) when is_binary(value) do
if Regex.match?(check, value), do: :ok, else: {:error, "format not matched"}
end
def validate_format(_value, _check) do
{:error, "format check only support string"}
end
@spec validate_length(map(), atom() | String.t(), map()) :: :ok | {:error, list()}
def validate_length(value, checks) do
actual_length = get_length(value)
checks
|> Enum.map(fn {condition, condition_value} ->
validate_length(condition, actual_length, condition_value)
end)
|> collect_result()
end
@spec get_length(any) :: pos_integer() | {:error, :wrong_type}
defp get_length(param) when is_list(param), do: length(param)
defp get_length(param) when is_binary(param), do: String.length(param)
defp get_length(param) when is_map(param), do: param |> Map.keys() |> get_length()
defp get_length(param) when is_tuple(param), do: tuple_size(param)
defp get_length(_param), do: {:error, :wrong_type}
defp validate_length(_, {:error, :wrong_type}, _check_value) do
{:error, "length check supports only lists, binaries, maps and tuples"}
end
defp validate_length(:min, actual_length, check_value) do
validate_length(:greater_than_or_equal_to, actual_length, check_value)
end
defp validate_length(:greater_than_or_equal_to, actual_length, check_value) do
(actual_length >= check_value && :ok) ||
{
:error,
"length must be greater than or equal to #{check_value}; got length: #{inspect(actual_length)}"
}
end
defp validate_length(:greater_than, actual_length, check_value) do
(actual_length > check_value && :ok) ||
{:error,
"length must be greater than #{check_value}; got length: #{inspect(actual_length)}"}
end
defp validate_length(:max, actual_length, check_value) do
validate_length(:less_than_or_equal_to, actual_length, check_value)
end
defp validate_length(:less_than_or_equal_to, actual_length, check_value) do
(actual_length <= check_value && :ok) ||
{
:error,
"length must be less than or equal to #{check_value}; got length: #{inspect(actual_length)}"
}
end
defp validate_length(:less_than, actual_length, check_value) do
(actual_length < check_value && :ok) ||
{
:error,
"length must be less than #{check_value}; got length: #{inspect(actual_length)}"
}
end
defp validate_length(:equal_to, actual_length, check_value) do
(actual_length == check_value && :ok) ||
{
:error,
"length must be equal to #{check_value}; got length: #{inspect(actual_length)}"
}
end
defp validate_length(:in, actual_length, check_value) do
if Enum.member?(check_value, actual_length) do
:ok
else
{
:error,
"length must be in range #{check_value}; got length: #{inspect(actual_length)}"
}
end
end
defp validate_length(check, _actual_length, _check_value) do
{:error, "unknown check '#{check}'"}
end
defp collect_result(results) do
Enum.reduce(results, :ok, fn
:ok, acc -> acc
{:error, msg}, :ok -> {:error, [msg]}
{:error, msg}, {:error, acc_msg} -> {:error, [msg | acc_msg]}
end)
end
end