env

package
v0.0.0-...-0718798 Latest Latest
Warning

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

Go to latest
Published: Dec 31, 2025 License: MIT Imports: 19 Imported by: 0

README

env

Go Reference Build Status

A library for declaring composable, reusable Go structs that load values parsed from environment variables.

✨ Features

🚀 Install

go get go.chrisrx.dev/x/env

[!IMPORTANT] This package is in an experimental module, meaning the API might not (yet) be stable. When it graduates to its own module the path will change (e.g. go.chrisrx.dev/env) and will be aliased to the new module here, indefinitely.

📋 Usage

var opts = env.MustParseFor[struct {
	Addr           string        `env:"ADDR" default:":8080" validate:"split_addr().port > 1024"`
	Dir            http.Dir      `env:"DIR" $default:"tempdir()"`
	ReadTimeout    time.Duration `env:"READ_TIMEOUT" default:"2m"`
	WriteTimeout   time.Duration `env:"WRITE_TIMEOUT" default:"30s"`
	MaxHeaderBytes int           `env:"MAX_HEADER_BYTES" $default:"1 << 20"`
}](env.RootPrefix("FILESERVER"))

func main() {
	s := &http.Server{
		Addr:           opts.Addr,
		Handler:        http.FileServer(opts.Dir),
		ReadTimeout:    opts.ReadTimeout,
		WriteTimeout:   opts.WriteTimeout,
		MaxHeaderBytes: opts.MaxHeaderBytes,
	}
	log.Printf("serving %s at %s ...\n", opts.Dir, opts.Addr)
	if err := s.ListenAndServe(); err != nil {
		log.Fatal(err)
	}
}

See testdata/pg for a more complete example of a reusable configuration struct.

Supported types

Basic types:

  • string, []byte
  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • float32, float64
  • bool

Go slices, maps and structs are also supported.

[!WARNING] Slices and maps cannot nest slices, maps or structs.

Any type that is convertible to a handled type can be used as well. For example, http.Dir is a type contructed from a string.

Built-in custom parsers:

  • time.Time, time.Duration
  • url.URL
  • rsa.PublicKey
  • x509.Certificate
  • net.HardwareAddr
  • net.IP

Any existing type that implements encoding.TextUnmarshaler will also work.

[!NOTE] Pointers to any types listed will also work.

Tags

The following struct tags are used to define how env reads from environment variables into values:

Name Description
env The name of the environment variable to load values from. If not present, the field is ignored.
default Specifies a default value for a field. This is used when the environment variable is not set.
$default Use an expression to set a default field value. This is used when the environment variable is not set.
validate Use a boolean expression to validate the field value.
required Set the field as required.
sep Separator used when parsing array/slice values. Defaults to ,.
layout Layout used to format/parse time.Time fields. Defaults to time.RFC3339Nano.
Auto-prefix

[!IMPORTANT] Features like auto-prefix are important to making structs composable, which is why it is on by default.

Nested structs will automatically prepend the field name to the environment variable name for nested fields:

type Config struct {
    DB struct {
        Host string `env:"HOST"`
        Port int    `env:"PORT"`
    }
}

The above struct has a nested anonymous struct with the field name DB, which results in the nested fields values being loaded from DB_HOST and DB_PORT.

The prefix used for a struct can be set explicitly using the env tag on the struct itself:

type Config struct {
    DB struct {
        Host string `env:"HOST"`
        Port int    `env:"PORT"`
    } `env:"USERS_DB"`
}

Setting env:"USERS_DB" here means that the environment variables are now loaded from USERS_DB_HOST/USERS_DB_PORT.

[!TIP] You can prevent auto-prefix on a nested struct by declaring it as an anonymous field, aka embedding.

Registering custom parsers

The Register function can be used to define custom type parsers. It takes a non-pointer type parameter for the custom type and the parser function as the argument:

env.Register[net.IP](func(field Field, s string) (any, error) {
    return net.ParseIP(s), nil
})

The type parameter must be a non-pointer, but registering a type will always work with both the provided type and the pointer version without needing to register them both.

Default expressions

Default values can be generated using a Go-like expression language:

type Config struct {
    Start time.Time `env:"START" $default:"now()"`
}

Unlike default, the $default tag will always evaluate the tag value as an expression. There are quite a few builtins available that can be used and custom ones can be added. Functions like now() return a time.Time so that method chaining can be used to construct more complex expressions:

type Config struct {
    End time.Time `env:"END" $default:"now().add(duration('1h'))"`
}

A couple interesting things are happening here. For one, the above is syntactic sugar for time.Now().Add(-1 * time.Hour). It works by transforming the method lookups for Go types from snakecase to the expected Go method text case. For example, now().is_zero() will call time.Now().IsZero(), under-the-hood.

The other interesting thing happening here is that strings are specified using single quotes. This was a workaround to deal with the strict requirements for parsing struct tags which requires using double quotes to enclose tag values.

Field validation

The validate tag can be used to specify a boolean expression that checks the value of a field once parsing is finished. This can be used to verify things like minimum string length:

type Config struct {
    Name string `env:"NAME" validate:"len(Name) > 3"`
}

The field value is injected into the expression scope allowing for it to be referenced by the field name.

[!TIP] Along with the exact name, the pseudo-variable self can be used to refer to the field value

Documentation

Overview

Package env provides functions for loading structs from environment variables.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func MustParse

func MustParse(v any, opts ...ParserOption)

Parse is a convenience function for calling Parse that panics if an error is encountered.

func MustParseFor

func MustParseFor[T any](opts ...ParserOption) T

MustParseFor is a convenience function for calling ParseFor that panics if an error is encountered.

