Enviable - Robust environment variable conversion

Enviable is a small collection of functions to make working with environment variables easier when configuring Elixir projects. It is designed to work configuration environment loaders like Dotenvy and provides robust value conversion like jetenv.

This has been published several times over the last few weeks, but I just completed a new feature (delimited list conversion) and figure that it’s worth sharing with the community now. This works best when using “12 Factor” configuration via environment variable.

# config/runtime.exs
import Config
import Enviable

client = fetch_env!("CLIENT")
Dotenvy.source([".env", ".env.#{client}", get_env()])

# Before
#
# config :my_app,
#   key: System.fetch_env!("SECRET_KEY"),
#   port: System.fetch_env!("PORT") |> String.to_integer(),
#   ssl: System.get_env("SSL_ENABLED") in ~w[1 true]

# After
config :my_app,
  key: fetch_env!("SECRET_KEY"),
  port: fetch_env_as_integer!("PORT"),
  ssl: get_env_as_boolean("SSL_ENABLED")

Because this is intended for use at configuration startup in a controlled environment, it offers options which would normally be considered unsafe, such as unconstrained atom conversion (fetch_env_as_atom!) while encouraging developers to consider safer options:

# This is allowed but is not recommended.
fetch_env_as_atom!("MY_ATOM_VALUE")
# This is allowed but can still provide unexpected values.
fetch_env_as_safe_atom!("MY_ATOM_VALUE")

# This is better than either of the above—and works as `atom` or `safe_atom` variants:
fetch_env_as_atom!("MY_ATOM_VALUE", allowed: [:red, :green, :blue])

It has been heavily influenced by jetenv for the breadth of conversion, but I have extended its conversion range substantially.

I push for 100% coverage of all branches, and have special attention paid to negative cases. I’m not currently using property checking, but it may be the next thing added. I’ve also tried to make sure that the documentation is comprehensive for all of the supported options, to the point where there’s 30% more @doc lines than there are code lines with doctests for most functions.

10 Likes

I just released Enviable v1.5, which is mostly a bugfix release, but added a couple of minor features.

I’m not sure when (or if) Enviable 2 will be released, but when it does, the default behaviour of boolean conversion will be to be case-insensitive. To make that transition easier, I added a compile-time option (:enviable, :boolean_downcase) that can be set to :default to match the eventual default and keep your fetch_env_as_boolean! calls simpler).

Although get_env_as_json and related functions documented that the default engine could be configured with :enviable, :json_engine, the code supporting that was incorrect in two ways: it was being evaluated at runtime, not at compile time, and it was using :enviable, :json instead of :enviable, :json_engine. This has been fixed and specifying the JSON engine now allows for an MFA tuple either as a parameter or as a compile configuration.

Changelog

  • Fixed a bug with list conversion for get_env_as_list and get_env_as where support for a :default value was not included.

  • Fixed a bug with :downcase conversions and nil values.

  • Added a compile-time configuration option to change the default boolean :downcase option. The default value is currently false (do not downcase). The next major version of Enviable will change this to :default, as it should not matter whether the matched value is true, TRUE, or True for boolean tests.

  • Added :upcase option to atom and safe_atom conversions.

  • Fixed :json_engine configuration so that it is properly compile-time and referenced. The JSON parsing code was looking this up at runtime under the wrong key.

  • Added support for {m, f, a} specification for :json_engine configuration or the :engine parameter for JSON conversion.

3 Likes