envalid

package module
v0.3.1-beta.1 Latest Latest
Warning

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

Go to latest
Published: Jan 14, 2026 License: MIT Imports: 16 Imported by: 0

README

envalid

envalid is a library for validating and accessing environment variables in Go, inspired by a similar library in TypeScript.


⚡️ Quick start

package config

import (
  env "github.com/nyashmyash99/envalid"
)

type Author struct {
  Name  string
  Email string
}

type Config struct {
  Env         string
  HttpPort    uint16
  DatabaseUrl *env.PURL
  Authors     []Author
}

func Load() (*Config, error) {
  devDefDatabaseUrl, _ := env.ParseURL("postgres://local:password@localhost:5432/postgres?sslmode=disable")

  return env.Load[Config](env.Schema{
    "Env": env.Supplier(env.Str, env.Variable[string]{
      Key: "GO_ENV",
      Variants: []string{
        "development",
        "test",
        "staging",
        "production",
      },
      // Default: env.WithDefault("development"),
    }),
    "HttpPort": env.Supplier(env.Port, env.Variable[uint16]{
      Key:         "HTTP_PORT",
      Description: "HTTP server port",
      Default:     env.WithDefault(uint16(8080)),
    }),
    "DatabaseUrl": env.Supplier(env.URL, env.Variable[*env.PURL]{
      Key:        "DATABASE_URL",
      DevDefault: &devDefDatabaseUrl,
    }),
    "Authors": env.Supplier(env.JSON, env.Variable[[]Author]{
      Key: "AUTHORS",
    }),
  })
}

📖 Documentation

Validator types

Str - ensures that the env var exists.

Note that an empty string is considered a valid value.

Int32/Int64/Float32/Float64 - ensures that the env var is a number.

Bool - ensures that the env var is a bool-like.

true: 1, true, t, yes, y, on

false: 0, false, f, no, n, off

* List can be expanded by modifying BoolMap.

Port - ensures that the env var is a TCP/UDP port (1-65535).

IPv4/IPv6 - ensures that the env var is a IP address.

Domain - ensures that the env var is a domain (including IDN).

Minimal format: name.zone

Maximal format: subdomain.name.zone

Host - ensures that the env var is a host (IPv4, IPv6, domain, including localhost).

URL - ensures that the env var is a url.

Minimal format: protocol://host

Maximal format: protocol://username:password@host:port/path?params#anchor

JSON - ensures that the env var is a JSON in the specified format.

Email - ensures that the env var is an email address in the format user@domain.

Not what you need? I welcome contribution.

Validator options

Description - a string describing the env var.

Variants - an array of available values for the env var.

Note that it case-sensitive.

Default - a fallback value that is returned if the env var has not been specified. Specifying a default value effectively makes the env var optional.

Note that Default values takes precedence over validator and Variants, i.e. it may not match their conditions.

DevDefault - a fallback value that is returned if the env var has not been specified, GO_ENV var is specified and is not production.

Custom validators

package config

import (
	env "github.com/nyashmyash99/envalid"
)

// Create a validator.
var Admin = env.TrimmedParser(func(s string) (bool, error) {
  return s == "NyashMyash99", nil
})

type Config struct {
  Admin bool
}

func Load() (*Config, error) {
  return env.Load[Config](env.Schema{
    // Use the validator with the appropriate Variable type.
    "Admin": env.Supplier(Admin, env.Variable[bool]{
      Key: "USER",
    }),
  })
}

🤝 Contributing

I appreciate contributions!

Check out contributing guidelines to learn more.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Bool = ParseBool

Bool see ParseBool.

View Source
var BoolMap = map[string]bool{
	"1": true, "true": true, "t": true, "yes": true, "y": true, "on": true,
	"0": false, "false": false, "f": false, "no": false, "n": false, "off": false,
}

BoolMap maps bool-like strings to bool values.

View Source
var Domain = ParseDomain

Domain see ParseDomain.

View Source
var Email = ParseEmail