Example
package main

import (
	"fmt"
	"os"

	"go.chrisrx.dev/x/env"
)

func main() {
	_ = os.Setenv("MYSERVICE_ADDR", ":8443")

	opts := env.MustParseFor[struct {
		Addr string `env:"ADDR" default:":8080" validate:"split_addr(self).port > 1024"`
	}](env.RootPrefix("MYSERVICE"))

	fmt.Printf("MYSERVICE_ADDR: %q\n", opts.Addr)

}
Output:

MYSERVICE_ADDR: ":8443"
Example (FileServer)
package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"time"

	"go.chrisrx.dev/x/context"
	"go.chrisrx.dev/x/env"
)

var opts = env.MustParseFor[struct {
	Addr           string        `env:"ADDR" default:":8080" validate:"split_addr().port > 1024"`
	Dir            http.Dir      `env:"DIR" $default:"tempdir()"`
	ReadTimeout    time.Duration `env:"TIMEOUT" default:"2m"`
	WriteTimeout   time.Duration `env:"WRITE_TIMEOUT" default:"30s"`
	MaxHeaderBytes int           `env:"MAX_HEADER_BYTES" $default:"1 << 20"`
}](env.RootPrefix("FILESERVER"))

func main() {
	ctx := context.Shutdown()

	s := &http.Server{
		Addr:           opts.Addr,
		Handler:        http.FileServer(opts.Dir),
		ReadTimeout:    opts.ReadTimeout,
		WriteTimeout:   opts.WriteTimeout,
		MaxHeaderBytes: opts.MaxHeaderBytes,
		BaseContext:    func(net.Listener) context.Context { return ctx },
	}
	ctx.AddHandler(func() {
		fmt.Println("\rCTRL+C pressed, attempting graceful shutdown ...")
		if err := s.Shutdown(ctx); err != nil {
			panic(err)
		}
	})
	log.Printf("serving %s at %s ...\n", opts.Dir, opts.Addr)
	if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
		log.Fatal(err)
	}
}

func Parse

func Parse(v any, opts ...ParserOption) error

Parse parses tags for the provided struct and sets values from environment variables. It will only set values that have the `env` tag. Value must be a pointer value to allow setting values.

func ParseFor

func ParseFor[T any](opts ...ParserOption) (T, error)

ParseFor parses tags for the provided struct and sets values from environment variables. It accepts a struct or pointer to a struct. If a non-pointer, a copy of the struct with values set will be returned.

func Print

func Print(v any, opts ...ParserOption)

Print prints a representation of a struct to standard output. It using the same rules as Parser.

Example
package main

import (
	"net/http"
	"time"

	"go.chrisrx.dev/x/env"
)

func main() {
	env.Print(env.MustParseFor[struct {
		Addr           string        `env:"ADDR" default:":8080" validate:"split_addr(self).port > 1024"`
		Dir            http.Dir      `env:"DIR" $default:"tempdir()"`
		ReadTimeout    time.Duration `env:"TIMEOUT" default:"2m"`
		WriteTimeout   time.Duration `env:"WRITE_TIMEOUT" default:"30s"`
		MaxHeaderBytes int           `env:"MAX_HEADER_BYTES" $default:"1 << 20"`
	}]())

}
Output:

Addr{
  env=ADDR
  default=:8080
  value=:8080
}
Dir{
  env=DIR
  value=/tmp
}
ReadTimeout{
  env=TIMEOUT
  default=2m
  value=2m0s
}
WriteTimeout{
  env=WRITE_TIMEOUT
  default=30s
  value=30s
}
MaxHeaderBytes{
  env=MAX_HEADER_BYTES
  value=1048576
}

func Register

func Register[T any](fn CustomParserFunc)

Register registers a custom parser with the provided type parameter. The type parameter must be a non-pointer type, however, registering a type will match for parsing for both the pointer and non-pointer of the type.

Types

type CustomParserFunc

type CustomParserFunc func(Field, string) (any, error)

CustomParserFunc is the function signature for a custom type parser.

type Deferred

type Deferred bool

Deferred is a special type used in fields to configure calling methods on structs after parsing is done.

type Field

type Field struct {
	Name      string
	Anonymous bool
	Exported  bool

	// tags
	Env         string
	Default     string
	DefaultExpr string
	Validate    string
	Separator   string
	Required    bool
	Layout      string

	DeferredMethod string
	// contains filtered or unexported fields
}

Field represents a parsed struct field.

func (Field) Key

func (f Field) Key() string

Key returns the environment variable name in screaming snake case. It includes any prefixes defined for this field.

type Parser

type Parser struct {
	DisableAutoPrefix bool
	RootPrefix        string
	RequireTagged     bool
	// contains filtered or unexported fields
}

Parser is an environment variable parser for structs.

func NewParser

func NewParser(opts ...ParserOption) *Parser

NewParser constructs a new Parser using the provided options.

func (*Parser) Parse

func (p *Parser) Parse(v any) error

Parse loads values into a struct from environment variables. It only accepts a pointer to a struct.

type ParserOption

type ParserOption func(*Parser)

func DisableAutoPrefix

func DisableAutoPrefix() ParserOption

DisableAutoPrefix is an option for Parser that disables the auto-prefix feature.

func RequireTagged

func RequireTagged() ParserOption

RequireTagged is an option for Parser that makes all struct fields required. This applies regardless of whether the `env` tag is set.

func RootPrefix

func RootPrefix(prefix string) ParserOption

RootPrefix is an option for Parser that sets a root prefix for environment variable names.

Jump to

Keyboard shortcuts

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