Home

Awesome

Mahaul

hex.pm hex.pm Coverage Status

Parse and validate your environment variables easily in Elixir with the following benefits.

Screenshot

Read more for understanding why to use this package and its benefits. The complete documentation for mahaul is available online at HexDocs.

Requirements

Elixir 1.13+

Installation

def deps do
  [
    {:mahaul, "~> 0.6.0"}
  ]
end
config :mahaul, mix_env: Mix.env()

Getting started

Define the environment variables needed in your app

defmodule MyApp.Env do
  use Mahaul,
    DEPLOYMENT_ENV: [
      type: :enum,
      defaults: [dev: "dev", test: "dev"],
      choices: [:dev, :staging, :live]
    ],
    PORT: [type: :port, defaults: [dev: "4000"]],
    DATABASE_URL: [type: :uri, defaults: [dev: "postgresql://user:pass@localhost:5432/app_dev"]],
    ANOTHER_ENV: [type: :host, default: "//localhost"]
end

Use in config/runtime.exs

import Config

if config_env() == :dev do
  MyApp.Env.validate()

  config :my_app, MyApp.Endpoint,
    http: [port: MyApp.Env.port()]
end

if config_env() == :prod do
  MyApp.Env.validate!()

  config :my_app, MyApp.Repo,
    url: MyApp.Env.database_url()

  config :my_app, :something,
    another: MyApp.Env.another_env()

  if MyApp.Env.deployment_env() == :staging do
    # configure something more
  end
end

Use anywhere in the code

defmodule MyApp.Magic
  def do_magic(env \\ MyApp.Env.deployment_env())
  def do_magic(:live), do: "Magic on live"
  def do_magic(:staging), do: "Magic on staging"
  def do_magic(:dev), do: "Magic on dev"
end

Supported types

The following type configurations are supported.

TypeElixir TypeValid Environment variables value
:strStringAny string
:enumAtomAny string
:numFloatAny string that can be parsed as float
:intIntegerAny string that can be parsed as integer
:boolBoolean"true" and "1" as true; "false" and "0" as false
:portIntegerAny valid port value between "1" to "65535" (casted as integer)
:hostStringAny valid host name
:uriStringAny valid uris

Setting Defaults

Any defaults and fallback values can be set globally using the default or for any mix environment using the defaults configuration options. Make sure to use the string values same as we set in the actual system environment, as it will be parsed depending upon the provided type configuration.

Globally

defmodule MyApp.Env do
  use Mahaul,
    MY_ENV: [type: :str, default: "Hello World"]
end
iex -S mix
iex> MyApp.Env.my_env()
Hello World

MY_ENV="Hello Universe" iex -S mix
iex> MyApp.Env.my_env()
Hello Universe

For any mix environment

defmodule MyApp.Env do
  use Mahaul,
    MY_ENV: [
      type: :str,
      defaults: [prod: "Hello Prod", dev: "Hello Dev", test: "Hello Test", custom: "Hello Custom"]
    ]
end
MIX_ENV=prod iex -S mix
iex> MyApp.Env.my_env()
Hello Prod

MIX_ENV=dev iex -S mix
iex> MyApp.Env.my_env()
Hello Dev

MIX_ENV=test iex -S mix
iex> MyApp.Env.my_env()
Hello Test

MIX_ENV=custom iex -S mix
iex> MyApp.Env.my_env()
Hello Custom

MIX_ENV=prod MY_ENV="Hello World" iex -S mix
iex> MyApp.Env.my_env()
Hello World

For any mix environment with fallback

defmodule MyApp.Env do
  use Mahaul,
    MY_ENV: [
      type: :str,
      default: "Hello World",
      defaults: [prod: "Hello Prod", dev: "Hello Dev", test: "Hello Test"]
    ]
end
MIX_ENV=prod iex -S mix
iex> MyApp.Env.my_env()
Hello Prod

MIX_ENV=dev iex -S mix
iex> MyApp.Env.my_env()
Hello Dev

MIX_ENV=test iex -S mix
iex> MyApp.Env.my_env()
Hello Test

MIX_ENV=custom iex -S mix
iex> MyApp.Env.my_env()
Hello World

MIX_ENV=custom MY_ENV="Hello Universe" iex -S mix
iex> MyApp.Env.my_env()
Hello Universe

Setting choices list

You can further restrict the parsed values to a predefined list by setting the choices option with list of allowed values. Note that the values are parsed first and then matched against the provided list.

defmodule MyApp.Env do
  use Mahaul,
    DEPLOYMENT_ENV: [type: :enum, choices: [:dev, :staging, :live]],
    DAY_OF_WEEK: [type: :int, choices: [1, 2, 3, 4, 5, 6, 7]]
end

Setting documentation

There is a default documentation added for each of the compile time generated function equivalents for the environment variables. However you may use the doc option to add a custom documentation with more details and explanations as per your needs.

Default

defmodule MyApp.Env do
  use Mahaul,
    PORT: [type: :port, defaults: [dev: "4000"]]
end

Documentation-default

Custom

defmodule MyApp.Env do
  use Mahaul,
    PORT: [
      type: :port,
      defaults: [dev: "4000"],
      doc: ~s"""
      This is an example documentation for the environment variable.
      We can use all the features from the elixir `@doc` tag that will
      appear as the function documentation during its usage.

      ## Usage

          Env.port()

      ## Notes
      These are some notes to keep in mind.
      """
    ]
end

Documentation-custom

Why this package

mahaul accomplishes the following functionalities for streamlining the environment variables requirements for an elixir app.

Compile time access guarantees

Using the meta programming capabilities of Elixir, mahaul creates compile time methods for accessing the environment variables. This guarantees that there are no accidental typos during the access of the environment variables from the code. Also as an added bonus, if you are using the Elixir Language Server for your development environment, you'd get code autocompletion.

Parsed values with accurate data types

Depending upon the configuration, the access to the predefined environment variables string values are parsed and the correct elixir data types are returned. mahaul supports a wide range of commonly set environment variable types. It also supports the choices options to limit the allowed values for an environment variable.

Validation of required values before app boot

Often times we release new versions of the app accessing new environment variables, but we forget to set those for one of our app deployments. This creates nasty bugs that are only discovered when certain parts of the app behaves erratically or fails. With mahaul, you can pre-validate the existence of the required environment variables with correct values before booting the app (ideally in config/runtime.exs). This ensures that your application will fail to boot unless you have set those environment variables with correct values. This works really well with any cloud deployments that makes new version of your app active and available only after ensuring the new deployment had a successful boot.

Defaults and fallbacks

You can set default values for the production or development environment of your app while configuring mahaul. This comes handy when you want some defaults for dev/test environment to let other contributors of your app quickly start the dev environment of your app without worrying to set some needed environment variables. Or have some sensible defaults for production version of your app with flexibility to change the values by setting an environment variable.

Contributing

Contributions are welcome. Please follow the commit guidelines from https://www.conventionalcommits.org.

Setup

Clone the repo and fetch its dependencies:

git clone https://github.com/emadalam/mahaul.git
cd mahaul
mix setup

Running tests

mix test

# or with coverage threshold
# mix coveralls

Building docs

MIX_ENV=docs mix docs

# or view the docs
# MIX_ENV=docs mix docs --open

LICENSE

See LICENSE