enflag

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Mar 16, 2025 License: MIT Imports: 11 Imported by: 0

README

Enflag Go Reference Go Report Card codecov

Enflag is a zero-dependency generics-based Golang package for configuring applications via environment variables and command-line flags.

go get -u github.com/atelpis/enflag

Why Enflag?

While many solutions are available for Go configuration, Enflag was created to address a specific gap. Most options fall into two extremes:

  • Single-source tools limit flexibility by supporting only environment variables, flags, or a specific file type.
  • All-in-one solutions rely on reflection and struct tags, which can lead to hidden runtime errors, require memorizing library-specific syntax, and make debugging more difficult.

Type-safe and balanced: Enflag uses Go's generics for compile-time type safety, avoiding runtime errors. By focusing on environment variables and command-line flags, it covers the majority of use cases with a clean, straightforward API, making configuration predictable and easy to manage.

Features

  • Type-safe – Generics-based design, no runtime reflection
  • Container-optimized – Unified API for env vars and CLI flags
  • Flexible – Handles primitives, slices, JSON, and binary formats
  • Extensible – Custom parsers can be added easily
  • Error handling – Built-in handlers with custom callback support
  • Secure & lightweight – No external dependencies, suitable for security-sensitive applications

Usage

The Var function takes a pointer to a configuration variable and assigns its value according to the specified command-line flag or environment variable using the Bind method. Both sources are optional, but a flag has higher priority.

Additional methods like WithDefault, WithTimeLayout, etc. could be chained.

Behind the scenes, flags are handled by the standard library's flag.CommandLine flag set, meaning you get the same help-message output and error handling. Enflag uses generics to provide a cleaner and more convenient interface.

See the full runnable example

type MyServiceConf struct {
    BaseURL *url.URL
    DBHost  string
    Dates  []time.Time
}

func main() {
    var conf MyServiceConf

    // Basic usage
    enflag.Var(&conf.BaseURL).Bind("BASE_URL", "base-url")

    // Simple bindings can be defined using the less verbose BindVar shortcut
    enflag.BindVar(&conf.BaseURL, "BASE_URL", "base-url")

    // Add settings
    enflag.Var(&conf.DBHost).
        WithDefault("127.0.0.1").
        WithFlagUsage("db hostname").
        Bind("DB_HOST", "db-host")

    // Slice
    enflag.Var(&conf.Dates).
        WithSliceSeparator("|").       // Split the slice using a non-default separator
        WithTimeLayout(time.DateOnly). // Use a non-default time layout
        BindEnv("DATES")               // Bind only the env variable, ignore the flag

    enflag.Parse()
}

Enflag supports the most essential data types out of the box like binary, strings, numbers, time, URLs, IP and corresponding slices. You can also use VarFunc with a custom parser to work with other types:

See the full runnable example

func main() {
    var customVar int64

    parser := func(s string) (int64, error) {
        res, err := strconv.ParseInt(s, 10, 64)
        if err != nil {
            return 0, err
        }

        return res * 10, nil
    }
    enflag.VarFunc(&customVar, parser).Bind("CUSTOM", "custom")

    enflag.Parse()
}

What about YAML?

While numerous packages handle complex configurations using YAML, TOML, JSON, etc., Enflag focuses strictly on supporting container-oriented applications with a reasonable configuration scope. It prioritizes simplicity and zero dependency, making it a solid choice for microservices and cloud-native deployments. For more complex configuration needs, consider using a dedicated configuration management tool.

Documentation

Overview

Example
package main

import (
	"flag"
	"fmt"
	"net/url"
	"os"
	"strconv"

	"github.com/atelpis/enflag"
)

func main() {
	// emulate environment variables
	{
		os.Setenv("ENV", "develop")
		os.Setenv("DB_HOST", "localhost")
		os.Setenv("SECRET", "AQID")
	}

	var conf MyServiceConf

	// Both env and flag are defined and provided,
	// flag value will be used as it has higher priority.
	enflag.Var(&conf.DBHost).WithDefault("127.0.0.1").WithFlagUsage("db hostname").Bind("DB_HOST", "db-host")

	// Both env and flag are defined, but neither is provided.
	// The value of DBPort will default to 5432.
	enflag.Var(&conf.DBPort).WithDefault(5432).Bind("DB_PORT", "db-port")

	// Example of parsing a non-primitive type.
	enflag.Var(&conf.BaseURL).Bind("BASE_URL", "base-url")

	// Both env and flag sources are optional. Skip the flag definition,
	// and retrieve this value only from the environment.
	enflag.Var(&conf.Env).BindEnv("ENV")

	// By default binary variables as parsed as base64 string.
	enflag.Var(&conf.Secret).Bind("SECRET", "secret")

	// Custom parser
	{
		parser := func(s string) (int64, error) {
			res, err := strconv.ParseInt(s, 10, 64)
			if err != nil {
				return 0, err
			}

			return res * 10, nil
		}
		enflag.VarFunc(&conf.CustomVar, parser).Bind("CUSTOM", "custom")
	}

	// emulate flag values
	{
		flag.CommandLine.Set("db-host", "db.mysrv.int")
		flag.CommandLine.Set("base-url", "https://my-website.com")
		flag.CommandLine.Set("custom", "3")
	}

	enflag.Parse()

	RunMyService(&conf)
}