Email see ParseEmail.

View Source
var Float32 = ParseFloat32

Float32 see ParseFloat32.

View Source
var Float64 = ParseFloat64

Float64 see ParseFloat64.

View Source
var Host = ParseHost

Host see ParseHost.

View Source
var IPv4 = ParseIPv4

IPv4 see ParseIPv4.

View Source
var IPv6 = ParseIPv6

IPv6 see ParseIPv6.

View Source
var Int32 = ParseInt32

Int32 see ParseInt32.

View Source
var Int64 = ParseInt64

Int64 see ParseInt64.

View Source
var ParseBool = TrimmedParser(func(s string) (bool, error) {
	s = strings.ToLower(s)
	if v, ok := BoolMap[s]; ok {
		return v, nil
	}
	return false, errors.New("value must be a bool-like")
})

ParseBool parses the string s as a bool.

It returns an error if the string s is not a bool.

View Source
var ParseDomain = TrimmedParser(func(s string) (*PDomain, error) {
	if strings.HasSuffix(s, ".") {
		return nil, errors.New("value must not contain a trailing dot")
	}

	if strings.IndexByte(s, '.') == -1 {
		return nil, errors.New("value must contain a zone")
	}

	v, err := idna.Registration.ToASCII(s)
	if err != nil {
		return nil, errors.New("value must be a valid domain")
	}

	lastDot := strings.LastIndexByte(v, '.')
	nextDot := strings.LastIndexByte(v[:lastDot], '.')
	var subdomainPtr *string

	hostname := v
	if nextDot != -1 {
		hostname = v[nextDot+1:]
		sub := v[:nextDot]
		subdomainPtr = &sub
	}

	zone, _ := publicsuffix.PublicSuffix(s)

	return &PDomain{
		String:    v,
		Subdomain: subdomainPtr,
		Hostname:  hostname,
		Zone:      ptr(zone),
	}, nil
})

ParseDomain parses the string s as a domain (including IDN).

It returns an error if the string s is not a valid domain.

View Source
var ParseEmail = TrimmedParser(func(s string) (string, error) {
	address, err := mail.ParseAddress(s)
	if err != nil {
		return "", errors.New("value must be an email")
	}

	hasDiff := address.Address != s

	hasBracket := strings.IndexByte(address.Address, '[') != -1
	if hasDiff || hasBracket {
		return "", errors.New("value must be in format \"user@domain\"")
	}

	_, domain, _ := strings.Cut(address.Address, "@")
	if _, err := ParseDomain(domain); err != nil {
		return "", errors.New("email must contain a valid domain")
	}

	return s, nil
})

ParseEmail parses the string s as an email address in the format "user@domain".

It returns an error if the string s is not a valid email address.

View Source
var ParseFloat32 = TrimmedParser(func(s string) (float32, error) {
	v, err := parseFloatGeneric[float32](s)
	return v, err
})

ParseFloat32 parses the string s as a float.

It returns an error if the string s is not a number or out of range.

View Source
var ParseFloat64 = TrimmedParser(func(s string) (float64, error) {
	v, err := parseFloatGeneric[float64](s)
	return v, err
})

ParseFloat64 parses the string s as a float.

It returns an error if the string s is not a number or out of range.

View Source
var ParseHost = TrimmedParser(func(s string) (*PHost, error) {
	if strings.EqualFold(s, "localhost") {
		d := &PDomain{String: "localhost", Hostname: "localhost"}
		return &PHost{String: "localhost", Domain: d}, nil
	}

	if v, err := ParseIPv4(s); err == nil {
		return &PHost{String: v.String(), IP: v}, nil
	}
	if v, err := ParseIPv6(s); err == nil {
		return &PHost{String: v.String(), IP: v}, nil
	}
	if v, err := ParseDomain(s); err == nil {
		return &PHost{String: v.String, Domain: v}, nil
	}
	return nil, errors.New("value must be a host (IPv4, IPv6, or domain, including localhost)")
})

