README.md

# envoker

`envoker` is a small Gleam helper for reading environment variables through
[`envoy`](https://hex.pm/packages/envoy) with a typed API.

## Features

- required and optional readers
- built-in parsing for `String`, `Int`, `Float`, and `Bool`
- defaults for missing and empty values
- structured errors:
  - `MissingFieldError`
  - `EmptyFieldError`
  - `ParseFieldError` (includes source value and optional details)
- declarative config loader with error accumulation

## Installation

```sh
gleam add envoker
```

## Basic example

```gleam
import envoker
import gleam/option

pub fn load_config() {
  let assert Ok(host) =
    envoker.read_required_string("HOST", option.None)

  let assert Ok(port) =
    envoker.read_required_int("PORT", option.Some(8080))

  let assert Ok(debug) =
    envoker.read_optional_bool("DEBUG", option.Some(False))

  #(host, port, debug)
}
```

`String` values are always trimmed with `string.trim`, and `Bool` parsing is
case-insensitive for `true` and `false`.

## Detailed parse errors

By default, `read_*_with_parser` expects a parser of type
`fn(String) -> Result(a, Nil)` and returns parse errors without a textual reason.

If you want parser-provided details, use `read_*_with_parser_detailed` with
`Result(a, String)`:

```gleam
import envoker
import envoker/error
import gleam/option

pub fn parse_color(value: String) -> Result(String, String) {
  case value {
    "red" -> Ok("red")
    _ -> Error("only red is allowed")
  }
}

pub fn load_color() {
  let result =
    envoker.read_required_with_parser_detailed(
      "COLOR",
      option.None,
      "Color",
      parse_color,
    )

  case result {
    Ok(color) -> Ok(color)
    Error(error.ParseFieldError(_, _, _, _)) -> result
    Error(_) -> result
  }
}
```

## Declarative config loading with accumulated errors

The `envoker/config` module lets you define config fields as a list and load
them in one pass while collecting all errors:

```gleam
import envoker/config
import gleam/option

type AppConfig {
  AppConfig(host: String, port: Int, debug: option.Option(Bool))
}

fn set_host(config: AppConfig, host: String) -> AppConfig {
  AppConfig(..config, host: host)
}

fn set_port(config: AppConfig, port: Int) -> AppConfig {
  AppConfig(..config, port: port)
}

fn set_debug(config: AppConfig, debug: option.Option(Bool)) -> AppConfig {
  AppConfig(..config, debug: debug)
}

pub fn load() {
  config.load(
    AppConfig(host: "", port: 0, debug: option.None),
    [
      config.required_string("HOST", option.None, set_host),
      config.required_int("PORT", option.None, set_port),
      config.optional_bool("DEBUG", option.None, set_debug),
    ],
  )
}
```

Result:
- `Ok(config)` when all fields are loaded successfully
- `Error(List(EnvFieldError))` when there are errors (no fail-fast)

## Development

```sh
gleam test
gleam check
gleam format
```