Envious.Env (envious v1.4.1)

Copy Markdown View Source

Functions for working with environment variables in configuration files.

This module provides convenient functions for extracting and converting environment variables from System.get_env/1. These functions are designed to work seamlessly with Envious for loading .env files into the system environment.

Typical Workflow

# Load .env file into system environment
".env" |> File.read!() |> Envious.parse!() |> System.put_env()

# Use in config/runtime.exs
use Envious  # imports both Envious and Envious.Env

config :my_app, MyApp.Repo,
  url: required!("DATABASE_URL"),
  pool_size: optional("POOL_SIZE", "10") |> integer!()

config :my_app,
  port: required!("PORT") |> integer!(),
  host: optional("HOST", "localhost"),
  enabled: optional("ENABLED", "true") |> boolean!(),
  log_level: optional("LOG_LEVEL", "info") |> atom!(),
  allowed_origins: optional("ALLOWED_ORIGINS", "http://localhost") |> list!(),
  timeout: optional("TIMEOUT", "30s") |> interval!(:milliseconds)

Value Extraction

The module provides three functions for extracting environment variable values:

Note that empty strings ("") are considered valid values and will not trigger defaults or raise errors.

Type Conversions

All type conversion functions follow the bang (!) convention and will raise an ArgumentError if the value cannot be converted. This fail-fast approach is appropriate for configuration that should be validated at application startup.

Error Handling

The module raises two types of errors:

Available converters:

  • integer!/1 - Convert to integer
  • float!/1 - Convert to float
  • boolean!/1 - Convert to boolean (handles "true", "false", "1", "0", "yes", "no", "on", "off")
  • atom!/1 - Convert to existing atom (safe, won't create new atoms)
  • list!/1 - Split string into list (comma-separated by default)
  • list!/2 - Split string and transform each element
  • interval!/2 - Parse time interval strings like "30s", "5m", "2h"
  • uri!/1 - Parse URI string
  • ip!/1 - Parse IP address (IPv4 or IPv6) into tuple

Examples

# Required value (raises if missing)
required!("DATABASE_URL")
#=> "postgresql://localhost/mydb"

# Optional with default
optional("PORT", "4000")
#=> "4000"

# Optional without default (returns nil if missing)
optional("OPTIONAL_FEATURE")
#=> nil

# Type conversions
optional("PORT", "4000") |> integer!()
#=> 4000

optional("ENABLED", "true") |> boolean!()
#=> true

optional("LOG_LEVEL", "info") |> atom!()
#=> :info

optional("CORS_ORIGINS", "http://localhost,https://example.com") |> list!()
#=> ["http://localhost", "https://example.com"]

# Custom list transformation
optional("PORTS", "4000,4001,4002") |> list!(&integer!/1)
#=> [4000, 4001, 4002]

# Time intervals (defaults to milliseconds like :timer)
optional("TIMEOUT", "30s") |> interval!()
#=> 30000

optional("CACHE_TTL", "5m") |> interval!(:seconds)
#=> 300

Summary

Functions

Converts a string value to an existing atom.

Converts a string value to a boolean.

Converts a string value to a float.

Converts a string value to an integer.

Parses a time interval string and converts it to the specified unit.

Parses an IP address string into a tuple.

Splits a string into a list using a delimiter.

Converts a string module name to a module atom.

Gets an environment variable value, returning nil if not set.

Gets an environment variable value, returning a default if not set.

Gets an environment variable value, raising if not set.

Parses a URI string into a URI struct.

Functions

atom!(value)

@spec atom!(String.t() | nil) :: atom()

Converts a string value to an existing atom.

This function uses String.to_existing_atom/1 to safely convert strings to atoms without creating new atoms dynamically, which prevents potential memory leaks.

Raises an ArgumentError if the atom does not already exist.

Examples

# Atom exists
:info
atom!("info")
#=> :info

# Atom doesn't exist
atom!("nonexistent_atom_12345")
#=> ** (ArgumentError) atom "nonexistent_atom_12345" does not exist

boolean!(value)

@spec boolean!(String.t() | nil) :: boolean()

Converts a string value to a boolean.

Recognizes common boolean representations (case-insensitive):

  • true: "true", "1", "yes", "on"
  • false: "false", "0", "no", "off"

Raises an ArgumentError if the value is not a recognized boolean string.

Examples

boolean!("true")
#=> true

boolean!("FALSE")
#=> false

boolean!("1")
#=> true

boolean!("0")
#=> false

boolean!("yes")
#=> true

boolean!("no")
#=> false

boolean!("on")
#=> true

boolean!("off")
#=> false

boolean!("maybe")
#=> ** (ArgumentError) could not convert "maybe" to boolean

float!(value)

@spec float!(String.t() | nil) :: float()

Converts a string value to a float.

Raises an ArgumentError if the value cannot be parsed as a float.

Examples

float!("3.14")
#=> 3.14

float!("-2.5")
#=> -2.5

float!("42")
#=> 42.0

float!("not a number")
#=> ** (ArgumentError) could not convert "not a number" to float

integer!(value)

@spec integer!(String.t() | nil) :: integer()

Converts a string value to an integer.

Raises an ArgumentError if the value cannot be parsed as an integer.

Examples

integer!("42")
#=> 42

integer!("-100")
#=> -100

integer!("not a number")
#=> ** (ArgumentError) could not convert "not a number" to integer

interval!(value)

@spec interval!(String.t() | nil) :: integer()

Parses a time interval string and converts it to the specified unit.

Defaults to :milliseconds to match Erlang's :timer module convention.

Supported input formats:

  • "300" - plain number (treated as milliseconds)
  • "30s" - seconds
  • "5m" - minutes
  • "2h" - hours
  • "1d" - days
  • "500ms" - milliseconds

Supported output units:

  • :milliseconds (default)
  • :seconds
  • :minutes
  • :hours
  • :days

Examples

# Plain numbers default to milliseconds (like :timer.sleep/1)
interval!("300")
#=> 300

interval!("30s")
#=> 30000

interval!("5m")
#=> 300000

# Custom units
interval!("5m", :seconds)
#=> 300

interval!("2h", :minutes)
#=> 120

interval!("1d", :hours)
#=> 24

interval!("invalid")
#=> ** (ArgumentError) could not parse interval "invalid"

interval!(value, unit)

@spec interval!(String.t() | nil, atom()) :: integer()

ip!(value)

@spec ip!(String.t() | nil) :: :inet.ip_address()

Parses an IP address string into a tuple.

Handles both IPv4 and IPv6 addresses. This is commonly used in Phoenix endpoint configuration for binding to specific IP addresses.

Raises an ArgumentError if the IP address cannot be parsed.

Examples

# IPv4
ip!("127.0.0.1")
#=> {127, 0, 0, 1}

ip!("0.0.0.0")
#=> {0, 0, 0, 0}

# IPv6
ip!("::1")
#=> {0, 0, 0, 0, 0, 0, 0, 1}

ip!("2001:db8::8a2e:370:7334")
#=> {8193, 3512, 0, 0, 0, 35374, 880, 29492}

# Invalid
ip!("not an ip")
#=> ** (ArgumentError) could not parse IP address "not an ip"

# Common Phoenix usage
config :my_app, MyAppWeb.Endpoint,
  http: [ip: required!("HTTP_IP") |> ip!(), port: 4000]

list!(value)

@spec list!(String.t() | nil) :: [String.t()]

Splits a string into a list using a delimiter.

Accepts options:

  • :delimiter - The string to split on (default: ",")
  • :trim - Whether to trim whitespace from each element (default: true)

Can also accept a transformer function as the second argument to transform each element after splitting. When using a transformer, options can be passed as the third argument.

Examples

list!("a,b,c")
#=> ["a", "b", "c"]

list!("foo, bar, baz")
#=> ["foo", "bar", "baz"]

list!("x:y:z", delimiter: ":")
#=> ["x", "y", "z"]

list!(" a , b , c ", trim: false)
#=> [" a ", " b ", " c "]

list!("")
#=> [""]

# With transformer function
list!("1,2,3", &integer!/1)
#=> [1, 2, 3]

list!("true,false,true", &boolean!/1)
#=> [true, false, true]

# With transformer and options
list!("1:2:3", &integer!/1, delimiter: ":")
#=> [1, 2, 3]

list!(value, opts)

@spec list!(String.t() | nil, keyword() | (String.t() -> any())) ::
  [String.t()] | [any()]

list!(value, transformer, opts)

@spec list!(String.t() | nil, (String.t() -> any()), keyword()) :: [any()]

module!(value)

@spec module!(String.t() | nil) :: module()

Converts a string module name to a module atom.

This function splits the string on "." and uses String.to_existing_atom/1 for each part, then concatenates them with Module.concat/1. This safely converts module names without creating new atoms.

Raises an ArgumentError if any part of the module name does not exist as an atom (typically meaning the module hasn't been compiled/loaded).

Examples

module!("Swoosh.Adapters.Mailgun")
#=> Swoosh.Adapters.Mailgun

module!("String")
#=> String

module!("NonExistent.Module")
#=> ** (ArgumentError) module "NonExistent.Module" does not exist

optional(key)

@spec optional(String.t()) :: String.t() | nil

Gets an environment variable value, returning nil if not set.

This is useful when you want to distinguish between an unset variable and an empty string, or when nil is an acceptable value in your configuration.

Examples

# Variable is set
System.put_env("MY_VAR", "value")
optional("MY_VAR")
#=> "value"

# Variable is not set
System.delete_env("MY_VAR")
optional("MY_VAR")
#=> nil

# Variable is set to empty string
System.put_env("MY_VAR", "")
optional("MY_VAR")
#=> ""

optional(key, default)

@spec optional(String.t(), String.t()) :: String.t()

Gets an environment variable value, returning a default if not set.

Note that empty strings are considered valid values and will not trigger the default. Only nil (variable not set) will cause the default to be used.

Examples

# Variable is set
System.put_env("MY_VAR", "value")
optional("MY_VAR", "default")
#=> "value"

# Variable is not set
System.delete_env("MY_VAR")
optional("MY_VAR", "default")
#=> "default"

# Variable is set to empty string
System.put_env("MY_VAR", "")
optional("MY_VAR", "default")
#=> ""

required!(key)

@spec required!(String.t()) :: String.t()

Gets an environment variable value, raising if not set.

Raises a System.EnvError if the environment variable is not set or is nil.

Examples

# Variable is set
System.put_env("MY_VAR", "value")
required!("MY_VAR")
#=> "value"

# Variable is not set
System.delete_env("MY_VAR")
required!("MY_VAR")
#=> ** (System.EnvError) could not fetch environment variable "MY_VAR" because it is not set

uri!(value)

@spec uri!(String.t() | nil) :: URI.t()

Parses a URI string into a URI struct.

Raises an ArgumentError if the URI cannot be parsed.

Examples

uri!("https://example.com:8080/path?query=value")
#=> %URI{
#=>   scheme: "https",
#=>   host: "example.com",
#=>   port: 8080,
#=>   path: "/path",
#=>   query: "query=value"
#=> }

uri!("postgresql://localhost/mydb")
#=> %URI{scheme: "postgresql", host: "localhost", path: "/mydb", ...}