Current section

Files

Jump to
litmus lib type number.ex
Raw

lib/type/number.ex

defmodule Litmus.Type.Number do
@moduledoc false
defstruct [
:min,
:max,
integer: false,
required: false
]
@type t :: %__MODULE__{
min: number | nil,
max: number | nil,
integer: boolean,
required: boolean
}
alias Litmus.Required
@spec validate_field(t, binary, map) :: {:ok, map} | {:error, binary}
def validate_field(type, field, data) do
with {:ok, data} <- Required.validate(type, field, data),
{:ok, data} <- convert(type, field, data),
{:ok, data} <- min_validate(type, field, data),
{:ok, data} <- max_validate(type, field, data),
{:ok, data} <- integer_validate(type, field, data) do
{:ok, data}
else
{:error, msg} -> {:error, msg}
end
end
@spec convert(t, binary, map) :: {:ok, map} | {:error, binary}
defp convert(%__MODULE__{}, field, params) do
cond do
!Map.has_key?(params, field) ->
{:ok, params}
is_number(params[field]) ->
{:ok, params}
is_binary(params[field]) && string_to_number(params[field]) ->
modified_value = string_to_number(params[field])
{:ok, Map.put(params, field, modified_value)}
true ->
{:error, "#{field} must be a number"}
end
end
@spec string_to_number(binary) :: number | nil
defp string_to_number(str) do
str = if String.starts_with?(str, "."), do: "0" <> str, else: str
cond do
int = string_to_integer(str) -> int
float = string_to_float(str) -> float
true -> nil
end
end
@spec string_to_integer(binary) :: number | nil
defp string_to_integer(str) do
case Integer.parse(str) do
{num, ""} -> num
_ -> nil
end
end
@spec string_to_float(binary) :: number | nil
defp string_to_float(str) do
case Float.parse(str) do
{num, ""} -> num
_ -> nil
end
end
@spec integer_validate(t, binary, map) :: {:ok, map} | {:error, binary}
defp integer_validate(%__MODULE__{integer: false}, _field, params) do
{:ok, params}
end
defp integer_validate(%__MODULE__{integer: true}, field, params) do
if Map.has_key?(params, field) && !is_integer(params[field]) do
{:error, "#{field} must be an integer"}
else
{:ok, params}
end
end
@spec min_validate(t, binary, map) :: {:ok, map} | {:error, binary}
defp min_validate(%__MODULE__{min: nil}, _field, params) do
{:ok, params}
end
defp min_validate(%__MODULE__{min: min}, field, params)
when is_number(min) do
if Map.has_key?(params, field) && params[field] < min do
{:error, "#{field} must be greater than or equal to #{min}"}
else
{:ok, params}
end
end
@spec max_validate(t, binary, map) :: {:ok, map} | {:error, binary}
defp max_validate(%__MODULE__{max: nil}, _field, params) do
{:ok, params}
end
defp max_validate(%__MODULE__{max: max}, field, params)
when is_number(max) do
if Map.has_key?(params, field) && params[field] > max do
{:error, "#{field} must be less than or equal to #{max}"}
else
{:ok, params}
end
end
defimpl Litmus.Type do
alias Litmus.Type
@spec validate(Type.t(), binary, map) :: {:ok, map} | {:error, binary}
def validate(type, field, data), do: Type.Number.validate_field(type, field, data)
end
end