Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: update README #46

Merged
merged 1 commit into from
Nov 12, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 66 additions & 104 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

![logo](logo.svg)

A lightweight package for loading environment variables into structs
🔍 Load environment variables into a config struct

[![awesome-go](https://awesome.re/badge.svg)](https://github.com/avelino/awesome-go#configuration)
[![checks](https://github.com/go-simpler/env/actions/workflows/checks.yml/badge.svg)](https://github.com/go-simpler/env/actions/workflows/checks.yml)
Expand All @@ -15,44 +15,42 @@ A lightweight package for loading environment variables into structs
## 📌 About

This package is made for apps that [store config in environment variables][1].
Its purpose is to replace multiple fragmented `os.Getenv` calls in `main.go`
with a single struct definition, which simplifies config management and improves
code readability.
Its purpose is to replace fragmented `os.Getenv` calls in `main.go` with a single struct definition,
which simplifies config management and improves code readability.

## 🚀 Features

* Support for all common types and user-defined types
* Options: [required](#required), [expand](#expand), [slice separator](#slice-separator)
* Configurable [source](#source) of environment variables
* Auto-generated [usage message](#usage-message)

## 📦 Install

```shell
go get go-simpler.org/env
```

## 🚀 Features

* Simple API
* Dependency-free
* Per-variable options: [required](#required), [expand](#expand)
* Global options: [source](#source), [slice separator](#slice-separator)
* Auto-generated [usage message](#usage-message)

## 📋 Usage

`Load` is the main function of this package. It loads environment variables into
the provided struct.
`Load` is the main function of the package.
It loads environment variables into the given struct.

The struct fields must have the `env:"VAR"` struct tag, where `VAR` is the name
of the corresponding environment variable. Unexported fields are ignored.
The struct fields must have the `env:"VAR"` struct tag,
where VAR is the name of the corresponding environment variable.
Unexported fields are ignored.

```go
os.Setenv("PORT", "8080")

var cfg struct {
Port int `env:"PORT"`
}
if err := env.Load(&cfg); err != nil {
if err := env.Load(&cfg, nil); err != nil {
fmt.Println(err)
}

fmt.Println(cfg.Port)
// Output: 8080
fmt.Println(cfg.Port) // 8080
```

### Supported types
Expand All @@ -64,78 +62,50 @@ fmt.Println(cfg.Port)
* `time.Duration`
* `encoding.TextUnmarshaler`
* slices of any type above
* nested structs of any depth

See the `strconv.Parse*` functions for parsing rules.

Nested structs of any depth level are supported, only the leaves of the config
tree must have the `env` tag.

```go
os.Setenv("HTTP_PORT", "8080")

var cfg struct {
HTTP struct {
Port int `env:"HTTP_PORT"`
}
}
if err := env.Load(&cfg); err != nil {
fmt.Println(err)
}

fmt.Println(cfg.HTTP.Port)
// Output: 8080
```
See the `strconv.Parse*` functions for the parsing rules.
User-defined types can be used by implementing the `encoding.TextUnmarshaler` interface.

### Default values

Default values can be specified using the `default` struct tag:
Default values can be specified using the `default:"VALUE"` struct tag:

```go
os.Unsetenv("PORT")

var cfg struct {
Port int `env:"PORT" default:"8080"`
}
if err := env.Load(&cfg); err != nil {
if err := env.Load(&cfg, nil); err != nil {
fmt.Println(err)
}

fmt.Println(cfg.Port)
// Output: 8080
fmt.Println(cfg.Port) // 8080
```

### Per-variable options

The name of the environment variable can be followed by comma-separated options
in the form of `env:"VAR,option1,option2,..."`.

#### Required
### Required

Use the `required` option to mark the environment variable as required. In case
no such variable is found, an error of type `NotSetError` will be returned.
Use the `required` option to mark an environment variable as required.
If it is not set, an error of type `NotSetError` is returned.

```go
os.Unsetenv("HOST")
os.Unsetenv("PORT")

var cfg struct {
Host string `env:"HOST,required"`
Port int `env:"PORT,required"`
Port int `env:"PORT,required"`
}
if err := env.Load(&cfg); err != nil {
if err := env.Load(&cfg, nil); err != nil {
var notSetErr *env.NotSetError
if errors.As(err, &notSetErr) {
fmt.Println(notSetErr.Names)
fmt.Println(notSetErr) // env: PORT is required but not set
}
}

// Output: [HOST PORT]
```

#### Expand
### Expand

Use the `expand` option to automatically expand the value of the environment
variable using `os.Expand`.
Use the `expand` option to automatically expand the value of an environment variable using `os.Expand`.

```go
os.Setenv("PORT", "8080")
Expand All @@ -144,91 +114,83 @@ os.Setenv("ADDR", "localhost:${PORT}")
var cfg struct {
Addr string `env:"ADDR,expand"`
}
if err := env.Load(&cfg); err != nil {
if err := env.Load(&cfg, nil); err != nil {
fmt.Println(err)
}

fmt.Println(cfg.Addr)
// Output: localhost:8080
fmt.Println(cfg.Addr) // localhost:8080
```

### Global options

`Load` also accepts global options that apply to all environment variables.
### Slice separator

#### Source

By default, `Load` retrieves environment variables values directly from OS.
To use a different source, provide an implementation of the `Source` interface via the `WithSource` option.

```go
// Source represents a source of environment variables.
type Source interface {
// LookupEnv retrieves the value of the environment variable named by the key.
LookupEnv(key string) (value string, ok bool)
}
```

Here's an example of using `Map`, a builtin `Source` implementation useful in tests:
Space is the default separator used to parse slice values.
It can be changed with `Options.SliceSep`:

```go
m := env.Map{"PORT": "8080"}
os.Setenv("PORTS", "8080,8081,8082")

var cfg struct {
Port int `env:"PORT"`
Ports []int `env:"PORTS"`
}
if err := env.Load(&cfg, env.WithSource(m)); err != nil {
if err := env.Load(&cfg, &env.Options{SliceSep: ","}); err != nil {
fmt.Println(err)
}

fmt.Println(cfg.Port)
// Output: 8080
fmt.Println(cfg.Ports) // [8080 8081 8082]
```

#### Slice separator
### Source

Space is the default separator when parsing slice values. It can be changed
using the `WithSliceSeparator` option:
By default, `Load` retrieves environment variables directly from OS.
To use a different source, pass an implementation of the `Source` interface via `Options.Source`.

```go
os.Setenv("PORTS", "8080;8081;8082")
type Source interface {
LookupEnv(key string) (value string, ok bool)
}
```

Here's an example of using `Map`, a `Source` implementation useful in tests:

```go
m := env.Map{"PORT": "8080"}

var cfg struct {
Ports []int `env:"PORTS"`
Port int `env:"PORT"`
}
if err := env.Load(&cfg, env.WithSliceSeparator(";")); err != nil {
if err := env.Load(&cfg, &env.Options{Source: m}); err != nil {
fmt.Println(err)
}

fmt.Println(cfg.Ports)
// Output: [8080 8081 8082]
fmt.Println(cfg.Port) // 8080
```

### Usage message

`env` supports printing an auto-generated usage message the same way the `flag` package does it.
The `Usage` function prints a usage message documenting all defined environment variables.

```go
os.Unsetenv("DB_HOST")
os.Unsetenv("DB_PORT")

var cfg struct {
DB struct {
Host string `env:"DB_HOST,required" desc:"database host"`
Port int `env:"DB_PORT,required" desc:"database port"`
Host string `env:"DB_HOST,required" usage:"database host"`
Port int `env:"DB_PORT,required" usage:"database port"`
}
HTTPPort int `env:"HTTP_PORT" default:"8080" desc:"http server port"`
HTTPPort int `env:"HTTP_PORT" default:"8080" usage:"http server port"`
}
if err := env.Load(&cfg); err != nil {
if err := env.Load(&cfg, nil); err != nil {
fmt.Println(err)
env.Usage(&cfg, os.Stdout)
}
```

// Output: env: [DB_HOST DB_PORT] are required but not set
// Usage:
// DB_HOST string required database host
// DB_PORT int required database port
// HTTP_PORT int default 8080 http server port
```
Usage:
DB_HOST string required database host
DB_PORT int required database port
HTTP_PORT int default 8080 http server port
```

[1]: https://12factor.net/config
Loading