Current section
Files
Jump to
Current section
Files
lib/enviable.ex
defmodule Enviable do
@moduledoc """
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][].
### Usage
Enviable will typically be imported in `config/runtime.exs` after `Config`, but may be
used anywhere that environment variables are read.
```elixir
# 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")
```
[dotenvy]: https://hexdocs.pm/dotenvy/readme.html
[jetenv]: https://hexdocs.pm/jetenv/readme.html
"""
alias Enviable.Conversion
@doc """
Set an environment variable value only if it is not yet set. This is a convenience
wrapper around `System.put_env/2` and `System.get_env/2`.
### Examples
```elixir
iex> Enviable.put_env_new("PORT", "3000")
:ok
iex> Enviable.get_env("PORT")
"3000"
iex> Enviable.put_env_new("PORT", "5000")
:ok
iex> Enviable.get_env("PORT")
"3000"
```
"""
@spec put_env_new(String.t(), String.t()) :: :ok
def put_env_new(varname, value), do: put_env(varname, get_env(varname, value))
@doc """
Returns the value of an environment variable converted to the target `type` or a default
value if the variable is unset. If no `default` is provided, `nil` is returned (unless
converting to `:boolean`, which will return `false`).
Suported primitive conversions are:
- `:atom` (`t:Enviable.Conversion.convert_atom/0`, `get_env_as_atom/2`)
- `:boolean` (`t:Enviable.Conversion.convert_boolean/0`, `get_env_as_boolean/2`)
- `:charlist` (`t:Enviable.Conversion.convert_charlist/0`, `get_env_as_charlist/2`)
- `:elixir` (`t:Enviable.Conversion.convert_elixir/0`, `get_env_as_elixir/1`)
- `:erlang` (`t:Enviable.Conversion.convert_erlang/0`, `get_env_as_erlang/1`)
- `:float` (`t:Enviable.Conversion.convert_float/0`, `get_env_as_float/2`)
- `:integer` (`t:Enviable.Conversion.convert_integer/0`, `get_env_as_integer/2`)
- `:json` (`t:Enviable.Conversion.convert_json/0`, `get_env_as_json/2`)
- `:log_level` (`t:Enviable.Conversion.convert_log_level/0`, `get_env_as_log_level/2`)
- `:module` (`t:Enviable.Conversion.convert_module/0`, `get_env_as_module/2`)
- `:pem` (`t:Enviable.Conversion.convert_pem/0`, `get_env_as_pem/2`)
- `:safe_atom` (`t:Enviable.Conversion.convert_safe_atom/0`, `get_env_as_safe_atom/2`)
- `:safe_module` (`t:Enviable.Conversion.convert_safe_module/0`,
`get_env_as_safe_module/2`)
Supported encoded conversions are:
- `:base16` (`t:Enviable.Conversion.encoded_base16/0`)
- `:base32` (`t:Enviable.Conversion.encoded_base32/0`)
- `:base64`, `:url_base64` (`t:Enviable.Conversion.encoded_base64/0`)
- `:hex32` (`t:Enviable.Conversion.encoded_hex32/0`)
See `Enviable.Conversion` for supported type conversions and options.
### Value Conversion and Default Values
Value conversion will be applied only to values contained in the requested environment
variable. Default values are used only when the environment variable is unset. In this
way, there is a meaningful difference between an unset environment variable (where
`System.get_env/1` will return `nil`) and an empty environment variable (where
`System.get_env/1` returns `""`).
```console
$ FOO= elixir -e 'IO.inspect([foo: System.get_env("FOO"), bar: System.get_env("BAR")])'
[foo: "", bar: nil]
```
### Examples
```elixir
iex> Enviable.get_env_as("UNSET", :atom)
nil
iex> Enviable.get_env_as("UNSET", :float)
nil
iex> Enviable.get_env_as("UNSET", :base16)
nil
iex> Enviable.put_env("NAME", "get_env_as")
iex> Enviable.get_env_as("NAME", :atom)
:get_env_as
iex> Enviable.put_env("NAME", "GET_ENV_AS")
iex> Enviable.get_env_as("NAME", :safe_atom, downcase: true)
:get_env_as
iex> Enviable.get_env_as("FLOAT", :float, default: "3.5")
3.5
iex> Enviable.get_env_as("FLOAT", :float, default: 3.5)
3.5
iex> Enviable.put_env("FLOAT", "3")
iex> Enviable.get_env_as("FLOAT", :float)
3.0
iex> Enviable.put_env("FLOAT", "3.1")
iex> Enviable.get_env_as("FLOAT", :float)
3.1
iex> red = Base.encode16("RED", case: :lower)
iex> Enviable.put_env("NAME", red)
iex> Enviable.get_env_as("NAME", :base16, case: :lower)
"RED"
iex> Enviable.get_env_as("NAME", {:base16, :string}, case: :lower)
"RED"
iex> Enviable.get_env_as("NAME", {:base16, :atom}, case: :lower, downcase: true)
:red
```
"""
@doc since: "1.1.0"
@doc group: "Conversion"
@spec get_env_as(String.t(), Conversion.conversion(), keyword) :: nil | term()
def get_env_as(varname, type, opts \\ []) do
varname
|> get_env()
|> Conversion.convert_as(varname, type, opts)
end
atom_exhaustion =
"[Preventing atom exhaustion](https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/atom_exhaustion)"
atom_options = """
### Options
- `:allowed`: A list of `t:atom/0` values indicating permitted atoms and used as
a lookup table, if present. Any value not found will result in an exception.
- `:default`: The `t:atom/0` or `t:binary/0` value representing the atom value to use if
the environment variable is unset (`nil`). If the `:allowed` option is present, the
default value must be one of the permitted values.
- `:downcase`: See `t:Enviable.Conversion.opt_downcase/0`.
A shorthand for the `default` option may be provided as a `t:atom/0` value.
"""
@doc """
Returns the value of an environment variable converted to `t:atom/0` or a default value
if the variable is unset. If no `default` is provided, `nil` is returned.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `String.to_atom/1` and may result in atom exhaustion if
> used without the `:allowed` option. See #{atom_exhaustion} from the Security Working
> Group of the Erlang Ecosystem Foundation.
#{atom_options}
### Examples
```elixir
iex> Enviable.get_env_as_atom("UNSET")
nil
iex> Enviable.get_env_as_atom("UNSET", :default)
:default
iex> Enviable.get_env_as_atom("UNSET", default: :default)
:default
iex> Enviable.put_env("NAME", "get_env_as_atom")
iex> Enviable.get_env_as_atom("NAME")
:get_env_as_atom
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_atom(
String.t(),
atom()
| binary()
| [
{:allowed, list(atom())}
| {:default, atom() | binary()}
| Conversion.opt_downcase()
]
) :: nil | atom()
def get_env_as_atom(varname, opts \\ [])
def get_env_as_atom(varname, default) when is_atom(default) or is_binary(default),
do: get_env_as(varname, :atom, default: default)
def get_env_as_atom(varname, opts), do: get_env_as(varname, :atom, opts)
@doc """
Returns the value of an environment variable converted to an existing `t:atom/0` or
a default value if the variable is unset. If no `default` is provided, `nil` is
returned.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `String.to_existing_atom/1` which will result in an
> exception if the resulting atom is not already known and if used without the
> `:allowed` option. See #{atom_exhaustion} from the Security Working Group of the
> Erlang Ecosystem Foundation.
#{atom_options}
### Examples
```elixir
iex> Enviable.get_env_as_safe_atom("UNSET")
nil
iex> Enviable.get_env_as_safe_atom("UNSET", :default)
:default
iex> Enviable.get_env_as_safe_atom("UNSET", default: :default)
:default
iex> Enviable.put_env("NAME", "GET_ENV_AS_SAFE_ATOM")
iex> Enviable.get_env_as_safe_atom("NAME", downcase: true)
:get_env_as_safe_atom
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_safe_atom(
String.t(),
atom()
| binary()
| [
{:allowed, list(atom())}
| {:default, atom() | binary()}
| Conversion.opt_downcase()
]
) :: nil | atom()
def get_env_as_safe_atom(varname, opts \\ [])
def get_env_as_safe_atom(varname, default) when is_atom(default) or is_binary(default),
do: get_env_as(varname, :safe_atom, default: default)
def get_env_as_safe_atom(varname, opts), do: get_env_as(varname, :safe_atom, opts)
@doc """
Returns the value of an environment variable converted to a `t:boolean/0` value, or
a default value if the variable is unset. If no `default` is provided, `false` will be
returned.
This function will always result in a `t:boolean/0` value. Unless configured with
`truthy` or `falsy`, only the values `"1"` and `"true"` will be converted to `true` and
any other value will result in `false`.
### Options
- `:default`: the default value (which must be `true` or `false`) if the variable is
unset. In most cases, when `falsy` is provided, `default: true` should also be
provided.
- `:truthy`: a list of string values to be compared for truth values. If the value of
the environment variable matches these values, `true` will be returned; other values
will result in `false`. Mutually exclusive with `falsy`.
- `:falsy`: a list of string values to be compared for false values. If the value of the
environment variable matches these values, `false` will be returned; other values will
result in `true`. Mutually exclusive with `truthy`.
- `:downcase`: either `false` (the default), `true`, or the mode parameter for
`String.downcase/2` (`:default`, `:ascii`, `:greek`, or `:turkic`).
A shorthand value for the `default` option may be provided as a `t:boolean/0` value.
### Examples
```elixir
iex> Enviable.get_env_as_boolean("FLAG")
false
iex> Enviable.get_env_as_boolean("FLAG", true)
true
iex> Enviable.get_env_as_boolean("FLAG", default: true)
true
iex> Enviable.put_env("FLAG", "1")
iex> Enviable.get_env_as_boolean("FLAG")
true
iex> Enviable.put_env("FLAG", "something")
iex> Enviable.get_env_as_boolean("FLAG")
false
iex> Enviable.put_env("FLAG", "oui")
iex> Enviable.get_env_as_boolean("FLAG", truthy: ["oui"])
true
iex> Enviable.put_env("FLAG", "OUI")
iex> Enviable.get_env_as_boolean("FLAG", truthy: ["oui"])
false
iex> Enviable.get_env_as_boolean("FLAG", truthy: ["oui"], downcase: true)
true
iex> Enviable.put_env("FLAG", "NON")
iex> Enviable.get_env_as_boolean("FLAG", falsy: ["non"])
true
iex> Enviable.get_env_as_boolean("FLAG", falsy: ["non"], downcase: true)
false
iex> Enviable.get_env_as_boolean("FLAG", default: nil)
** (ArgumentError) could not convert environment variable "FLAG" to type boolean: non-boolean `default` value
iex> Enviable.get_env_as_boolean("FLAG", downcase: nil)
** (ArgumentError) could not convert environment variable "FLAG" to type boolean: invalid `downcase` value
iex> Enviable.get_env_as_boolean("FLAG", truthy: ["oui"], falsy: ["non"])
** (ArgumentError) could not convert environment variable "FLAG" to type boolean: `truthy` and `falsy` options both provided
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_boolean(
String.t(),
boolean()
| [
{:default, boolean()}
| Conversion.opt_downcase()
| {:truthy | :falsy, list(binary())}
]
) :: boolean()
def get_env_as_boolean(varname, opts \\ [])
def get_env_as_boolean(varname, default) when is_boolean(default), do: get_env_as(varname, :boolean, default: default)
def get_env_as_boolean(varname, opts), do: get_env_as(varname, :boolean, opts)
@doc """
Returns the value of an environment variable converted to a `t:charlist/0` or a default
value if the variable is unset. If no `default` is provided, `nil` is returned.
### Options
- `:default`: The default value, either as a `t:charlist/0` or `t:binary/0`.
A shorthand for the `default` value may be provided as a `t:binary/0` (there is no guard
for `t:charlist/0`).
### Examples
```elixir
iex> Enviable.get_env_as_charlist("UNDEFINED")
nil
iex> Enviable.get_env_as_charlist("UNDEFINED", "default")
~c"default"
iex> Enviable.get_env_as_charlist("UNDEFINED", default: "default")
~c"default"
iex> Enviable.get_env_as_charlist("UNDEFINED", default: ~c"default")
~c"default"
iex> Enviable.put_env("NAME", "get_env_as")
iex> Enviable.get_env_as_charlist("NAME")
~c"get_env_as"
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_charlist(String.t(), binary() | [{:default, charlist() | binary()}]) :: nil | charlist()
def get_env_as_charlist(varname, opts \\ [])
def get_env_as_charlist(varname, default) when is_binary(default), do: get_env_as(varname, :charlist, default: default)
def get_env_as_charlist(varname, opts), do: get_env_as(varname, :charlist, opts)
@doc """
Returns the value of an environment variable converted to a `t:integer/0` value or
a default value if the variable is unset. If no `default` is provided, `nil` is
returned.
### Options
- `:base`: The base (`2..36`) for integer conversion. Defaults to base `10` like
`String.to_integer/2`.
- `:default`: the default value, which must be either a binary string value or an
integer. If provided as a binary, this will be interpreted according to the `base`
option provided.
A shorthand for the `default` value may be provided as a `t:integer/0` or `t:binary/0`
value.
Failure to parse a binary string `default` or the value of the environment variable will
result in an exception.
### Examples
```elixir
iex> Enviable.get_env_as_integer("PORT")
nil
iex> Enviable.get_env_as_integer("PORT", 5432)
5432
iex> Enviable.get_env_as_integer("PORT", "5432")
5432
iex> Enviable.get_env_as_integer("PORT", default: 5432)
5432
iex> Enviable.get_env_as_integer("PORT", default: "5432")
5432
iex> Enviable.get_env_as_integer("PORT", default: 3.5)
** (ArgumentError) could not convert environment variable "PORT" to type integer: non-integer `default` value
iex> Enviable.put_env("PORT", "5432")
iex> Enviable.get_env_as_integer("PORT")
5432
iex> Enviable.put_env("PORT", "18eb")
iex> Enviable.get_env_as_integer("PORT")
** (Enviable.ConversionError) could not convert environment variable "PORT" to type integer
iex> Enviable.put_env("PORT", "18EB")
iex> Enviable.get_env_as_integer("PORT", base: 16)
6379
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_integer(String.t(), integer() | binary() | [{:base, 2..36} | {:default, binary() | integer()}]) ::
integer() | nil
def get_env_as_integer(varname, opts \\ [])
def get_env_as_integer(varname, default) when is_binary(default) or is_integer(default),
do: get_env_as(varname, :integer, default: default)
def get_env_as_integer(varname, opts), do: get_env_as(varname, :integer, opts)
@doc """
Returns the value of an environment variable converted to a `t:float/0` value or
a default value if the variable is unset. If no `default` is provided, `nil` will be
returned.
### Options
- `:default`: The default value, either as `t:float/0`, `t:integer/0` (which will be
converted to float), or `t:binary/0` (which must parse cleanly as float).
A shorthand for the `default` value may be provided as a `t:float/0`, `t:integer/0`, or
`t:binary/0` value.
### Examples
```elixir
iex> Enviable.get_env_as_float("FLOAT")
nil
iex> Enviable.get_env_as_float("FLOAT", 25.5)
25.5
iex> Enviable.get_env_as_float("FLOAT", 25)
25.0
iex> Enviable.get_env_as_float("FLOAT", "255")
255.0
iex> Enviable.get_env_as_float("FLOAT", default: 25.5)
25.5
iex> Enviable.get_env_as_float("FLOAT", default: 25)
25.0
iex> Enviable.get_env_as_float("FLOAT", default: "255")
255.0
iex> Enviable.get_env_as_float("FLOAT", default: "3.5R")
** (ArgumentError) could not convert environment variable "FLOAT" to type float: non-float `default` value
iex> Enviable.put_env("FLOAT", "1")
iex> Enviable.get_env_as_float("FLOAT")
1.0
iex> Enviable.put_env("FLOAT", "ff")
iex> Enviable.get_env_as_float("FLOAT")
** (Enviable.ConversionError) could not convert environment variable "FLOAT" to type float
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_float(String.t(), float() | integer() | binary() | [{:default, binary() | float() | integer()}]) ::
float() | nil
def get_env_as_float(varname, opts \\ [])
def get_env_as_float(varname, default) when is_binary(default) or is_float(default) or is_integer(default),
do: get_env_as(varname, :float, default: default)
def get_env_as_float(varname, opts), do: get_env_as(varname, :float, opts)
@doc """
Returns the value of an environment variable converted to
a `t:Enviable.Conversion.json/0` value or a default value if the variable is unset. If
no `default` is provided, `nil` will be returned.
### Options
- `:default`: The default value, which may be any valid JSON type.
- `:engine`: The JSON engine to use. May be provided as a `t:module/0` (which must
export `decode/1`) or an arity 1 function. If it produces `{:ok, json_value}` or an
expected JSON type result, it will be considered successful. Any other result will be
treated as failure.
If the Erlang/OTP `m::json` module is available, or [json_polyfill][jp] is available,
it will be used as the default JSON engine. Otherwise, [Jason][jason] will be treated as
the default engine. This choice may be overridden with application configuration, as
this example shows using [Thoas][thoas].
```elixir
import Config
config :enviable, :json_engine, :thoas
```
[jp]: https://hexdocs.pm/json_polyfill/readme.html
[jason]: https://hexdocs.pm/jason/readme.html
[thoas]: https://hexdocs.pm/thoas/readme.html
### Examples
```elixir
iex> Enviable.get_env_as_json("JSON")
nil
iex> Enviable.get_env_as_json("JSON", default: "3.5R")
"3.5R"
iex> Enviable.put_env("JSON", ~S|[{"foo":"bar"}]|)
iex> Enviable.get_env_as_json("JSON")
[%{"foo" => "bar"}]
iex> Enviable.put_env("JSON", "ff")
iex> Enviable.get_env_as_json("JSON")
** (Enviable.ConversionError) could not convert environment variable "JSON" to type json
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_json(String.t(), [
{:default, Conversion.json()}
| {:engine, module() | (String.t() -> Conversion.json())}
]) :: Conversion.json() | nil
def get_env_as_json(varname, opts \\ []), do: get_env_as(varname, :json, opts)
module_options = """
### Options
- `:allowed`: A list of `t:module/0` values indicating permitted module and used as
a lookup table, if present. Any value not found will result in an exception.
- `:default`: The `t:module/0` or `t:binary/0` value representing the atom value to use
if the environment variable is unset (`nil`). If the `:allowed` option is present, the
default value must be one of the permitted values.
A shorthand for the `default` option may be provided as a `t:atom/0` or `t:binary/0`
value.
"""
@doc """
Returns the value of an environment variable converted to `t:module/0` or a default
value if the variable is unset. If no `default` is provided, `nil` will be returned.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `Module.concat/1` and may result in atom exhaustion if
> used without the `:allowed` option. See #{atom_exhaustion} from the Security Working
> Group of the Erlang Ecosystem Foundation.
#{module_options}
### Examples
```elixir
iex> Enviable.get_env_as_module("UNSET")
nil
iex> Enviable.get_env_as_module("UNSET", Enviable)
Elixir.Enviable
iex> Enviable.put_env("NAME", "Enviable")
iex> Enviable.get_env_as_module("NAME")
Elixir.Enviable
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_module(
String.t(),
module()
| binary()
| [{:allowed, list(module())} | {:default, module() | binary()}]
) :: nil | module()
def get_env_as_module(varname, opts \\ [])
def get_env_as_module(varname, default) when is_binary(default) or is_atom(default),
do: get_env_as(varname, :module, default: default)
def get_env_as_module(varname, opts), do: get_env_as(varname, :module, opts)
@doc """
Returns the value of an environment variable converted to `t:module/0` or a default
value if the variable is unset. If no `default` is provided, `nil` will be returned. The
resulting `t:module/0` must already exist.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `Module.safe_concat/1` which will result in an exception
> if the resulting module is not already known and if used without the `:allowed`
> option. See #{atom_exhaustion} from the Security Working Group of the Erlang Ecosystem
> Foundation.
#{module_options}
### Examples
```elixir
iex> Enviable.get_env_as_safe_module("UNSET")
nil
iex> Enviable.get_env_as_safe_module("UNSET", Enviable)
Elixir.Enviable
iex> Enviable.put_env("NAME", "Enviable")
iex> Enviable.get_env_as_safe_module("NAME")
Elixir.Enviable
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_safe_module(
String.t(),
binary()
| module()
| [{:allowed, list(module())} | {:default, module() | binary()}]
) :: nil | module()
def get_env_as_safe_module(varname, opts \\ [])
def get_env_as_safe_module(varname, default) when is_binary(default) or is_atom(default),
do: get_env_as(varname, :safe_module, default: default)
def get_env_as_safe_module(varname, opts), do: get_env_as(varname, :safe_module, opts)
@doc """
Returns the value of an environment variable converted to a log level `t:atom/0` for
`Logger.configure/1` or a default value if the variable is unset. If no `default` is
provided, `nil` will be returned.
### Options
- `:default`: The default value. Must be a valid value.
A shorthand for the `default` option may be provided as a `t:atom/0` or `t:binary/0`
value.
### Examples
```elixir
iex> Enviable.get_env_as_log_level("LOG_LEVEL")
nil
iex> Enviable.get_env_as_log_level("LOG_LEVEL", :error)
:error
iex> Enviable.get_env_as_log_level("LOG_LEVEL", "info")
:info
iex> Enviable.get_env_as_log_level("LOG_LEVEL", default: :error)
:error
iex> Enviable.get_env_as_log_level("LOG_LEVEL", default: "info")
:info
iex> Enviable.put_env("LOG_LEVEL", "critical")
iex> Enviable.get_env_as_log_level("LOG_LEVEL")
:critical
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_log_level(String.t(), [{:default, Conversion.log_level()}]) :: nil | Conversion.log_level()
def get_env_as_log_level(varname, opts \\ [])
def get_env_as_log_level(varname, default) when is_binary(default) or is_atom(default),
do: get_env_as(varname, :log_level, default: default)
def get_env_as_log_level(varname, opts), do: get_env_as(varname, :log_level, opts)
@doc """
Returns the value of an environment variable converted from a PEM string through
`:public_key.pem_decode/1` or `nil` if the variable is unset.
### Options
- `:filter`: Filters the output of `:public_key.pem_decode/1`. Permitted values are
`false`, `true`, `:cert`, or `:key`. The default is `true`.
- `false`: the list is returned without processing, suitable for further processing
with `:public_key.pem_entry_decode/2`.
- `true`: returns the first unencrypted `:PrivateKeyInfo` found, a list of unencrypted
`:Certificate` records, or an empty list.
- `:cert`: returns a list of unencrypted `:Certificate` records or raises an
exception if none are found.
- `:key`: returns the first unencrypted `:PrivateKeyIinfo` record or raises an
exception if one is not found.
### Examples
```elixir
iex> Enviable.get_env_as_pem("PEM")
nil
iex> Enviable.put_env("PEM", "")
iex> Enviable.get_env_as_pem("PEM")
[]
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_pem(String.t(), [{:filter, boolean() | :cert | :key}]) :: nil | Conversion.pem()
def get_env_as_pem(varname, opts \\ []), do: get_env_as(varname, :pem, opts)
@doc """
Returns the value of an environment variable parsed and evaluated as Erlang code, or
`nil` if the environment variable is not set.
This can be used for tuples, complex map declarations, or other expressions difficult to
represent with other types. Longer code blocks should be encoded as base 64 text and
decoded as `{:base64, :erlang}`.
> #### Untrusted Input {: .error}
>
> This function parses (with `:erl_scan.string/1`) and evaluates (with
> `:erl_parse.parse_term/1`) Erlang code from environment variables in the
> context of your application. Do not use this with untrusted input.
### Examples
```elixir
iex> Enviable.get_env_as_erlang("TERM")
nil
iex> Enviable.put_env("TERM", "{ok, true}.")
iex> Enviable.get_env_as_erlang("TERM")
{:ok, true}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_erlang(String.t()) :: nil | term()
def get_env_as_erlang(varname), do: get_env_as(varname, :erlang, [])
@doc """
Returns the value of an environment variable parsed and evaluated as Elixir code, or
`nil` if the environment variable is not set.
This can be used for tuples, complex map declarations, or other expressions difficult to
represent with other types. Longer code blocks should be encoded as base 64 text and
decoded as `{:base64, :elixir}`.
> #### Untrusted Input {: .error}
>
> This function parses (with `Code.string_to_quoted/1`) and evaluates (with
> `Code.eval_quoted/1`) elixir code from environment variables in the context of your
> application. Do not use this with untrusted input.
### Examples
```elixir
iex> Enviable.get_env_as_elixir("TERM")
nil
iex> Enviable.put_env("TERM", "11000..11100//3")
iex> Enviable.get_env_as_elixir("TERM")
11000..11100//3
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec get_env_as_elixir(String.t()) :: nil | term()
def get_env_as_elixir(varname), do: get_env_as(varname, :elixir, [])
@doc """
Returns the value of an environment variable converted to the target `type` as `{:ok,
term()}` or `:error` if the variable is unset.
Suported primitive conversions are:
- `:atom` (`t:Enviable.Conversion.convert_atom/0`, `fetch_env_as_atom/2`)
- `:boolean` (`t:Enviable.Conversion.convert_boolean/0`, `fetch_env_as_boolean/2`)
- `:charlist` (`t:Enviable.Conversion.convert_charlist/0`, `fetch_env_as_charlist/1`)
- `:elixir` (`t:Enviable.Conversion.convert_elixir/0`, `fetch_env_as_elixir/1`)
- `:erlang` (`t:Enviable.Conversion.convert_erlang/0`, `fetch_env_as_erlang/1`)
- `:float` (`t:Enviable.Conversion.convert_float/0`, `fetch_env_as_float/1`)
- `:integer` (`t:Enviable.Conversion.convert_integer/0`, `fetch_env_as_integer/2`)
- `:json` (`t:Enviable.Conversion.convert_json/0`, `fetch_env_as_json/2`)
- `:log_level` (`t:Enviable.Conversion.convert_log_level/0`, `fetch_env_as_log_level/1`)
- `:module` (`t:Enviable.Conversion.convert_module/0`, `fetch_env_as_module/2`)
- `:pem` (`t:Enviable.Conversion.convert_pem/0`, `fetch_env_as_pem/2`)
- `:safe_atom` (`t:Enviable.Conversion.convert_safe_atom/0`, `fetch_env_as_safe_atom/2`)
- `:safe_module` (`t:Enviable.Conversion.convert_safe_module/0`,
`fetch_env_as_safe_module/2`)
Supported encoded conversions are:
- `:base16` (`t:Enviable.Conversion.encoded_base16/0`)
- `:base32` (`t:Enviable.Conversion.encoded_base32/0`)
- `:base64`, `:url_base64` (`t:Enviable.Conversion.encoded_base64/0`)
- `:hex32` (`t:Enviable.Conversion.encoded_hex32/0`)
See `Enviable.Conversion` for supported type conversions and options, but note that any
`default` values are ignored for `fetch_env_as/3` and related `fetch_env_as_*`
functions.
### Examples
```elixir
iex> Enviable.fetch_env_as("UNSET", :atom)
:error
iex> Enviable.fetch_env_as("UNSET", :float)
:error
iex> Enviable.fetch_env_as("UNSET", :base16)
:error
iex> Enviable.put_env("NAME", "fetch_env_as")
iex> Enviable.fetch_env_as("NAME", :atom)
{:ok, :fetch_env_as}
iex> Enviable.put_env("NAME", "FETCH_ENV_AS")
iex> Enviable.fetch_env_as("NAME", :safe_atom, downcase: true)
{:ok, :fetch_env_as}
iex> Enviable.fetch_env_as("UNSET", :float, default: "3.5")
:error
iex> Enviable.fetch_env_as("UNSET", :float, default: 3.5)
:error
iex> Enviable.put_env("FLOAT", "3")
iex> Enviable.fetch_env_as("FLOAT", :float)
{:ok, 3.0}
iex> Enviable.put_env("FLOAT", "3.1")
iex> Enviable.fetch_env_as("FLOAT", :float)
{:ok, 3.1}
iex> red = Base.encode16("RED", case: :lower)
iex> Enviable.put_env("NAME", red)
iex> Enviable.fetch_env_as("NAME", :base16, case: :lower)
{:ok, "RED"}
iex> Enviable.fetch_env_as("NAME", {:base16, :string}, case: :lower)
{:ok, "RED"}
iex> Enviable.fetch_env_as("NAME", {:base16, :atom}, case: :lower, downcase: true)
{:ok, :red}
```
"""
@doc since: "1.1.0"
@doc group: "Conversion"
@spec fetch_env_as(String.t(), Conversion.conversion(), keyword) :: {:ok, term()} | :error
def fetch_env_as(varname, type, opts \\ []) do
case fetch_env(varname) do
:error -> :error
{:ok, value} -> {:ok, Conversion.convert_as(value, varname, type, opts)}
end
end
atom_options = """
### Options
- `:allowed`: A list of `t:atom/0` values indicating permitted atoms and used as
a lookup table, if present. Any value not found will result in an exception.
- `:downcase`: See `t:Enviable.Conversion.opt_downcase/0`.
"""
@doc """
Returns the value of an environment variable converted to `t:atom/0` as `{:ok, atom()}`
or `:error` if the variable is unset.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `String.to_atom/1` and may result in atom exhaustion if
> used without the `:allowed` option. See #{atom_exhaustion} from the Security Working
> Group of the Erlang Ecosystem Foundation.
#{atom_options}
### Examples
```elixir
iex> Enviable.fetch_env_as_atom("UNSET")
:error
iex> Enviable.put_env("NAME", "fetch_env_as_atom")
iex> Enviable.fetch_env_as_atom("NAME")
{:ok, :fetch_env_as_atom}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_atom(String.t(), [{:allowed, list(atom())} | Conversion.opt_downcase()]) :: {:ok, atom()} | :error
def fetch_env_as_atom(varname, opts \\ []), do: fetch_env_as(varname, :atom, opts)
@doc """
Returns the value of an environment variable converted to an existing `t:atom/0` as
`{:ok, atom()}` or `:error` if the variable is unset.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `String.to_existing_atom/1` which will result in an
> exception if the resulting atom is not already known and if used without the
> `:allowed` option. See #{atom_exhaustion} from the Security Working Group of the
> Erlang Ecosystem Foundation.
#{atom_options}
### Examples
```elixir
iex> Enviable.fetch_env_as_safe_atom("UNSET")
:error
iex> Enviable.put_env("NAME", "FETCH_ENV_AS_SAFE_ATOM")
iex> Enviable.fetch_env_as_safe_atom("NAME", downcase: true)
{:ok, :fetch_env_as_safe_atom}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_safe_atom(String.t(), [{:allowed, list(atom())} | Conversion.opt_downcase()]) ::
{:ok, atom()} | :error
def fetch_env_as_safe_atom(varname, opts \\ []), do: fetch_env_as(varname, :safe_atom, opts)
@doc """
Returns the value of an environment variable converted to a `t:boolean/0` value as
`{:ok, boolean()}` or `:error` if the variable is unset.
This function will always result in a `t:boolean/0` value. Unless configured with
`truthy` or `falsy`, only the values `"1"` and `"true"` will be converted to `true` and
any other value will result in `false`.
### Options
- `:truthy`: a list of string values to be compared for truth values. If the value of the
environment variable matches these values, `true` will be returned; other values will
result in `false`. Mutually exclusive with `falsy`.
- `:falsy`: a list of string values to be compared for false values. If the value of the
environment variable matches these values, `false` will be returned; other values will
result in `true`. Mutually exclusive with `truthy`.
- `:downcase`: either `false` (the default), `true`, or the mode parameter for
`String.downcase/2` (`:default`, `:ascii`, `:greek`, or `:turkic`).
### Examples
```elixir
iex> Enviable.fetch_env_as_boolean("UNSET")
:error
iex> Enviable.put_env("FLAG", "1")
iex> Enviable.fetch_env_as_boolean("FLAG")
{:ok, true}
iex> Enviable.put_env("FLAG", "something")
iex> Enviable.fetch_env_as_boolean("FLAG")
{:ok, false}
iex> Enviable.put_env("FLAG", "oui")
iex> Enviable.fetch_env_as_boolean("FLAG", truthy: ["oui"])
{:ok, true}
iex> Enviable.put_env("FLAG", "OUI")
iex> Enviable.fetch_env_as_boolean("FLAG", truthy: ["oui"])
{:ok, false}
iex> Enviable.put_env("FLAG", "OUI")
iex> Enviable.fetch_env_as_boolean("FLAG", truthy: ["oui"], downcase: true)
{:ok, true}
iex> Enviable.put_env("FLAG", "NON")
iex> Enviable.fetch_env_as_boolean("FLAG", falsy: ["non"])
{:ok, true}
iex> Enviable.put_env("FLAG", "NON")
iex> Enviable.fetch_env_as_boolean("FLAG", falsy: ["non"], downcase: true)
{:ok, false}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_boolean(String.t(), [Conversion.opt_downcase() | {:truthy | :falsy, list(binary())}]) ::
{:ok, boolean} | :error
def fetch_env_as_boolean(varname, opts \\ []), do: fetch_env_as(varname, :boolean, opts)
@doc """
Returns the value of an environment variable converted to a `t:charlist/0` as `{:ok,
charlist()}` or `:error` if the variable is unset.
### Examples
```elixir
iex> Enviable.fetch_env_as_charlist("UNSET")
:error
iex> Enviable.put_env("NAME", "fetch_env_as")
iex> Enviable.fetch_env_as_charlist("NAME")
{:ok, ~c"fetch_env_as"}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_charlist(String.t()) :: {:ok, charlist()} | :error
def fetch_env_as_charlist(varname), do: fetch_env_as(varname, :charlist, [])
@doc """
Returns the value of an environment variable converted to a `t:integer/0` value as
`{:ok, integer()}` or `:error` if the variable is unset.
### Options
- `:base`: The base (`2..36`) for integer conversion. Defaults to base `10` like
`String.to_integer/2`.
Failure to parse the value of the environment variable will result in an exception.
### Examples
```elixir
iex> Enviable.fetch_env_as_integer("UNSET")
:error
iex> Enviable.put_env("PORT", "5432")
iex> Enviable.fetch_env_as_integer("PORT")
{:ok, 5432}
iex> Enviable.put_env("PORT", "18eb")
iex> Enviable.fetch_env_as_integer("PORT")
** (Enviable.ConversionError) could not convert environment variable "PORT" to type integer
iex> Enviable.put_env("PORT", "18EB")
iex> Enviable.fetch_env_as_integer("PORT", base: 16)
{:ok, 6379}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_integer(String.t(), [{:base, 2..36}]) :: {:ok, integer()} | :error
def fetch_env_as_integer(varname, opts \\ []), do: fetch_env_as(varname, :integer, opts)
@doc """
Returns the value of an environment variable converted to a `t:float/0` value as `{:ok,
float()}` or :error if the variable is unset.
### Examples
```elixir
iex> Enviable.fetch_env_as_float("UNSET")
:error
iex> Enviable.put_env("FLOAT", "1")
iex> Enviable.fetch_env_as_float("FLOAT")
{:ok, 1.0}
iex> Enviable.put_env("FLOAT", "ff")
iex> Enviable.fetch_env_as_float("FLOAT")
** (Enviable.ConversionError) could not convert environment variable "FLOAT" to type float
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_float(String.t()) :: {:ok, float()} | :error
def fetch_env_as_float(varname), do: fetch_env_as(varname, :float, [])
@doc """
Returns the value of an environment variable converted to
a `t:Enviable.Conversion.json/0` value as `{:ok, Conversion.json()}` or `:error` if the
variable is unset.
### Options
- `:engine`: The JSON engine to use. May be provided as a `t:module/0` (which must
export `decode/1`) or an arity 1 function. If it produces `{:ok, json_value}` or an
expected JSON type result, it will be considered successful. Any other result will be
treated as failure.
If the Erlang/OTP `m::json` module is available, or [json_polyfill][jp] is available,
it will be used as the default JSON engine. Otherwise, [Jason][jason] will be treated as
the default engine. This choice may be overridden with application configuration, as
this example shows using [Thoas][thoas].
```elixir
import Config
config :enviable, :json_engine, :thoas
```
[jp]: https://hexdocs.pm/json_polyfill/readme.html
[jason]: https://hexdocs.pm/jason/readme.html
[thoas]: https://hexdocs.pm/thoas/readme.html
### Examples
```elixir
iex> Enviable.fetch_env_as_json("UNSET")
:error
iex> Enviable.put_env("JSON", ~S|[{"foo":"bar"}]|)
iex> Enviable.fetch_env_as_json("JSON")
{:ok, [%{"foo" => "bar"}]}
iex> Enviable.put_env("JSON", "ff")
iex> Enviable.fetch_env_as_json("JSON")
** (Enviable.ConversionError) could not convert environment variable "JSON" to type json
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_json(String.t(), [{:engine, module() | (String.t() -> Conversion.json())}]) ::
{:ok, Conversion.json()} | :error
def fetch_env_as_json(varname, opts \\ []), do: fetch_env_as(varname, :json, opts)
module_options = """
### Options
- `:allowed`: A list of `t:module/0` values indicating permitted module and used as
a lookup table, if present. Any value not found will result in an exception.
"""
@doc """
Returns the value of an environment variable converted to `t:module/0` as `{:ok,
module()}` or `:error` if the variable is unset.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `Module.concat/1` and may result in atom exhaustion if
> used without the `:allowed` option. See #{atom_exhaustion} from the Security Working
> Group of the Erlang Ecosystem Foundation.
#{module_options}
### Examples
```elixir
iex> Enviable.fetch_env_as_module("UNSET")
:error
iex> Enviable.put_env("NAME", "Enviable")
iex> Enviable.fetch_env_as_module("NAME")
{:ok, Elixir.Enviable}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_module(String.t(), [{:allowed, list(module())}]) :: {:ok, module()} | :error
def fetch_env_as_module(varname, opts \\ []), do: fetch_env_as(varname, :module, opts)
@doc """
Returns the value of an environment variable converted to `t:module/0` as `{:ok,
module()}` or `:error` if the variable is unset. The resulting `t:module/0` must
already exist.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `Module.safe_concat/1` which will result in an exception
> if the resulting module is not already known and if used without the `:allowed`
> option. See #{atom_exhaustion} from the Security Working Group of the Erlang Ecosystem
> Foundation.
#{module_options}
### Examples
```elixir
iex> Enviable.fetch_env_as_safe_module("UNSET")
:error
iex> Enviable.put_env("NAME", "Enviable")
iex> Enviable.fetch_env_as_safe_module("NAME")
{:ok, Elixir.Enviable}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_safe_module(String.t(), [{:allowed, list(module())}]) :: {:ok, module()} | :error
def fetch_env_as_safe_module(varname, opts \\ []), do: fetch_env_as(varname, :safe_module, opts)
@doc """
Returns the value of an environment variable converted to a log level `t:atom/0` for
`Logger.configure/1` as `{:ok, atom()}` or `:error` if the variable is unset.
### Examples
```elixir
iex> Enviable.fetch_env_as_log_level("UNSET")
:error
iex> Enviable.put_env("LOG_LEVEL", "critical")
iex> Enviable.fetch_env_as_log_level("LOG_LEVEL")
{:ok, :critical}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_log_level(String.t()) :: {:ok, Conversion.log_level()} | :error
def fetch_env_as_log_level(varname), do: fetch_env_as(varname, :log_level, [])
@doc """
Returns the value of an environment variable converted from a PEM string through
`:public_key.pem_decode/1` as `{:ok, value}` or `:error` if the variable is unset.
### Options
- `:filter`: Filters the output of `:public_key.pem_decode/1`. Permitted values are
`false`, `true`, `:cert`, or `:key`. The default is `true`.
- `false`: the list is returned without processing, suitable for further processing
with `:public_key.pem_entry_decode/2`.
- `true`: returns the first unencrypted `:PrivateKeyInfo` found, a list of unencrypted
`:Certificate` records, or an empty list.
- `:cert`: returns a list of unencrypted `:Certificate` records or raises an
exception if none are found.
- `:key`: returns the first unencrypted `:PrivateKeyIinfo` record or raises an
exception if one is not found.
### Examples
```elixir
iex> Enviable.fetch_env_as_pem("UNSET")
:error
iex> Enviable.put_env("PEM", "")
iex> Enviable.fetch_env_as_pem("PEM")
{:ok, []}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_pem(String.t(), [{:filter, boolean() | :cert | :key}]) :: {:ok, Conversion.pem()} | :error
def fetch_env_as_pem(varname, opts \\ []), do: fetch_env_as(varname, :pem, opts)
@doc """
Returns the value of an environment variable parsed and evaluated as Erlang code with
the result as `{:ok, term()}`, or `:error` if the environment variable is not set.
This can be used for tuples, complex map declarations, or other expressions difficult to
represent with other types. Longer code blocks should be encoded as base 64 text and
decoded as `{:base64, :erlang}`.
> #### Untrusted Input {: .error}
>
> This function parses (with `:erl_scan.string/1`) and evaluates (with
> `:erl_parse.parse_term/1`) Erlang code from environment variables in the
> context of your application. Do not use this with untrusted input.
### Examples
```elixir
iex> Enviable.fetch_env_as_erlang("UNSET")
:error
iex> Enviable.put_env("TERM", "{ok, true}.")
iex> Enviable.fetch_env_as_erlang("TERM")
{:ok, {:ok, true}}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_erlang(String.t()) :: {:ok, term()} | :error
def fetch_env_as_erlang(varname), do: fetch_env_as(varname, :erlang, [])
@doc """
Returns the value of an environment variable parsed and evaluated as Elixir code with
the result as `{:ok, term()}`, or `:error` if the environment variable is not set.
This can be used for tuples, complex map declarations, or other expressions difficult to
represent with other types. Longer code blocks should be encoded as base 64 text and
decoded as `{:base64, :elixir}`.
> #### Untrusted Input {: .error}
>
> This function parses (with `Code.string_to_quoted/1`) and evaluates (with
> `Code.eval_quoted/1`) elixir code from environment variables in the context of your
> application. Do not use this with untrusted input.
### Examples
```elixir
iex> Enviable.fetch_env_as_elixir("UNSET")
:error
iex> Enviable.put_env("TERM", "11000..11100//3")
iex> Enviable.fetch_env_as_elixir("TERM")
{:ok, 11000..11100//3}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_elixir(String.t()) :: {:ok, term()} | :error
def fetch_env_as_elixir(varname), do: fetch_env_as(varname, :elixir, [])
@doc """
Returns the value of an environment variable converted to the target `type` or raises an
exception if the variable is unset.
Suported primitive conversions are:
- `:atom` (`t:Enviable.Conversion.convert_atom/0`, `fetch_env_as_atom!/2`)
- `:boolean` (`t:Enviable.Conversion.convert_boolean/0`, `fetch_env_as_boolean!/2`)
- `:charlist` (`t:Enviable.Conversion.convert_charlist/0`, `fetch_env_as_charlist!/2`)
- `:elixir` (`t:Enviable.Conversion.convert_elixir/0`, `fetch_env_as_elixir!/1`)
- `:erlang` (`t:Enviable.Conversion.convert_erlang/0`, `fetch_env_as_erlang!/1`)
- `:float` (`t:Enviable.Conversion.convert_float/0`, `fetch_env_as_float!/2`)
- `:integer` (`t:Enviable.Conversion.convert_integer/0`, `fetch_env_as_integer!/2`)
- `:json` (`t:Enviable.Conversion.convert_json/0`, `fetch_env_as_json!/2`)
- `:log_level` (`t:Enviable.Conversion.convert_log_level/0`,
`fetch_env_as_log_level!/2`)
- `:module` (`t:Enviable.Conversion.convert_module/0`, `fetch_env_as_module!/2`)
- `:pem` (`t:Enviable.Conversion.convert_pem/0`, `fetch_env_as_pem!/2`)
- `:safe_atom` (`t:Enviable.Conversion.convert_safe_atom/0`,
`fetch_env_as_safe_atom!/2`)
- `:safe_module` (`t:Enviable.Conversion.convert_safe_module/0`,
`fetch_env_as_safe_module!/2`)
Supported encoded conversions are:
- `:base16` (`t:Enviable.Conversion.encoded_base16/0`)
- `:base32` (`t:Enviable.Conversion.encoded_base32/0`)
- `:base64`, `:url_base64` (`t:Enviable.Conversion.encoded_base64/0`)
- `:hex32` (`t:Enviable.Conversion.encoded_hex32/0`)
See `Enviable.Conversion` for supported type conversions and options, but note that any
documented `default` values are ignored for `fetch_env_as!/3` and related
`fetch_env_as_*!` functions.
### Examples
```elixir
iex> Enviable.fetch_env_as!("UNSET", :atom)
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.fetch_env_as!("UNSET", :float)
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.fetch_env_as!("UNSET", :base16)
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("NAME", "fetch_env_as!")
iex> Enviable.fetch_env_as!("NAME", :atom)
:fetch_env_as!
iex> Enviable.put_env("NAME", "FETCH_ENV_AS!")
iex> Enviable.fetch_env_as!("NAME", :safe_atom, downcase: true)
:fetch_env_as!
iex> Enviable.fetch_env_as!("UNSET", :float, default: "3.5")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.fetch_env_as!("UNSET", :float, default: 3.5)
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("FLOAT", "3")
iex> Enviable.fetch_env_as!("FLOAT", :float)
3.0
iex> Enviable.put_env("FLOAT", "3.1")
iex> Enviable.fetch_env_as!("FLOAT", :float)
3.1
iex> red = Base.encode16("RED", case: :lower)
iex> Enviable.put_env("NAME", red)
iex> Enviable.fetch_env_as!("NAME", :base16, case: :lower)
"RED"
iex> Enviable.fetch_env_as!("NAME", {:base16, :string}, case: :lower)
"RED"
iex> Enviable.fetch_env_as!("NAME", {:base16, :atom}, case: :lower, downcase: true)
:red
```
"""
@doc since: "1.1.0"
@doc group: "Conversion"
@spec fetch_env_as!(String.t(), Conversion.conversion(), keyword) :: term()
def fetch_env_as!(varname, type, opts \\ []) do
varname
|> fetch_env!()
|> Conversion.convert_as(varname, type, opts)
end
@doc """
Returns the value of an environment variable converted to `t:atom/0` or raises an
exception if the variable is unset.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `String.to_atom/1` and may result in atom exhaustion if
> used without the `:allowed` option. See #{atom_exhaustion} from the Security Working
> Group of the Erlang Ecosystem Foundation.
#{atom_options}
### Examples
```elixir
iex> Enviable.fetch_env_as_atom!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("NAME", "fetch_env_as_atom!")
iex> Enviable.fetch_env_as_atom!("NAME")
:fetch_env_as_atom!
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_atom!(String.t(), [{:allowed, list(atom())} | Conversion.opt_downcase()]) :: atom()
def fetch_env_as_atom!(varname, opts \\ []), do: fetch_env_as!(varname, :atom, opts)
@doc """
Returns the value of an environment variable converted to an existing `t:atom/0` or
raises an exception if the variable is unset.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `String.to_existing_atom/1` which will result in an
> exception if the resulting atom is not already known and if used without the
> `:allowed` option. See #{atom_exhaustion} from the Security Working Group of the
> Erlang Ecosystem Foundation.
#{atom_options}
### Examples
```elixir
iex> Enviable.fetch_env_as_safe_atom!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("NAME", "FETCH_ENV_AS_SAFE_ATOM!")
iex> Enviable.fetch_env_as_safe_atom!("NAME", downcase: true)
:fetch_env_as_safe_atom!
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_safe_atom!(String.t(), [{:allowed, list(atom())} | Conversion.opt_downcase()]) ::
atom()
def fetch_env_as_safe_atom!(varname, opts \\ []), do: fetch_env_as!(varname, :safe_atom, opts)
@doc """
Returns the value of an environment variable converted to a `t:boolean/0` value or
raises an exception if the variable is unset.
This function will always result in a `t:boolean/0` value. Unless configured with
`truthy` or `falsy`, only the values `"1"` and `"true"` will be converted to `true` and
any other value will result in `false`.
### Options
- `:truthy`: a list of string values to be compared for truth values. If the value of
the environment variable matches these values, `true` will be returned; other values
will result in `false`. Mutually exclusive with `falsy`.
- `:falsy`: a list of string values to be compared for false values. If the value of the
environment variable matches these values, `false` will be returned; other values will
result in `true`. Mutually exclusive with `truthy`.
- `:downcase`: either `false` (the default), `true`, or the mode parameter for
`String.downcase/2` (`:default`, `:ascii`, `:greek`, or `:turkic`).
### Examples
```elixir
iex> Enviable.fetch_env_as_boolean!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("FLAG", "1")
iex> Enviable.fetch_env_as_boolean!("FLAG")
true
iex> Enviable.put_env("FLAG", "something")
iex> Enviable.fetch_env_as_boolean!("FLAG")
false
iex> Enviable.put_env("FLAG", "oui")
iex> Enviable.fetch_env_as_boolean!("FLAG", truthy: ["oui"])
true
iex> Enviable.put_env("FLAG", "OUI")
iex> Enviable.fetch_env_as_boolean!("FLAG", truthy: ["oui"])
false
iex> Enviable.put_env("FLAG", "OUI")
iex> Enviable.fetch_env_as_boolean!("FLAG", truthy: ["oui"], downcase: true)
true
iex> Enviable.put_env("FLAG", "NON")
iex> Enviable.fetch_env_as_boolean!("FLAG", falsy: ["non"])
true
iex> Enviable.put_env("FLAG", "NON")
iex> Enviable.fetch_env_as_boolean!("FLAG", falsy: ["non"], downcase: true)
false
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_boolean!(String.t(), [Conversion.opt_downcase() | {:truthy | :falsy, list(binary())}]) ::
boolean
def fetch_env_as_boolean!(varname, opts \\ []), do: fetch_env_as!(varname, :boolean, opts)
@doc """
Returns the value of an environment variable converted to a `t:charlist/0` or raises an
exception if the variable is unset.
### Examples
```elixir
iex> Enviable.fetch_env_as_charlist!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("NAME", "fetch_env_as")
iex> Enviable.fetch_env_as_charlist!("NAME")
~c"fetch_env_as"
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_charlist!(String.t()) :: charlist()
def fetch_env_as_charlist!(varname), do: fetch_env_as!(varname, :charlist, [])
@doc """
Returns the value of an environment variable converted to a `t:integer/0` value or
raises an exception if the variable is unset.
### Options
- `:base`: The base (`2..36`) for integer conversion. Defaults to base `10` like
`String.to_integer/2`.
Failure to parse the value of the environment variable will result in an exception.
### Examples
```elixir
iex> Enviable.fetch_env_as_integer!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("PORT", "5432")
iex> Enviable.fetch_env_as_integer!("PORT")
5432
iex> Enviable.put_env("PORT", "18eb")
iex> Enviable.fetch_env_as_integer!("PORT")
** (Enviable.ConversionError) could not convert environment variable "PORT" to type integer
iex> Enviable.put_env("PORT", "18EB")
iex> Enviable.fetch_env_as_integer!("PORT", base: 16)
6379
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_integer!(String.t(), [{:base, 2..36}]) :: integer()
def fetch_env_as_integer!(varname, opts \\ []), do: fetch_env_as!(varname, :integer, opts)
@doc """
Returns the value of an environment variable converted to a `t:float/0` value or
`:error` if the variable is unset.
### Examples
```elixir
iex> Enviable.fetch_env_as_float!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("FLOAT", "1")
iex> Enviable.fetch_env_as_float!("FLOAT")
1.0
iex> Enviable.put_env("FLOAT", "ff")
iex> Enviable.fetch_env_as_float!("FLOAT")
** (Enviable.ConversionError) could not convert environment variable "FLOAT" to type float
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_float!(String.t()) :: float()
def fetch_env_as_float!(varname), do: fetch_env_as!(varname, :float, [])
@doc """
Returns the value of an environment variable converted to
a `t:Enviable.Conversion.json/0` value or raises an exception if the variable is unset.
### Options
- `:engine`: The JSON engine to use. May be provided as a `t:module/0` (which must
export `decode/1`) or an arity 1 function. If it produces `{:ok, json_value}` or an
expected JSON type result, it will be considered successful. Any other result will be
treated as failure.
If the Erlang/OTP `m::json` module is available, or [json_polyfill][jp] is available,
it will be used as the default JSON engine. Otherwise, [Jason][jason] will be treated as
the default engine. This choice may be overridden with application configuration, as
this example shows using [Thoas][thoas].
```elixir
import Config
config :enviable, :json_engine, :thoas
```
[jp]: https://hexdocs.pm/json_polyfill/readme.html
[jason]: https://hexdocs.pm/jason/readme.html
[thoas]: https://hexdocs.pm/thoas/readme.html
### Examples
```elixir
iex> Enviable.fetch_env_as_json!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("JSON", ~S|[{"foo":"bar"}]|)
iex> Enviable.fetch_env_as_json!("JSON")
[%{"foo" => "bar"}]
iex> Enviable.put_env("JSON", "ff")
iex> Enviable.fetch_env_as_json!("JSON")
** (Enviable.ConversionError) could not convert environment variable "JSON" to type json
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_json!(String.t(), [{:engine, module() | (String.t() -> Conversion.json())}]) ::
Conversion.json()
def fetch_env_as_json!(varname, opts \\ []), do: fetch_env_as!(varname, :json, opts)
module_options = """
### Options
- `:allowed`: A list of `t:module/0` values indicating permitted module and used as
a lookup table, if present. Any value not found will result in an exception.
"""
@doc """
Returns the value of an environment variable converted to `t:module/0` or raises an
exception if the variable is unset.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `Module.concat/1` and may result in atom exhaustion if
> used without the `:allowed` option. See #{atom_exhaustion} from the Security Working
> Group of the Erlang Ecosystem Foundation.
#{module_options}
### Examples
```elixir
iex> Enviable.fetch_env_as_module!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("NAME", "Enviable")
iex> Enviable.fetch_env_as_module!("NAME")
Elixir.Enviable
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_module!(String.t(), [{:allowed, list(module())}]) :: module()
def fetch_env_as_module!(varname, opts \\ []), do: fetch_env_as!(varname, :module, opts)
@doc """
Returns the value of an environment variable converted to `t:module/0` or raises an
exception if the variable is unset. The resulting `t:module/0` must already exist.
> #### Untrusted Input {: .warning}
>
> This conversion routine uses `Module.safe_concat/1` which will result in an exception
> if the resulting module is not already known and if used without the `:allowed`
> option. See #{atom_exhaustion} from the Security Working Group of the Erlang Ecosystem
> Foundation.
#{module_options}
### Examples
```elixir
iex> Enviable.fetch_env_as_safe_module!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("NAME", "Enviable")
iex> Enviable.fetch_env_as_safe_module!("NAME")
Elixir.Enviable
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_safe_module!(String.t(), [{:allowed, list(module())}]) :: module()
def fetch_env_as_safe_module!(varname, opts \\ []), do: fetch_env_as!(varname, :safe_module, opts)
@doc """
Returns the value of an environment variable converted to a log level `t:atom/0` for
`Logger.configure/1` or raises an exception if the variable is unset.
### Examples
```elixir
iex> Enviable.fetch_env_as_log_level!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("LOG_LEVEL", "critical")
iex> Enviable.fetch_env_as_log_level!("LOG_LEVEL")
:critical
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_log_level!(String.t()) :: Conversion.log_level()
def fetch_env_as_log_level!(varname), do: fetch_env_as!(varname, :log_level, [])
@doc """
Returns the value of an environment variable converted from a PEM string through
`:public_key.pem_decode/1` or raises an exception if the variable is unset.
### Options
- `:filter`: Filters the output of `:public_key.pem_decode/1`. Permitted values are
`false`, `true`, `:cert`, or `:key`. The default is `true`.
- `false`: the list is returned without processing, suitable for further processing
with `:public_key.pem_entry_decode/2`.
- `true`: returns the first unencrypted `:PrivateKeyInfo` found, a list of unencrypted
`:Certificate` records, or an empty list.
- `:cert`: returns a list of unencrypted `:Certificate` records or raises an
exception if none are found.
- `:key`: returns the first unencrypted `:PrivateKeyIinfo` record or raises an
exception if one is not found.
### Examples
```elixir
iex> Enviable.fetch_env_as_pem!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("PEM", "")
iex> Enviable.fetch_env_as_pem!("PEM")
[]
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_pem!(String.t(), [{:filter, boolean() | :cert | :key}]) :: Conversion.pem()
def fetch_env_as_pem!(varname, opts \\ []), do: fetch_env_as!(varname, :pem, opts)
@doc """
Returns the value of an environment variable parsed and evaluated as Erlang code with
the result , or raises an exception if the environment variable is not set.
This can be used for tuples, complex map declarations, or other expressions difficult to
represent with other types. Longer code blocks should be encoded as base 64 text and
decoded as `{:base64, :erlang}`.
> #### Untrusted Input {: .error}
>
> This function parses (with `:erl_scan.string/1`) and evaluates (with
> `:erl_parse.parse_term/1`) Erlang code from environment variables in the
> context of your application. Do not use this with untrusted input.
### Examples
```elixir
iex> Enviable.fetch_env_as_erlang!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("TERM", "{ok, true}.")
iex> Enviable.fetch_env_as_erlang!("TERM")
{:ok, true}
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_erlang!(String.t()) :: term()
def fetch_env_as_erlang!(varname), do: fetch_env_as!(varname, :erlang, [])
@doc """
Returns the value of an environment variable parsed and evaluated as Elixir code with
the result , or raises an exception if the environment variable is not set.
This can be used for tuples, complex map declarations, or other expressions difficult to
represent with other types. Longer code blocks should be encoded as base 64 text and
decoded as `{:base64, :elixir}`.
> #### Untrusted Input {: .error}
>
> This function parses (with `Code.string_to_quoted/1`) and evaluates (with
> `Code.eval_quoted/1`) elixir code from environment variables in the context of your
> application. Do not use this with untrusted input.
### Examples
```elixir
iex> Enviable.fetch_env_as_elixir!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("TERM", "11000..11100//3")
iex> Enviable.fetch_env_as_elixir!("TERM")
11000..11100//3
```
"""
@doc since: "1.3.0"
@doc group: "Conversion"
@spec fetch_env_as_elixir!(String.t()) :: term()
def fetch_env_as_elixir!(varname), do: fetch_env_as!(varname, :elixir, [])
@doc """
Returns the value of an environment variable converted to a `t:boolean/0` value. See
`get_env_as_boolean/2` for more details.
### Examples
```elixir
iex> Enviable.get_env_boolean("UNSET")
false
```
"""
@doc deprecated: "Use get_env_as_boolean/2 or get_env_as/3 instead"
@doc group: "Conversion"
@spec get_env_boolean(String.t(), keyword) :: boolean()
def get_env_boolean(varname, opts \\ []), do: get_env_as(varname, :boolean, opts)
@doc """
Returns the value of an environment variable as `{:ok, t:boolean/0}` value or `:error`
if the variable is unset. See `fetch_env_as_boolean/2` or more details.
### Examples
```elixir
iex> Enviable.fetch_env_boolean("UNSET")
:error
iex> Enviable.put_env("FLAG", "1")
iex> Enviable.fetch_env_boolean("FLAG")
{:ok, true}
```
"""
@doc deprecated: "Use fetch_env_as_boolean/2 or fetch_env_as/3 instead"
@doc group: "Conversion"
@spec fetch_env_boolean(String.t(), keyword) :: {:ok, boolean()} | :error
def fetch_env_boolean(varname, opts \\ []), do: fetch_env_as(varname, :boolean, opts)
@doc """
Returns the value of an environment variable converted to a `t:boolean/0` value or
raises an exception if the variable is unset. See `fetch_env_as_boolean!/2` for details.
```elixir
iex> Enviable.fetch_env_boolean!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("FLAG", "1")
iex> Enviable.fetch_env_boolean!("FLAG")
true
```
"""
@doc deprecated: "Use fetch_env_as_boolean!/2 or fetch_env_as!/3 instead"
@doc group: "Conversion"
@spec fetch_env_boolean!(String.t(), keyword) :: boolean()
def fetch_env_boolean!(varname, opts \\ []), do: fetch_env_as!(varname, :boolean, opts)
@doc """
Returns the value of an environment variable converted to a `t:integer/0` value or `nil`
if the variable is not set and a `default` is not provided. See `get_env_as_integer/2`
for details.
### Examples
```elixir
iex> Enviable.get_env_integer("UNSET")
nil
iex> Enviable.get_env_integer("PORT", default: 255)
255
```
"""
@doc deprecated: "Use get_env_as_integer/2 or get_env_as/3 instead"
@doc group: "Conversion"
@spec get_env_integer(String.t(), keyword) :: integer() | nil
def get_env_integer(varname, opts \\ []), do: get_env_as(varname, :integer, opts)
@doc """
Returns the value of an environment variable as `{:ok, t:integer/0}` or `:error` if the
variable is unset. See `fetch_env_as_integer/2` for details.
### Examples
```elixir
iex> Enviable.fetch_env_integer("UNSET")
:error
iex> Enviable.put_env("PORT", "1")
iex> Enviable.fetch_env_integer("PORT")
{:ok, 1}
```
"""
@doc deprecated: "Use fetch_env_as_integer/2 or fetch_env_as/3 instead"
@doc group: "Conversion"
@spec fetch_env_integer(String.t(), keyword) :: {:ok, integer()} | :error
def fetch_env_integer(varname, opts \\ []), do: fetch_env_as(varname, :integer, opts)
@doc """
Returns the value of an environment variable converted to a `t:integer/0` value raises
an exception if the variable is unset. See `fetch_env_as_integer!/2` for details.
### Examples
```elixir
iex> Enviable.fetch_env_integer!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("PORT", "1")
iex> Enviable.fetch_env_integer!("PORT")
1
```
"""
@doc deprecated: "Use fetch_env_as_integer!/2 or fetch_env_as!/3 instead"
@doc group: "Conversion"
@spec fetch_env_integer!(String.t(), keyword) :: integer()
def fetch_env_integer!(varname, opts \\ []), do: fetch_env_as!(varname, :integer, opts)
@doc """
Deletes an environment variable, removing `varname` from the environment.
"""
@doc group: "Delegates"
@spec delete_env(String.t()) :: :ok
defdelegate delete_env(varname), to: System
@doc """
Returns the value of the given environment variable or `:error` if not found.
If the environment variable `varname` is set, then `{:ok, value}` is returned where
value is a string. If `varname` is not set, `:error` is returned.
### Examples
```elixir
iex> Enviable.fetch_env("PORT")
:error
iex> Enviable.put_env("PORT", "4000")
iex> Enviable.fetch_env("PORT")
{:ok, "4000"}
```
"""
@doc group: "Delegates"
@spec fetch_env(String.t()) :: {:ok, String.t()} | :error
defdelegate fetch_env(varname), to: System
@doc """
Returns the value of the given environment variable or raises if not found.
Same as `get_env/1` but raises instead of returning `nil` when the variable is not set.
### Examples
```elixir
iex> Enviable.fetch_env!("UNSET")
** (System.EnvError) could not fetch environment variable "UNSET" because it is not set
iex> Enviable.put_env("PORT", "4000")
iex> Enviable.fetch_env!("PORT")
"4000"
```
"""
@doc group: "Delegates"
@spec fetch_env!(String.t()) :: String.t()
defdelegate fetch_env!(varname), to: System
@doc """
Returns all system environment variables.
The returned value is a map containing name-value pairs. Variable names and their values
are strings.
"""
@doc group: "Delegates"
@spec get_env() :: %{optional(String.t()) => String.t()}
defdelegate get_env, to: System
@doc """
Returns the value of the given environment variable.
The returned value of the environment variable `varname` is a string. If the environment
variable is not set, returns the string specified in `default` or `nil` if none is
specified.
### Examples
```elixir
iex> Enviable.get_env("PORT")
nil
iex> Enviable.get_env("PORT", "4001")
"4001"
iex> Enviable.put_env("PORT", "4000")
iex> Enviable.get_env("PORT")
"4000"
iex> Enviable.get_env("PORT", "4001")
"4000"
```
"""
@doc group: "Delegates"
@spec get_env(String.t(), default) :: String.t() | default
when default: String.t() | nil
defdelegate get_env(varname, default \\ nil), to: System
@doc """
Sets an environment variable value.
Sets a new `value` for the environment variable `varname`.
"""
@doc group: "Delegates"
@spec put_env(String.t(), String.t()) :: :ok
defdelegate put_env(varname, value), to: System
@doc """
Sets multiple environment variables.
Sets a new value for each environment variable corresponding to each `{key, value}` pair
in `enum`. Keys and non-nil values are automatically converted to charlists. `nil`
values erase the given keys.
Overall, this is a convenience wrapper around `put_env/2` and `delete_env/2` with
support for different key and value formats.
"""
@doc group: "Delegates"
@spec put_env(Enumerable.t()) :: :ok
defdelegate put_env(var_map), to: System
end