defmodule Fussy.Validators.Integer do
@behaviour Fussy.Validator
alias Fussy.ValidationError
defstruct strict: true,
gt: nil,
lt: nil,
gte: nil,
lte: nil,
multiple_of: nil
@opaque t :: %__MODULE__{}
@spec new(
strict: boolean(),
gt: number() | nil,
lt: number() | nil,
gte: number() | nil,
lte: number() | nil,
multiple_of: integer() | nil
) :: __MODULE__.t()
def new(args \\ []), do: struct!(%__MODULE__{}, args)
def validate(opts, term), do: validate(opts, [], term)
@impl true
def validate(%__MODULE__{strict: false} = v, path, term) when is_float(term) do
validate(v, path, trunc(term), term)
end
@impl true
def validate(%__MODULE__{} = v, path, term) when is_integer(term) do
validate(v, path, term, term)
end
@impl true
def validate(%__MODULE__{strict: false} = v, path, term) when is_binary(term) do
case Integer.parse(term) do
{n, ""} ->
validate(v, path, n, term)
{_, _} ->
{:error,
[%ValidationError{path: path, mod: __MODULE__, msg: "must be an integer", term: term}]}
:error ->
{:error,
[%ValidationError{path: path, mod: __MODULE__, msg: "must be an integer", term: term}]}
end
end
@impl true
def validate(%__MODULE__{}, path, term),
do:
{:error,
[%ValidationError{path: path, mod: __MODULE__, msg: "must be an integer", term: term}]}
def validate(
%__MODULE__{gt: gt, lt: lt, gte: gte, lte: lte, multiple_of: multiple_of},
path,
term,
original_term
)
when is_integer(term) do
[gt: gt, lt: lt, gte: gte, lte: lte, multiple_of: multiple_of]
|> Enum.filter(fn
{_, nil} -> false
_ -> true
end)
|> Enum.reduce([], fn validator, errors ->
case validate_with(validator, term) do
:ok -> errors
reason -> [reason | errors]
end
end)
|> then(fn
[] ->
{:ok, term}
errors ->
{:error,
errors
|> Enum.map(fn msg ->
%ValidationError{path: path, mod: __MODULE__, msg: msg, term: original_term}
end)}
end)
end
defp validate_with({:gt, gt}, n) when n > gt, do: :ok
defp validate_with({:gt, gt}, _), do: "must be greater than #{gt}"
defp validate_with({:gte, gte}, n) when n >= gte, do: :ok
defp validate_with({:gte, gte}, _), do: "must be greater than or equal to #{gte}"
defp validate_with({:lt, lt}, n) when n < lt, do: :ok
defp validate_with({:lt, lt}, _), do: "must be less than #{lt}"
defp validate_with({:lte, lte}, n) when n <= lte, do: :ok
defp validate_with({:lte, lte}, _), do: "must be less than or equal to #{lte}"
defp validate_with({:multiple_of, multiple_of}, n) do
if Integer.mod(n, multiple_of) == 0 do
:ok
else
"must be a multiple of #{multiple_of}"
end
end
end