defmodule Dsv.Number do
use Dsv.Validator
@behaviour Dsv.Comparator
@moduledoc """
Dsv.Number module provides functions to validate a number based on various options.
## Possible options
- `:gt` - Greater than (value must be greater than the specified number).
- `:lt` - Lower than (value must be lower than the specified number).
- `:gte` - Greater than or equal (value must be greater than or equal to the specified number).
- `:lte` - Lower than or equal (value must be lower than or equal to the specified number).
- `:eq` - Equal (value must be equal to the specified number).
- `:between` - Between (value must be within the specified range defined by two numbers).
Options can be combined.
"""
message({:gt, "Value <%= inspect data %> must be greater than: <%= options[:gt] %>"})
message({:lt, "Value <%= inspect data %> must be lower than: <%= options[:lt] %>"})
message({:eq, "Value <%= inspect data %> must be equal to value <%= options[:eq] %>"})
message(
{[:gt, :lt],
"Value <%= inspect data %> must be lower than: <%= options[:lt] %> and greater than: <%= options[:gt] %>"}
)
message(
{[:gte, :lt],
"Value <%= inspect data %> must be lower than: <%= options[:lt] %> and greater than or equal: <%= options[:gte] %>"}
)
message(
{[:gt, :lte],
"Value <%= inspect data %> must be lower than or equal: <%= options[:lte] %> and greater than: <%= options[:gt] %>"}
)
message({:gte, "Value <%= inspect data %> must be greater than or equal: <%= options[:gte] %>"})
message({:lte, "Value <%= inspect data %> must be lower than or equal: <%= options[:lte] %>"})
message(
{[:gte, :lte],
"Value <%= inspect data %> must be lower than or equal: <%= options[:lte] %> and greater than or equal: <%= options[:gte] %>"}
)
message(
{[:between],
"Value <%= inspect data %> must be lower than or equal: <%= elem(options[:between], 1) %> and greater than or equal: <%= elem(options[:between], 0) %>"}
)
@doc """
The `valid?/2` function checks if a provided number meets specific validation criteria based on the given options (in the form of a keyword list).
## Parameters
* `number` - The number to be validated (integer of float).
* `options` - A list of validation options. Each option consists of a keyword followed by the corresponding value.
Supported options include:
- `:gt` - Greater than (value must be greater than the specified number).
- `:lt` - Lower than (value must be lower than the specified number).
- `:gte` - Greater than or equal (value must be greater than or equal to the specified number).
- `:lte` - Lower than or equal (value must be lower than or equal to the specified number).
- `:eq` - Equal (value must be equal to the specified number).
- `:between` - Between (value must be within the specified range defined by two numbers).
## Returns
A boolean value:
- `true` if the `number` meets all the specified validation criteria.
- `false` if the `number` fails to meet any of the specified criteria.
## Examples
`:gt` example:
iex> Dsv.Number.valid?(4, gt: 3)
:true
iex> Dsv.Number.valid?(4, gt: 4)
:false
iex> Dsv.Number.valid?(4, gt: 5)
:false
`:gte` example:
iex> Dsv.Number.valid?(4, gte: 3)
:true
iex> Dsv.Number.valid?(4, gte: 4)
:true
iex> Dsv.Number.valid?(4, gte: 5)
:false
`:lt` example:
iex> Dsv.Number.valid?(4, lt: 3)
:false
iex> Dsv.Number.valid?(4, lt: 4)
:false
iex> Dsv.Number.valid?(4, lt: 5)
:true
`:lte` example:
iex> Dsv.Number.valid?(4, lte: 3)
:false
iex> Dsv.Number.valid?(4, lte: 4)
:true
iex> Dsv.Number.valid?(4, lte: 5)
:true
`:gt & :lt` example:
iex> Dsv.Number.valid?(4, gt: 3, lt: 5)
:true
iex> Dsv.Number.valid?(4, gt: 4, lt: 5)
:false
iex> Dsv.Number.valid?(4, gt: 2, lt: 4)
:false
`:gte & :lt` example:
iex> Dsv.Number.valid?(4, gte: 3, lt: 5)
:true
iex> Dsv.Number.valid?(4, gte: 4, lt: 5)
:true
iex> Dsv.Number.valid?(4, gte: 2, lt: 4)
:false
`:gt & :lte` example:
iex> Dsv.Number.valid?(4, gt: 3, lte: 5)
:true
iex> Dsv.Number.valid?(4, gt: 4, lte: 5)
:false
iex> Dsv.Number.valid?(4, gt: 2, lte: 4)
:true
`:gte & :lte` example:
iex> Dsv.Number.valid?(4, gte: 3, lte: 5)
:true
iex> Dsv.Number.valid?(4, gte: 4, lte: 5)
:true
iex> Dsv.Number.valid?(4, gte: 2, lte: 4)
:true
iex> Dsv.Number.valid?(1, gte: 2, lte: 4)
:false
iex> Dsv.Number.valid?(5, gte: 2, lte: 4)
:false
`:between` example:
iex> Dsv.Number.valid?(4, between: {3, 5})
:true
iex> Dsv.Number.valid?(4, between: {4, 5})
:true
iex> Dsv.Number.valid?(4, between: {2, 4})
:true
iex> Dsv.Number.valid?(1, between: {2, 4})
:false
iex> Dsv.Number.valid?(5, between: {2, 4})
:false
`:eq` example:
iex> Dsv.Number.valid?(4, eq: 4)
:true
iex> Dsv.Number.valid?(4, eq: 5)
:false
"""
def valid?(data, options) when is_list(options),
do:
options
|> Enum.all?(fn {function_name, value} -> compare(function_name, data, value) end)
@doc """
The `validate/2` function checks if a provided number meets specific validation criteria based on the given options.
## Parameters
* `number` - The number to be validated.
* `options` - A list of validation options. Each option consists of a keyword followed by the corresponding value.
Supported options include:
- `:gt` - Greater than (value must be greater than the specified number).
- `:lt` - Lower than (value must be lower than the specified number).
- `:gte` - Greater than or equal (value must be greater than or equal to the specified number).
- `:lte` - Lower than or equal (value must be lower than or equal to the specified number).
- `:eq` - Equal (value must be equal to the specified number).
- `:between` - Between (value must be within the specified range defined by two numbers).
* `message` (optional) - Custom message returned in case of error.
## Returns
- `:ok` if the `number` meets all the specified validation criteria.
- `{:error, message}` if the `number` fails to meet any of the specified criteria.
## Examples
`:gt` example:
iex> Dsv.Number.validate(4, gt: 3)
:ok
iex> Dsv.Number.validate(4, gt: 4)
{:error, "Value 4 must be greater than: 4"}
iex> Dsv.Number.validate(4, gt: 5)
{:error, "Value 4 must be greater than: 5"}
iex> Dsv.Number.validate(4, gt: 5, message: "This number is too small.")
{:error, "This number is too small."}
`:gte` example:
iex> Dsv.Number.validate(4, gte: 3)
:ok
iex> Dsv.Number.validate(4, gte: 4)
:ok
iex> Dsv.Number.validate(4, gte: 5)
{:error, "Value 4 must be greater than or equal: 5"}
iex> Dsv.Number.validate(2, gte: 5, message: fn
...> data, _options when data > 3 -> "This value is too small."
...> _data, _options -> "This value is way too small."
...> end)
{:error, "This value is way too small."}
`:lt` example:
iex> Dsv.Number.validate(4, lt: 3)
{:error, "Value 4 must be lower than: 3"}
iex> Dsv.Number.validate(4, lt: 4)
{:error, "Value 4 must be lower than: 4"}
iex> Dsv.Number.validate(4, lt: 4, message: "Please provide smaller number.")
{:error, "Please provide smaller number."}
iex> Dsv.Number.validate(4, lt: 5)
:ok
`:lte` example:
iex> Dsv.Number.validate(4, lte: 3)
{:error, "Value 4 must be lower than or equal: 3"}
iex> Dsv.Number.validate(4, lte: 4)
:ok
iex> Dsv.Number.validate(4, lte: 5)
:ok
`:gt & :lt` example:
iex> Dsv.Number.validate(4, gt: 3, lt: 5)
:ok
iex> Dsv.Number.validate(4, gt: 4, lt: 5)
{:error, "Value 4 must be lower than: 5 and greater than: 4"}
iex> Dsv.Number.validate(4, gt: 2, lt: 4)
{:error, "Value 4 must be lower than: 4 and greater than: 2"}
`:gte & :lt` example:
iex> Dsv.Number.validate(4, gte: 3, lt: 5)
:ok
iex> Dsv.Number.validate(4, gte: 4, lt: 5)
:ok
iex> Dsv.Number.validate(4, gte: 2, lt: 4)
{:error, "Value 4 must be lower than: 4 and greater than or equal: 2"}
`:gt & :lte` example:
iex> Dsv.Number.validate(4, gt: 3, lte: 5)
:ok
iex> Dsv.Number.validate(4, gt: 4, lte: 5)
{:error, "Value 4 must be lower than or equal: 5 and greater than: 4"}
iex> Dsv.Number.validate(4, gt: 2, lte: 4)
:ok
`:gte & :lte` example:
iex> Dsv.Number.validate(4, gte: 3, lte: 5)
:ok
iex> Dsv.Number.validate(4, gte: 4, lte: 5)
:ok
iex> Dsv.Number.validate(4, gte: 2, lte: 4)
:ok
iex> Dsv.Number.validate(1, gte: 2, lte: 4)
{:error, "Value 1 must be lower than or equal: 4 and greater than or equal: 2"}
iex> Dsv.Number.validate(5, gte: 2, lte: 4)
{:error, "Value 5 must be lower than or equal: 4 and greater than or equal: 2"}
`:between` example:
iex> Dsv.Number.validate(4, between: {3, 5})
:ok
iex> Dsv.Number.validate(4, between: {4, 5})
:ok
iex> Dsv.Number.validate(4, between: {2, 4})
:ok
iex> Dsv.Number.validate(1, between: {2, 4})
{:error, "Value 1 must be lower than or equal: 4 and greater than or equal: 2"}
iex> Dsv.Number.validate(5, between: {2, 4})
{:error, "Value 5 must be lower than or equal: 4 and greater than or equal: 2"}
`:eq` example:
iex> Dsv.Number.validate(4, eq: 4)
:ok
iex> Dsv.Number.validate(4, eq: 5)
{:error, "Value 4 must be equal to value 5"}
`wrong options` example:
iex> Dsv.Number.validate(1, lt: 2, gt: "not a number")
{:error, "Value 1 must be lower than: 2 and greater than: not a number"}
iex> Dsv.Number.validate("wrong data", between: {1, 5})
{:error, ~s(Value "wrong data" must be lower than or equal: 5 and greater than or equal: 1)}
"""
def validate(data, options), do: super(data, options)
defp compare(_, number1, {min, max})
when not is_number(number1) or not is_number(min) or not is_number(max),
do: false
defp compare(_, number1, number2)
when not is_number(number1) or (not is_number(number2) and not is_tuple(number2)),
do: false
defp compare(:gt, number1, number2), do: number1 > number2
defp compare(:lt, number1, number2), do: number1 < number2
defp compare(:gte, number1, number2), do: number1 >= number2
defp compare(:lte, number1, number2), do: number1 <= number2
defp compare(:between, number1, {min, max}), do: number1 >= min and number1 <= max
defp compare(:eq, number1, number2), do: number1 == number2
@impl Dsv.Comparator
def to_comparator(value, [:lt]), do: [lt: value]
@impl Dsv.Comparator
def to_comparator(value, [:gt]), do: [gt: value]
@impl Dsv.Comparator
def to_comparator(value, [:gte]), do: [gte: value]
@impl Dsv.Comparator
def to_comparator(value, [:lte]), do: [lte: value]
@impl Dsv.Comparator
def to_comparator(_value, options) do
{:error, "Can't create comparator from given options #{options}"}
end
end