Current section
Files
Jump to
Current section
Files
lib/money/validate.ex
defmodule Money.Validate do
@moduledoc """
Implements Ecto validations for the `t:Money.t/0` type based upon the
`Money.Ecto.Composite.Type` type.
"""
@money_validators %{
less_than: "must be less than %{money}",
greater_than: "must be greater than %{money}",
less_than_or_equal_to: "must be less than or equal to %{money}",
greater_than_or_equal_to: "must be greater than or equal to %{money}",
equal_to: "must be equal to %{money}",
not_equal_to: "must be not equal to %{money}"
}
@doc """
Validates the properties of a `t:Money.t/0`.
This function, including its options, is designed to
mirror the function `Ecto.Changeset.validate_number/3`.
## Options
* `:less_than`
* `:greater_than`
* `:less_than_or_equal_to`
* `:greater_than_or_equal_to`
* `:equal_to`
* `:not_equal_to`
* `:message` - the message on failure, defaults to one of:
* "must be less than %{money}"
* "must be greater than %{money}"
* "must be less than or equal to %{money}"
* "must be greater than or equal to %{money}"
* "must be equal to %{money}"
* "must be not equal to %{money}"
## Examples
validate_money(changeset, :value, less_than: Money.new(:USD, 200))
validate_money(changeset, :value, less_than_or_equal_to: Money.new(:USD, 200)
validate_money(changeset, :value, less_than_or_equal_to: Money.new(:USD, 100))
validate_money(changeset, :value, greater_than: Money.new(:USD, 50))
validate_money(changeset, :value, greater_than_or_equal_to: Money.new(:USD, 50))
validate_money(changeset, :value, greater_than_or_equal_to: Money.new(:USD, 100))
"""
@spec validate_money(Ecto.Changeset.t(), atom, Keyword.t()) :: Ecto.Changeset.t()
def validate_money(changeset, field, opts) do
Ecto.Changeset.validate_change(changeset, field, {:money, opts}, fn
field, value ->
{message, opts} = Keyword.pop(opts, :message)
Enum.find_value(opts, [], fn {spec_key, target_value} ->
case Map.fetch(@money_validators, spec_key) do
{:ok, default_message} ->
validate_money(field, value, message || default_message, spec_key, target_value)
:error ->
supported_options = @money_validators |> Map.keys()
raise ArgumentError, """
unknown option #{inspect(spec_key)} given to validate_money/3
The supported options are:
#{supported_options}
"""
end
end)
end)
end
defp validate_money(field, %Money{} = value, message, spec_key, %Money{} = target_value) do
result = Money.compare(value, target_value)
case money_compare(result, spec_key) do
true ->
nil
false ->
[{field, {message, validation: :money, kind: spec_key, money: target_value}}]
{:error, {_exception, reason}} ->
[{field, {reason, validation: :money, kind: spec_key, money: target_value}}]
end
end
defp validate_money(_field, value, _message, _spec_key, %Money{} = _target_value) do
raise ArgumentError, "expected value to be of type Money, got: #{inspect(value)}"
end
defp validate_money(_field, %Money{} = _value, _message, _spec_key, target_value) do
raise ArgumentError,
"expected target_value to be of type Money, got: #{inspect(target_value)}"
end
defp validate_money(_field, value, _message, _spec_key, target_value) do
raise ArgumentError,
"expected value and target_value to be of type Money, " <>
"got value: #{inspect(value)} and target_value: #{target_value}"
end
defp money_compare(:lt, spec), do: spec in [:less_than, :less_than_or_equal_to, :not_equal_to]
defp money_compare(:gt, spec),
do: spec in [:greater_than, :greater_than_or_equal_to, :not_equal_to]
defp money_compare(:eq, spec),
do: spec in [:equal_to, :less_than_or_equal_to, :greater_than_or_equal_to]
defp money_compare(other, _spec), do: other
end