Skip to content

Repository files navigation

cfg

A convenient, type-safe configuration library for Go that populates your structs from multiple sources including environment variables, command-line flags, JSON files, and more.

Features

  • Multiple Providers: Load configuration from environment variables, flags, JSON, or static values
  • Type Safety: Automatic type conversion for primitive types, slices, maps, and custom types
  • Struct Tags: Simple tag-based configuration using env, flag, json tags
  • Composable: Chain multiple providers with fallback behavior
  • Extensible: Create custom providers for any configuration source
  • CLI Support: Built-in CLI framework with command hierarchies and help generation

Quick Start

package main
import (
"context""fmt""os""github.com/upfluence/cfg"
)
typeConfigstruct {
Hoststring`env:"HOST" flag:"host"`Portint`env:"PORT" flag:"port"`Debugbool`env:"DEBUG" flag:"debug"`Timeout time.Duration`env:"TIMEOUT" flag:"timeout"`
}
funcmain() {
varcConfigctx:=context.Background()
iferr:=cfg.NewDefaultConfigurator().Populate(ctx, &c); err!=nil {
fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
os.Exit(1)
}
fmt.Printf("Server: %s:%d (debug=%v, timeout=%s)\n",
c.Host, c.Port, c.Debug, c.Timeout)
}

The default configurator uses both environment variables and command-line flags:

# Using environment variables
$ HOST=localhost PORT=8080 DEBUG=true TIMEOUT=30s ./app
Server: localhost:8080 (debug=true, timeout=30s)
# Using flags
$ ./app --host localhost --port 8080 --debug --timeout 30s
Server: localhost:8080 (debug=true, timeout=30s)
# Boolean flags support --no- prefix
$ ./app --host localhost --no-debug
Server: localhost: (debug=false, timeout=0s)

Providers

Environment Variables

The env provider reads from environment variables with automatic uppercasing and underscore conversion.

typeConfigstruct {
APIKeystring`env:"API_KEY"`BaseURLstring`env:"BASE_URL"`
}

You can also use prefixed environment variables:

provider:=env.NewProvider("MYAPP") // Looks for MYAPP_* variablesconfigurator:=cfg.NewConfigurator(provider)

Command-Line Flags

The flags provider parses command-line arguments with support for short flags, equals syntax, and boolean negation.

typeConfigstruct {
Verbosebool`flag:"verbose,v"`// --verbose or -vOutputstring`flag:"output,o"`// --output file or -o fileForcebool`flag:"force"`// --force or --no-force
}

Supported formats:

  • --flag value
  • --flag=value
  • -f value
  • --flag (boolean true)
  • --no-flag (boolean false)

JSON Files

Load configuration from JSON files:

file, _:=os.Open("config.json")
deferfile.Close()
jsonProvider:=json.NewProviderFromReader(file)
configurator:=cfg.NewConfigurator(jsonProvider)

The JSON provider supports nested structures using dot notation:

{
"database": {
"host": "localhost",
"port": 5432
}
}
typeConfigstruct {
DBHoststring`json:"database.host"`DBPortint`json:"database.port"`
}

Static Provider

Provide configuration from Go values directly:

staticProvider:=static.NewProvider(map[string]interface{}{
"host": "localhost",
"port": 8080,
})
configurator:=cfg.NewConfigurator(staticProvider)

Custom Providers

Create your own provider by implementing the Provider interface:

typeProviderinterface {
StructTag() stringProvide(context.Context, string) (string, bool, error)
}

Example custom provider:

typeConsulProviderstruct{}
func (p*ConsulProvider) StructTag() string { return"consul" }
func (p*ConsulProvider) Provide(ctx context.Context, keystring) (string, bool, error) {
// Fetch from Consulvalue, err:=consulClient.Get(key)
iferr==ErrNotFound {
return"", false, nil
}
returnvalue, true, err
}

Provider Chaining

Providers are evaluated in order. The first provider that returns a value wins:

configurator:=cfg.NewConfigurator(
flags.NewDefaultProvider(), // Check flags firstenv.NewDefaultProvider(), // Then environment variablesjsonProvider, // Then JSON filestaticProvider, // Finally defaults
)

Supported Types

The library automatically handles type conversion for:

  • Primitives: string, bool, all int and float types
  • Time: time.Duration (via time.ParseDuration), time.Time (RFC3339 format)
  • Slices: Comma-separated values ("a,b,c"[]string{"a", "b", "c"})
  • Maps: Key-value pairs ("k1=v1,k2=v2"map[string]string{"k1": "v1", "k2": "v2"})
  • Nested Structs: Dot notation for nested fields
  • Custom Types: Any type implementing:
    • json.Unmarshaler
    • encoding.TextUnmarshaler
    • interface { Parse(string) error }

Advanced Usage

Configuration Options

configurator:=cfg.NewConfiguratorWithOptions(
cfg.WithProviders(myProvider),
cfg.IgnoreMissingTag, // Don't error on fields without tags
)

Default Values

Use the default struct tag to provide fallback values when no other provider supplies one:

typeConfigstruct {
Hoststring`env:"HOST" flag:"host" default:"localhost"`Portint`env:"PORT" flag:"port" default:"8080"`Verbosebool`env:"VERBOSE" default:"false"`
}

The default provider has the lowest priority — any value from environment variables, flags, or other providers takes precedence. It is included automatically in NewDefaultConfigurator. Nested structs work as expected: only fields with an explicit default tag receive a value.

Required Fields

Use the required struct tag together with the HonorRequired option to enforce that a field receives a value from at least one provider:

typeConfigstruct {
APIKeystring`env:"API_KEY" required:"true"`Debugbool`env:"DEBUG"`
}
configurator:=cfg.NewConfiguratorWithOptions(
cfg.WithProviders(env.NewDefaultProvider()),
cfg.HonorRequired,
)
// Returns a *cfg.RequiredError if API_KEY is not seterr:=configurator.Populate(ctx, &cfg)

The tag accepts any truthy value (true, yes, y, 1, t). Fields that receive a value from any provider — including the default tag — satisfy the requirement. HonorRequired is turned on by default in NewDefaultConfigurator.

Nested Structs

typeDatabasestruct {
Hoststring`env:"DB_HOST" flag:"db-host"`Portint`env:"DB_PORT" flag:"db-port"`
}
typeConfigstruct {
DatabaseDatabase// Automatically traversedAppNamestring`env:"APP_NAME" flag:"app-name"`
}

Custom Configurator

For more control, create a configurator without the default providers:

configurator:=cfg.NewConfigurator(
myProvider1,
myProvider2,
)

CLI Framework

The x/cli package provides a framework for building CLI applications with automatic help generation:

import"github.com/upfluence/cfg/x/cli"typeRunConfigstruct {
Hoststring`flag:"host,h"`Portint`flag:"port,p"`
}
cmd:= cli.StaticCommand{
Help: cli.HelpWriter(&RunConfig{}),
Synopsis: cli.SynopsisWriter(&RunConfig{}),
Execute: func(ctx context.Context, cctx cli.CommandContext) error {
varcfgRunConfig// Configuration is automatically populatedfmt.Printf("Running on %s:%d\n", cfg.Host, cfg.Port)
returnnil
},
}
app:=cli.NewApp(
cli.WithName("myapp"),
cli.WithCommand(cmd),
)
app.Run(context.Background())

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

About

No description or website provided.

Topics

Resources

Stars

4 stars

Watchers

11 watching

Forks

Releases

Packages

Used by

Contributors

Languages