defmodule EnvGuard.Cast do
@moduledoc """
Defines functions that cast values to a specific type.
"""
@type type :: EnvGuard.Types.type()
@doc """
Takes in a string value and casts it to the specified type.
Returns an error if the cast is not possible.
"""
@spec cast(any(), type) :: {:ok, any()} | {:error, :cast_fail, String.t()}
def cast(value, :boolean) when is_binary(value) do
cond do
value in ["true", "TRUE", "1"] -> {:ok, true}
value in ["false", "FALSE", "0"] -> {:ok, false}
true -> {:error, :cast_fail, "boolean expected, got '#{value}'."}
end
end
def cast(value, :string) when is_binary(value) do
{:ok, value}
end
def cast(value, :atom) when is_binary(value) do
{:ok, String.to_atom(value)}
end
def cast(value, :charlist) when is_binary(value) do
{:ok, to_charlist(value)}
end
def cast(value, :integer) when is_binary(value) do
case Integer.parse(value) do
{int, ""} -> {:ok, int}
_ -> {:error, :cast_fail, "integer expected, got '#{value}'"}
end
end
def cast(value, :float) when is_binary(value) do
case Float.parse(value) do
{float, ""} -> {:ok, float}
_ -> {:error, :cast_fail, "float expected, got '#{value}'"}
end
end
def cast(value, {:list, type}) when is_binary(value) do
value
|> String.trim()
|> String.split(",")
|> case do
[""] -> {:ok, []}
list -> cast_list(list, type)
end
end
def cast(value, {:enum, options}) when is_binary(value) do
if value in options do
{:ok, value}
else
{:error, :cast_fail, "'#{value}' not in enum #{inspect(options)}"}
end
end
def cast(value, type) when not is_binary(value) do
{:error, :cast_fail, "binary expected for #{inspect(type)}, got #{inspect(value)}"}
end
@spec cast_list([String.t()], type) :: {:ok, [any()]} | {:error, :cast_fail, String.t()}
defp cast_list(values, type) do
values
|> Enum.reduce_while({:ok, []}, fn value, {:ok, acc} ->
case cast(value, type) do
{:ok, casted} -> {:cont, {:ok, [casted | acc]}}
error -> {:halt, error}
end
end)
|> case do
{:ok, list} -> {:ok, Enum.reverse(list)}
error -> error
end
end
end