config

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package config loads configuration from the environment into a struct, once, at boot — and fails the boot when something required is missing.

The failure mode this exists to kill: `os.Getenv` scattered through the code, read lazily, so a missing variable is discovered by the first request that needs it at 2am rather than by the process refusing to start. Declare the whole surface as a struct, call Load, and a misconfigured deployment cannot come up.

type Config struct {
    Port     int           `env:"PORT" default:"8080"`
    DSN      string        `env:"DATABASE_URL" required:"true" secret:"true"`
    Timeout  time.Duration `env:"HTTP_TIMEOUT" default:"30s"`
    Origins  []string      `env:"CORS_ORIGINS" default:"*"`
    Redis    RedisConfig   `envPrefix:"REDIS_"`
}
Example

The whole configuration surface is one struct, loaded once. LoadFrom takes a lookup so a test needs no environment mutation.

package main

import (
	"fmt"
	"time"

	"github.com/IncepTools/go/src/core/config"
)

type redisConfig struct {
	Addr string `env:"ADDR" default:"localhost:6379"`
	DB   int    `env:"DB" default:"0"`
}

type appConfig struct {
	Port           int           `env:"PORT" default:"8080"`
	RequestTimeout time.Duration `env:"REQUEST_TIMEOUT" default:"30s"`
	Origins        []string      `env:"CORS_ORIGINS" default:"https://app.example.com"`
	DatabaseURL    string        `env:"DATABASE_URL" required:"true" secret:"true"`
	Redis          redisConfig   `envPrefix:"REDIS_"`
}

func main() {
	var cfg appConfig
	err := config.LoadFrom(&cfg, config.MapLookup(map[string]string{
		"DATABASE_URL": "postgres://user:hunter2@db/shop",
		"REDIS_ADDR":   "redis.internal:6379",
	}))
	if err != nil {
		panic(err)
	}

	fmt.Println(cfg.Port)           // from the default
	fmt.Println(cfg.RequestTimeout) // parsed as a duration, not nanoseconds
	fmt.Println(cfg.Origins)
	fmt.Println(cfg.Redis.Addr) // the prefixed variable
	fmt.Println(cfg.Redis.DB)
}
Output:
8080
30s
[https://app.example.com]
redis.internal:6379
0

Index

Examples

Constants

View Source
const (
	TagEnv      = "env"       // variable name
	TagDefault  = "default"   // value used when the variable is unset or empty
	TagRequired = "required"  // "true" to fail the load when unset
	TagPrefix   = "envPrefix" // prefix applied to a nested struct's variables
	TagSecret   = "secret"    // "true" to mask the value in Describe output
	TagSep      = "sep"       // slice separator, default ","
)

Tag names read by Load.

Variables

This section is empty.

Functions

func Describe

func Describe(src any) []string

Describe returns one line per configured variable, with any field tagged `secret:"true"` masked. Log it at boot: knowing what the process actually read beats guessing which ConfigMap won.

Example

Describe is safe to log at boot: anything tagged secret is masked.

package main

import (
	"fmt"
	"time"

	"github.com/IncepTools/go/src/core/config"
)

type redisConfig struct {
	Addr string `env:"ADDR" default:"localhost:6379"`
	DB   int    `env:"DB" default:"0"`
}

type appConfig struct {
	Port           int           `env:"PORT" default:"8080"`
	RequestTimeout time.Duration `env:"REQUEST_TIMEOUT" default:"30s"`
	Origins        []string      `env:"CORS_ORIGINS" default:"https://app.example.com"`
	DatabaseURL    string        `env:"DATABASE_URL" required:"true" secret:"true"`
	Redis          redisConfig   `envPrefix:"REDIS_"`
}

func main() {
	cfg := appConfig{
		Port:        8080,
		DatabaseURL: "postgres://user:hunter2@db/shop",
	}
	for _, line := range config.Describe(&cfg) {
		fmt.Println(line)
	}
}
Output:
CORS_ORIGINS=[]
DATABASE_URL=po********op
PORT=8080
REDIS_ADDR=
REDIS_DB=0
REQUEST_TIMEOUT=0s

func Load

func Load(dst any) error

Load fills dst (a non-nil pointer to a struct) from the process environment.

Example (ReportsEveryProblemAtOnce)

Every problem is reported in one pass. Fixing configuration one restart at a time wastes a deploy each round.

package main

import (
	"fmt"

	"github.com/IncepTools/go/src/core/config"
)

func main() {
	type cfg struct {
		DSN  string `env:"DATABASE_URL" required:"true"`
		Port int    `env:"PORT"`
	}
	var c cfg
	err := config.LoadFrom(&c, config.MapLookup(map[string]string{
		"PORT": "eighty",
	}))

	fmt.Println(err)
}
Output:
config: 2 problem(s):
  - DATABASE_URL is required but not set
  - PORT: "eighty" is not an integer

func LoadDotEnv

func LoadDotEnv(path string) error

LoadDotEnv reads a .env file and sets any variable that is not ALREADY in the environment. The precedence is deliberate: a real environment variable always wins, so a committed .env cannot override what a deployment sets.

A missing file is not an error — that is the normal production case.

func LoadFrom

func LoadFrom(dst any, lookup Lookup) error

LoadFrom fills dst from an arbitrary lookup.

func OSLookup

func OSLookup(key string) (string, bool)

OSLookup reads the process environment.

Types

type Error

type Error struct{ Problems []string }

Error reports every problem found in one Load, rather than the first. Fixing configuration one restart at a time is a waste of a deploy.

func (*Error) Error

func (e *Error) Error() string

type Lookup

type Lookup func(key string) (string, bool)

Lookup resolves a variable name. It exists so tests and the CLI can load a config from a map instead of the real environment.

func MapLookup

func MapLookup(m map[string]string) Lookup

MapLookup reads a fixed map. Useful in tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL