defmodule EnvGuard.Constraints do @moduledoc """ Defines functions that can be used to check constraints on environment variables. """ @type constraint :: EnvGuard.Types.constraint() @type type :: EnvGuard.Types.type() @spec check_constraints(any(), type, [constraint]) :: {:ok, any()} | {:error, :constraint_violation, String.t()} def check_constraints(value, type, constraints) do Enum.reduce_while(constraints, {:ok, value}, fn constraint, {:ok, value} -> # ...> if x < 5 do # ...> {:cont, acc + x} # ...> else # ...> {:halt, acc} # ...> end case check(value, type, constraint) do {:ok, value} -> {:cont, {:ok, value}} err -> {:halt, err} end end) end @spec check(any(), type, constraint) :: {:ok, any()} | {:error, :constraint_violation, String.t()} def check(value, {:list, _type}, {:length, length}) do if Enum.count(value) == length do {:ok, value} else {:error, :constraint_violation, "List length is not equal to #{length}"} end end def check(value, {:list, _type}, {:min_length, length}) do if Enum.count(value) >= length do {:ok, value} else {:error, :constraint_violation, "List length is less than #{length}"} end end def check(value, {:list, _type}, {:max_length, length}) do if Enum.count(value) <= length do {:ok, value} else {:error, :constraint_violation, "List length is greater than #{length}"} end end def check(value, :integer, {:min, minimum}) do if value >= minimum do {:ok, value} else {:error, :constraint_violation, "integer is less than than #{minimum}"} end end def check(value, :integer, {:max, maximum}) do if value <= maximum do {:ok, value} else {:error, :constraint_violation, "integer is larger than than #{maximum}"} end end def check(value, :string, {:allow_empty?, allow_empty?}) do if trimmed_length(value) == 0 and not allow_empty? do {:error, :constraint_violation, "string is empty"} else {:ok, value} end end def check(value, :string, {:max_length, max}) do if trimmed_length(value) > max do {:error, :constraint_violation, "string is longer than #{max} characters"} else {:ok, value} end end def check(value, :string, {:min_length, min}) do if trimmed_length(value) < min do {:error, :constraint_violation, "string is shorter than #{min} characters"} else {:ok, value} end end def check(_value, type, constraint) do {:error, :constraint_violation, "constraint #{inspect(constraint)} not supported for type #{inspect(type)}"} end @spec trimmed_length(String.t()) :: non_neg_integer() defp trimmed_length(value) do value |> String.trim() |> String.length() end end