ParseHost parses a string as a host, which can be: - an IPv4 address; - an IPv6 address; - a domain (including localhost).

It returns an error if the string s is not a host.

View Source
var ParseIPv4 = TrimmedParser(func(s string) (net.IP, error) {
	v := net.ParseIP(s)
	if v == nil || v.To4() == nil {
		return nil, errors.New("value must be a valid IPv4 address")
	}
	return v, nil
})

ParseIPv4 parses the string s as a IPv4 address.

It returns an error if the string s is not a valid IPv4 address.

View Source
var ParseIPv6 = TrimmedParser(func(s string) (net.IP, error) {
	v := net.ParseIP(s)
	if v == nil || v.To16() == nil || v.To4() != nil {
		return nil, errors.New("value must be a valid IPv6 address")
	}
	return v, nil
})

ParseIPv6 parses the string s as a IPv6 address.

It returns an error if the string s is not a valid IPv6 address.

View Source
var ParseInt32 = TrimmedParser(func(s string) (int32, error) {
	v, err := parseIntGeneric[int32](s)
	return v, err
})

ParseInt32 parses the string s as an integer.

It returns an error if the string s is not a number or out of range.

View Source
var ParseInt64 = TrimmedParser(func(s string) (int64, error) {
	v, err := parseIntGeneric[int64](s)
	return v, err
})

ParseInt64 parses the string s as an integer.

It returns an error if the string s is not a number or out of range.

View Source
var ParsePort = TrimmedParser(func(s string) (uint16, error) {
	v, err := parseIntGeneric[int32](s)
	if err != nil || v < minPort || v > maxPort {
		return 0, fmt.Errorf("port must be a number between %d and %d", minPort, maxPort)
	}
	return uint16(v), nil
})

ParsePort parses the string s as a TCP/UDP port (1–65535).

It returns an error if the string s is not a number or out of range.

View Source
var ParseStr = TrimmedParser(func(s string) (string, error) {
	return s, nil
})

ParseStr parses string s as a trimmed string.

View Source
var ParseURL = TrimmedParser(func(s string) (*PURL, error) {
	uri, err := url.Parse(s)
	if err != nil {
		return nil, errors.New("value must be a URL")
	}

	if uri.Scheme == "" {
		return nil, errors.New("value must be in absolute format (protocol://...)")
	}

	if uri.Opaque != "" {
		return nil, errors.New("value must be in default url format")
	}

	if h := uri.Hostname(); h == "" {
		return nil, errors.New("value must contain a host")
	}

	var portPtr *uint16
	if p := uri.Port(); p != "" {
		pp, err := ParsePort(p)
		if err != nil {
			return nil, err
		}
		portPtr = &pp
	}

	host, err := ParseHost(uri.Hostname())
	if err != nil {
		return nil, errors.New("hostname must be a valid host (IPv4, IPv6, or domain, including localhost)")
	}

	var usernamePtr, passwordPtr *string
	if uri.User != nil {
		usernamePtr = ptr(uri.User.Username())
		p, _ := uri.User.Password()
		passwordPtr = &p
	}

	var pathPtr *string
	if p := uri.Path; p != "" && p != "/" {
		pathPtr = ptr(strings.TrimPrefix(p, "/"))
	}

	var fragmentPtr *string
	if uri.Fragment != "" {
		fragmentPtr = ptr(uri.Fragment)
	}

	return &PURL{
		String:   s,
		Protocol: strings.ToLower(uri.Scheme),
		Username: usernamePtr,
		Password: passwordPtr,
		Host:     *host,
		Port:     portPtr,
		Path:     pathPtr,
		Params:   uri.Query(),
		Anchor:   fragmentPtr,
	}, nil
})

ParseURL parses the string s as a url in the maximum format "protocol://username:password@host:port/path?params#anchor".

It returns an error if the string s is not a valid url.

View Source
var Port = ParsePort

Port see ParsePort.

View Source
var Str = ParseStr

Str see ParseStr.

View Source
var URL = ParseURL

