Current section
Files
Jump to
Current section
Files
lib/coercion.ex
defmodule Coercion do
@moduledoc """
Rigorous coercion of untrusted values to native primitive types
"""
@spec coerce(any, :integer | :boolean | :string) :: {:ok | :invalid | :blank, String.t | integer | Boolean.t }
@doc """
Coerce and validate a value to the given type.
## Examples
iex> import Coercion # For demo purposes
Coercion
iex> coerce(" 20 ", :integer)
{:ok, 20}
iex> coerce(" x", :integer)
{:invalid, 0}
iex> coerce(" TRue ", :boolean)
{:ok, true}
iex> coerce(" T ", :boolean)
{:ok, true}
iex> coerce(" y ", :boolean)
{:ok, true}
iex> coerce(" Yes ", :boolean)
{:ok, true}
iex> coerce(" 1 ", :boolean)
{:ok, true}
iex> coerce(" TRu ", :boolean)
{:invalid, false}
iex> coerce(" F ", :boolean)
{:ok, false}
iex> coerce(" N ", :boolean)
{:ok, false}
iex> coerce(" 0 ", :boolean)
{:ok, false}
iex> coerce(" hello ", :string)
{:ok, "hello"}
iex> coerce(" ", :string)
{:blank, ""}
iex> coerce(true, :string)
{:ok, "true"}
iex> coerce(10.5, :string)
{:ok, "10.5"}
"""
# Integer
def coerce(value, :integer) when is_integer(value), do: {:ok, value}
def coerce(value, :integer) when is_float(value), do: {:ok, Kernel.round(value)}
def coerce(value, :integer) when is_bitstring(value) do
value
|> String.trim
|> coerce(:integer, :trimmed)
end
def coerce(_value, :integer), do: {:invalid, 0}
# Boolean
def coerce(value, :boolean) when is_boolean(value), do: {:ok, value}
def coerce(value, :boolean) when is_bitstring(value) do
value
|> String.trim
|> coerce(:boolean, :trimmed)
end
def coerce(_value, :boolean), do: {:invalid, false}
# String
def coerce(value, :string) when is_integer(value), do: coerce(Integer.to_string(value), :string)
def coerce(value, :string) when is_float(value), do: coerce(Float.to_string(value), :string)
def coerce(_value = true, :string), do: coerce("true", :string)
def coerce(_value = false, :string), do: coerce("false", :string)
def coerce(value, :string) when is_nil(value), do: coerce("", :string)
def coerce(value, :string) when is_bitstring(value) do
value
|> String.trim
|> coerce(:string, :trimmed)
end
def coerce(_value, :string), do: {:invalid, ""}
#
# Private functions
#
# Integer from a trimmed string
defp coerce(_value = "", :integer, :trimmed), do: {:blank, 0}
defp coerce(value, :integer, :trimmed) do
Integer.parse(value, 10)
|> coerce(:integer, :parsed)
end
# From the result of `Integer.parse`
defp coerce(:error, :integer, :parsed), do: {:invalid, 0}
defp coerce({int, ""}, :integer, :parsed), do: {:ok, int}
defp coerce({int, _}, :integer, :parsed), do: {:invalid, int}
# From a trimmed string
defp coerce(_value = "", :string, :trimmed), do: {:blank, ""}
defp coerce(value, :string, :trimmed), do: {:ok, value}
# Parse boolean from trimmed string
defp coerce(value, :boolean, :trimmed), do: coerce(String.downcase(value), :boolean, :downcased)
# Parse boolean from trimmed, downcased string
defp coerce(_value = "true", :boolean, :downcased), do: {:ok, true}
defp coerce(_value = "false", :boolean, :downcased), do: {:ok, false}
defp coerce(_value = "t", :boolean, :downcased), do: {:ok, true}
defp coerce(_value = "f", :boolean, :downcased), do: {:ok, false}
defp coerce(_value = "yes", :boolean, :downcased), do: {:ok, true}
defp coerce(_value = "no", :boolean, :downcased), do: {:ok, false}
defp coerce(_value = "y", :boolean, :downcased), do: {:ok, true}
defp coerce(_value = "n", :boolean, :downcased), do: {:ok, false}
defp coerce(_value = "on", :boolean, :downcased), do: {:ok, true}
defp coerce(_value = "off", :boolean, :downcased), do: {:ok, false}
defp coerce(_value = "1", :boolean, :downcased), do: {:ok, true}
defp coerce(_value = "0", :boolean, :downcased), do: {:ok, false}
defp coerce(_value = "", :boolean, :downcased), do: {:blank, false}
defp coerce(_value, :boolean, :downcased), do: {:invalid, false}
end