BoxTurtle.Runtime.Config.Helper (BoxTurtle v0.1.0)

Copy Markdown View Source

Reads environment variables and parses them into typed, validated Elixir values, so config files don't need their own parsing and validation.

Example

# config/runtime.exs
import Config
alias BoxTurtle.Runtime.Config.Helper

config :my_app, MyApp.Repo,
  # "20" -> 20; must be 1..100; 10 when POOL_SIZE is unset
  pool_size: Helper.get_env("POOL_SIZE", type: :integer, default: 10, in_range: 1..100)

config :my_app,
  # "debug" -> :debug; only these existing atoms are accepted
  log_level: Helper.get_env("LOG_LEVEL", type: :atom, in_set: [:debug, :info, :warning], default: :info),
  # "a.example.com, b.example.com" -> ["a.example.com", "b.example.com"]
  hosts: Helper.get_env("HOSTS", type: :list, default: ["localhost"]),
  # "localhost,5432" -> {"localhost", 5432}, one subtype per element
  db_endpoint: Helper.get_env("DB_ENDPOINT", type: :tuple, subtype: {:string, :integer}),
  # ~s({"beta": true}) -> %{"beta" => true}
  features: Helper.get_env("FEATURES", type: :map, default: %{})

An unset variable gives the :default, or nil when there is none. With a validation (:in_set, :in_range, :regex, :custom) and no :default, an unset variable fails validation, so the variable is effectively required.

A value that fails to parse or validate is returned as {:error, message}, not raised. Check for it where a bad value must stop the boot:

case Helper.get_env("PORT", type: :integer, in_range: 1..65_535, default: 4000) do
  {:error, message} -> raise "invalid PORT: #{message}"
  port -> config :my_app, MyAppWeb.Endpoint, http: [port: port]
end

Valid options:

type:

:atom - The string read from the environment is converted to an atom.
:boolean - The string read from the environment must be in the Elixir set [true, false]
:charlist - The environment variable is converted to an erlang-compatible character list.
:float - The env variable is converted to a float.
:integer - The env variable is converted to an integer.
:module - The env variable is interpreted as an existing (loaded) module name.
:string - The env variable is left as a raw string (elixir binary()).
:list - The env variable is interpreted as a comma-separated list of values of the specified subtype (default: :string)
:tuple - The env variable is interpreted as a comma-separated tuple of values of the specified subtype (default: :string)
:map - The env variable is decoded as a JSON object (with `JSON.decode/1`) into a map with string keys.

subtype: For lists and tuples, a subtype may be specified indicating the type(s) of elements they are expected to contain.

:atom - the resulting list/tuple will be a set of atoms.
:boolean - the resulting list/tuple will be a set of boolean values.
:charlist - the resulting list/tuple will be a set of charlist elements (list of lists).
:float - the resulting list/tuple will be a set of floating point values.
:integer - the resulting list/tuple will be a set of integers.
:module - the resulting list/tuple will be a set of loaded modules.
:string - the resulting list/tuple will be a set of strings.
:map - the resulting list/tuple will be a set of map() objects.
tuple() - valid only for a type of tuple, the subtype of each element in the tuple can be expressed separately for
  each element in the tuple.  Note that the size of the subtype tuple must be the same as the number of elements
  expressed in the comma-separated values of the environment variable parsed.
list() - the subtype of each element in the tuple or list can be expressed separately for each element in the
  list or tuple.  Note that the size of the subtype list must be the same as the number of elements
  expressed in the comma-separated values of the environment variable parsed.

default: any() The default value returned when the specified variable is not provided in the system environment. in_set: list() The value parsed from the environment variable must be in the specified list of values of the same

type.

in_range: range() The value parsed from the environment must be within the specified range of values. regex: /regex/ pattern the (type: :string) variable must match. custom: (tuple(): {Module, validator/2}) A module and function that takes a value and helper options for custom

validation of the value.

required: (boolean()), when true a value must be provided in the environment. A nil generates an {:error, msg} result. unsafe_atoms: (boolean()), :atom and :module values must name existing atoms unless this is true. Atoms are never

garbage collected, so only enable it for trusted environments.

Summary

Functions

Logs an inspection of the given value, optionally preceded by a message, and returns the value unchanged. Used to place inspections in the midst of a chain of piped calls.

Reads the environment variable var_name, parses it into the requested type and validates it.

Functions

debug(val, message \\ nil, opts \\ [])

@spec debug(any(), String.t() | nil, keyword()) :: any()

Logs an inspection of the given value, optionally preceded by a message, and returns the value unchanged. Used to place inspections in the midst of a chain of piped calls.

Nothing is logged unless this module is listed in the application's :debug_enabled_modules setting.

Options

  • :condition - a function/1 receiving the value; the message is only logged when it returns true. Useful for throttling messages in highly repetitive code (log only a given id, a matching name, etc.).

Examples

# Inside a module that does `use BoxTurtle.Debug.PipeDebug` and is listed in
# config :my_app, debug_enabled_modules: [...]
%{jazz: "Art", messenger: "Blakey"}
|> Map.values()
|> debug("Jazz Message")          # logs: Jazz Message: ["Art", "Blakey"]
|> Enum.reverse()                 # returns ["Blakey", "Art"]

get_env(var_name, opts \\ [])

@spec get_env(binary(), keyword()) :: {:error, binary()} | any()

Reads the environment variable var_name, parses it into the requested type and validates it.

Returns the parsed value, the :default (or nil) when the variable is unset, or {:error, message} when parsing or validation fails. See the module documentation for the options.

Helper.get_env("POOL_SIZE", type: :integer, default: 10, in_range: 1..100)