defmodule Talos.Types.NumberType do
@moduledoc """
Type for check value is number
For example:
```elixir
iex> import Talos, only: [number: 1]
iex> percents = number(gteq: 0, lteq: 100)
iex> Talos.valid?(percents, 42)
true
iex> Talos.valid?(percents, -15)
false
iex> Talos.valid?(percents, 30.0)
true
```
Additional parameters:
`allow_nil` - allows value to be nil
`gteq` - greater than or equal, same as `>=`
`lteq` - lower than or equal, same as `<=`
`gt` - lower than, same as `>`
`lt` - lower than, same as `<`
"""
defstruct [:gteq, :lteq, :gt, :lt, :type, allow_nil: false, example_value: nil]
@behaviour Talos.Types
@type t :: %{
__struct__: atom,
gteq: nil | number,
lteq: nil | number,
gt: nil | number,
lt: nil | number,
allow_nil: boolean,
example_value: any,
type: :float | :integer | nil
}
@spec valid?(Talos.Types.NumberType.t(), any) :: boolean
def valid?(type, value) do
errors(type, value) == []
end
@spec errors(Talos.Types.NumberType.t(), binary) :: list(String.t())
def errors(%__MODULE__{allow_nil: true}, nil) do
[]
end
def errors(%__MODULE__{gteq: gteq, lteq: lteq, gt: gt, lt: lt, type: type}, value) do
errors =
case check_type(type, value) do
true ->
[
{is_nil(lt) || value < lt, "should be lower than #{lt}"},
{is_nil(gt) || value > gt, "should be greater than #{gt}"},
{is_nil(gteq) || value >= gteq, "should be greater than or equal to #{gteq}"},
{is_nil(lteq) || value <= lteq, "should be lower than or equal to #{lteq}"}
]
|> Enum.filter(fn {bool, _} -> bool == false end)
|> Enum.map(fn {_, error_text} -> error_text end)
false ->
["should be #{type || "float or integer"} type"]
end
case errors == [] do
true -> []
false -> [inspect(value)] ++ errors
end
end
defp check_type(nil, value) do
is_float(value) || is_integer(value)
end
defp check_type(:integer, value) do
is_integer(value)
end
defp check_type(:float, value) do
is_float(value)
end
end