# `Envious.Env`
[🔗](https://github.com/jax-ex/envious/blob/main/lib/envious/env.ex#L1)

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:

- `optional/1` - Returns the value or `nil` if not set
- `optional/2` - Returns the value or a default if not set
- `required!/1` - Returns the value or raises `System.EnvError` if not set

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:

- `System.EnvError` - Raised by `required!/1` when an environment variable is missing
- `ArgumentError` - Raised by type converters when a value cannot be converted

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

# `atom!`

```elixir
@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!`

```elixir
@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!`

```elixir
@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!`

```elixir
@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!`

```elixir
@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!`

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

# `ip!`

```elixir
@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!`

```elixir
@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!`

```elixir
@spec list!(String.t() | nil, keyword() | (String.t() -&gt; any())) ::
  [String.t()] | [any()]
```

# `list!`

```elixir
@spec list!(String.t() | nil, (String.t() -&gt; any()), keyword()) :: [any()]
```

# `module!`

```elixir
@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`

```elixir
@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`

```elixir
@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!`

```elixir
@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!`

```elixir
@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", ...}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