func RunMyService(c *MyServiceConf) error {
	fmt.Println("Starting service:")

	fmt.Printf("- Env: %s\n", c.Env)
	fmt.Printf("- DB Host: %s\n", c.DBHost)
	fmt.Printf("- DB Port: %d\n", c.DBPort)
	fmt.Printf("- Base URL: %v\n", c.BaseURL)
	fmt.Printf("- Custom var: %v\n", c.CustomVar)
	fmt.Printf("- Secret len: %d\n", len(c.Secret))

	return nil
}

type MyServiceConf struct {
	Env string

	DBHost  string
	DBPort  int
	BaseURL *url.URL
	Secret  []byte

	CustomVar int64
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var DecodeStringFunc = base64.StdEncoding.DecodeString

DecodeStringFunc is the default string-to-[]byte decoder.

View Source
var ErrorHandlerFunc = OnErrorExit

ErrorHandlerFunc is a function called after a value parser returns an error. See predefined options: OnErrorExit, OnErrorIgnore, and OnErrorLogAndContinue. It can also be replaced with a custom handler.

View Source
var OnErrorExit = func(err error, rawVal string, target any, envName string, flagName string) {
	OnErrorLogAndContinue(err, rawVal, target, envName, flagName)
	osExitFunc(2)
}

OnErrorExit prints the error and exits with status code 2.

View Source
var OnErrorIgnore = func(err error, rawVal string, target any, envName string, flagName string) {}

OnErrorIgnore silently ignores the error. If a default value is specified, it will be used.

View Source
var OnErrorLogAndContinue = func(err error, rawVal string, target any, envName string, flagName string) {
	_, _ = err, rawVal

	var msg string
	if envName != "" {
		msg = fmt.Sprintf("unable to parse env-variable %q as type %T\n", envName, target)
	} else if flagName != "" {
		msg = fmt.Sprintf("unable to parse flag %q as type %T\n", flagName, target)
	}

	flag.CommandLine.Output().Write([]byte(msg))
}

OnErrorLogAndContinue prints the error message but continues execution. If a default value is specified, it will be used.

View Source
var SliceSeparator = ","

SliceSeparator is the default separator for parsing slices.

View Source
var TimeLayout = time.RFC3339

TimeLayout is the default layout for parsing time.

Functions

func Bind deprecated

func Bind[T builtin](p *T, envName string, flagName string, value T, flagUsage string)

Deprecated: use Var or BindVar functions instead.

func BindFunc deprecated

func BindFunc[T any](
	p *T,
	envName string,
	flagName string,
	value T,
	flagUsage string,
	parser func(s string) (T, error),
)

Deprecated: use VarFunc function instead.

func BindVar added in v0.2.0

func BindVar[T builtin](p *T, envName string, flagName string, flagUsage ...string)

BindVar is a shorthand for Var(p).WithFlagUsage(flagUsage).Bind(envName, flagName), allowing the definition of a simple variable without verbose chaining. Only the first element of flagUsage will be used if provided.

For more complex cases, refer to the Var() function.

func Parse

func Parse()

Parse calls the standard library's `flag` package's `Parse()` function. Like the standard library's `flag` package, Parse() must be called after all flags have been defined.

Types

type Binding added in v0.2.0

type Binding[T builtin] struct {
	// contains filtered or unexported fields
}

Binding holds a pointer to a specified variable along with settings for parsing environment variables and command-line flags into it. It is a generic type constrained by `builtin`. For details on the supported types, refer to the `builtin` constraint.

A Binding should always be created using the Var function and finalized by calling Bind(), BindEnv(), or BindFlag().

Example usage:

var port int
Var(&port).Bind("PORT", "port")

func Var added in v0.2.0

func Var[T builtin](p *T) *Binding[T]

Var creates a new Binding for the given pointer p.

The created Binding should be finalized by calling Bind(), BindEnv(), or BindFlag().

Example usage:

var port int
Var(&port).Bind("PORT", "port")

For more advanced usage, methods like WithDefault and WithSliceSeparator can be chained. For example:

var ts time.Time
Var(&ts).
    WithFlagUsage("").
    WithTimeLayout(time.DateOnly).
    Bind("START_TIME", "start-time")

func (*Binding[T]) Bind added in v0.2.0

func (b *Binding[T]) Bind(envName string, flagName string)

Bind registers an environment variable and a command-line flag as data sources for this Binding. Both sources are optional. Use BindEnv or BindFlag to bind a single source.

Data sources are prioritized as follows: flag > environment variable > default value.

If a flag is used, Parse() must be called after all bindings are created.

func (*Binding[T]) BindEnv added in v0.2.0

func (b *Binding[T]) BindEnv(name string)

BindEnv is a shorthand for Bind when only an environment variable is needed.

func (*Binding[T]) BindFlag added in v0.2.0

func (b *Binding[T]) BindFlag(name string)

BindFlag is a shorthand for Bind when only a command-line flag is needed.

func (*Binding[T]) WithDecodeStringFunc added in v0.2.1

func (b *Binding[T]) WithDecodeStringFunc(f func(string) ([]byte, error)) *Binding[T]

WithDecodeStringFunc sets a function for decoding a string into []byte. This is only applicable to []byte variables.

If not explicitly set, the global variable DecodeStringFunc() will be used. The default decoder is base64.StdEncoding.DecodeString.

func (*Binding[T]) WithDefault added in v0.2.0

func (b *Binding[T]) WithDefault(val T) *Binding[T]

WithDefault sets the default value for Binding.

func (*Binding[T]) WithFlagUsage added in v0.2.0

func (b *Binding[T]) WithFlagUsage(usage string) *Binding[T]

WithFlagUsage sets the help message for the bound command-line flag.

func (*Binding[T]) WithSliceSeparator added in v0.2.0

func (b *Binding[T]) WithSliceSeparator(sep string) *Binding[T]

WithSliceSeparator sets a slice separator for the Binding. This is only applicable to slice types of the builtin constraint.

If not explicitly set, the global variable SliceSeparator will be used. The default value of the SliceSeparator is ",".

func (*Binding[T]) WithTimeLayout added in v0.2.0

func (b *Binding[T]) WithTimeLayout(layout string) *Binding[T]

WithTimeLayout sets a layout for parsing time for this Binding. This is only applicable to time variables.

If not explicitly set, the global variable TimeLayout() will be used. The default layout is time.RFC3339.

type CustomBinding added in v0.2.0

type CustomBinding[T any] struct {
	// contains filtered or unexported fields
}

CustomBinding holds a pointer to a variable along with a custom parser and additional settings.

A CustomBinding should always be created using VarFunc or its alternatives, such as VarJSON, and must be finalized by calling Bind(), BindEnv(), or BindFlag().

func VarFunc added in v0.2.0

func VarFunc[T any](p *T, parser func(string) (T, error)) *CustomBinding[T]

VarFunc creates a new CustomBinding for the given pointer p and the specified string parser function. The parser function is used to convert a string into the desired type T and will be used to parse both the environment variable and the flag.

func VarJSON added in v0.2.0

func VarJSON[T any](p *T) *CustomBinding[T]

VarJSON creates a new CustomBinding for the given pointer p and uses JSON unmarshaling as the parser for both the environment variable and the flag.

func (*CustomBinding[T]) Bind added in v0.2.0

func (b *CustomBinding[T]) Bind(envName string, flagName string)

Bind registers an environment variable and a command-line flag as data sources for this Binding. Both sources are optional. Use BindEnv or BindFlag to bind a single source.

Data sources are prioritized as follows: flag > environment variable > default value.

If a flag is used, Parse() must be called after all bindings are created.

func (*CustomBinding[T]) BindEnv added in v0.2.0

func (b *CustomBinding[T]) BindEnv(name string)

BindEnv is a shorthand for Bind when only an environment variable is needed.

func (*CustomBinding[T]) BindFlag added in v0.2.0

func (b *CustomBinding[T]) BindFlag(name string)

BindFlag is a shorthand for Bind when only a command-line flag is needed.

func (*CustomBinding[T]) WithDefault added in v0.2.0

func (b *CustomBinding[T]) WithDefault(val T) *CustomBinding[T]

WithDefault sets the default value for the CustomBinding.

func (*CustomBinding[T]) WithFlagUsage added in v0.2.0

func (b *CustomBinding[T]) WithFlagUsage(usage string) *CustomBinding[T]

WithFlagUsage sets the help message for the bound command-line flag.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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