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