defmodule EnvGuard.Macros do @moduledoc """ Macros for declaring environment variables in `runtime.exs`. Import these into your config to fetch, cast, and validate environment variables in a single expression: import Config require EnvGuard.Macros import EnvGuard.Macros if config_env() == :prod do secret_key_base = required("SECRET_KEY_BASE", :string, min_length: 64) phx_server = optional("PHX_SERVER", :boolean, false) end See `EnvGuard.Types` for the supported types and constraints. """ @doc """ Fetches a required environment variable, casting it to `type` and checking `constraints`. Returns the cast value. Raises a `RuntimeError` if the variable is not set, cannot be cast to `type`, or violates one of the `constraints`. ## Examples secret_key_base = required("SECRET_KEY_BASE", :string, min_length: 64) pool_size = required("POOL_SIZE", :integer, min: 1, max: 100) """ defmacro required(env_name, type, constraints \\ []) do quote do case EnvGuard.test_var(unquote(env_name), unquote(type), unquote(constraints)) do {:ok, value} -> value {:error, :env_var_not_set} -> raise "Environment variable #{unquote(env_name)} is not set" {:error, :invalid_type, err} -> raise "Environment variable #{unquote(env_name)} is not of type #{inspect(unquote(type))}. #{err}" {:error, :constraint_violation, err} -> raise "Environment variable #{unquote(env_name)} does not meet constraints. #{err}" end end end @doc """ Fetches an optional environment variable, falling back to `default`. When the variable is set, it is cast to `type` and checked against `constraints`, and the cast value is returned. When it is not set — or it is set but fails casting or a constraint — `default` is returned instead. In the failure cases a warning is logged via `Logger` before falling back. ## Examples phx_server = optional("PHX_SERVER", :boolean, false) log_level = optional("LOG_LEVEL", {:enum, ["debug", "info", "warning"]}, "info") """ defmacro optional(env_name, type, default, constraints \\ []) do quote do case EnvGuard.test_var(unquote(env_name), unquote(type), unquote(constraints)) do {:ok, value} -> value {:error, :env_var_not_set} -> unquote(default) {:error, :invalid_type, err} -> require Logger Logger.warning( "Environment variable #{unquote(env_name)} is set but not of type #{inspect(unquote(type))}. #{err} Falling back to default." ) unquote(default) {:error, :constraint_violation, err} -> require Logger Logger.warning( "Environment variable #{unquote(env_name)} is set but does not meet constraints. #{err} Falling back to default." ) unquote(default) end end end end