URL see ParseURL.

Functions

func JSON

func JSON[T any](s string) (T, error)

JSON see ParseJSON.

func Load

func Load[T any](s Schema) (*T, error)

Load loads and validates environment variables, creating a config with type T using the provided Schema.

func ParseJSON

func ParseJSON[T any](s string) (T, error)

ParseJSON parses the string s as an JSON with schema T.

It returns an error if the string s is not a valid JSON.

func Validate

func Validate[T any](validator Validator[T], variable Variable[T]) (T, error)

Validate loads and validates an environment variable using the provided Validator.

It returns the default value if the env var is not set and a default value is specified; the dev default value if the env var is not set, a dev default value is specified, GO_ENV var is specified and is not "production"; a ValidationError with ErrNoValue if the env var is not set and no default value is specified; a ValidationError with ErrInvalidValue if the env var is not found in the available variants or is invalid for the target type.

func WithDefault

func WithDefault[T any](v T) *T

WithDefault provides a default value for a Variable.

Types

type ErrorCode

type ErrorCode int
const (

	// ErrNoValue occurs when an env var is not set and no default value is specified.
	ErrNoValue ErrorCode
	// ErrInvalidValue occurs when an env var is invalid for the target type.
	ErrInvalidValue
)

type PDomain

type PDomain struct {
	String    string  // e.g. docs.nyashmyash99.dev
	Subdomain *string // docs (see String)
	Hostname  string  // nyashmyash99.dev (see String)
	Zone      *string // dev (see String)
}

PDomain describes a parsed domain.

type PHost

type PHost struct {
	String string   // e.g. docs.nyashmyash99.dev, 127.0.0.1 or ::1
	Domain *PDomain // Optional domain, depending on the host type
	IP     net.IP   // Optional ip, depending on the host type
}

PHost describes a parsed host.

type PURL

type PURL struct {
	String   string     // e.g. http://user:pass@docs.nyashmyash99.dev:443/envalid?tab=documentation#quick-start
	Protocol string     // http (see String)
	Username *string    // user (see String)
	Password *string    // pass (see String)
	Host     PHost      // docs.nyashmyash99.dev (see String)
	Port     *uint16    // 443 (see String)
	Path     *string    // envalid (see String)
	Params   url.Values // tab="documentation" (see String)
	Anchor   *string    // quick-start
}

PURL describes a parsed url.

type Parser

type Parser[T any] func(string) (T, error)

Parser describes a generic function that parses a string into a value of type T.

It returns an error if the input is invalid for the target type.

func TrimmedParser

func TrimmedParser[T any](p Parser[T]) Parser[T]

TrimmedParser wraps the Parser, preprocessing the input with the strings.TrimSpace function.

type Schema

type Schema map[string]ValidatorSupplier

Schema maps config field names to their validators.

type ValidationError

type ValidationError struct {
	Code ErrorCode
	// contains filtered or unexported fields
}

ValidationError occurs when validation fails.

It implements the error interface.

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

type Validator

type Validator[T any] = Parser[T]

Validator see Parser.

type ValidatorSupplier

type ValidatorSupplier func() (any, error)

ValidatorSupplier describes a function that provides a validator for the Schema.

func Supplier

func Supplier[T any](validator Validator[T], variable Variable[T]) ValidatorSupplier

Supplier adapts the validator in ValidatorSupplier.

type Variable

type Variable[T any] struct {
	Key         string   // Required env var key (e.g. GO_ENV)
	Description string   // Optional description used in errors
	Variants    []string // Optional array of available values for the env var. It is case-sensitive.
	Default     *T       // Optional default value returned if the env var is not set. See Variable for priority determination.
	DevDefault  *T       // Optional default value returned if the env var is not set, GO_ENV var is specified and is not "production". See Variable for priority determination.
}

Variable describes the environment variable for validation.

Options priority: 1. DevDefault (if GO_ENV != production) 2. Default 3. Variants 4. Validator

Jump to

Keyboard shortcuts

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