config

package
v1.10.5 Latest Latest
Warning

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

Go to latest
Published: Apr 2, 2026 License: MIT Imports: 18 Imported by: 0

README

Config

Robust configuration management wrapping spf13/viper with type safety and interface-based testability.

Features:

  • Mockable Containable interface
  • Automatic environment variable mapping
  • Built-in observer pattern for file watching
  • Support for YAML, embedded assets, and more

For detailed documentation, usage examples, and testing strategies, see the Config Component Documentation.

Documentation

Overview

Package config provides configuration loading, merging, and access via the Containable interface backed by Viper.

Configurations can be loaded from multiple sources — local files, embedded assets, environment variables, and command-line flags — and merged with deterministic precedence. Factory functions include NewFilesContainer, LoadFilesContainer, NewReaderContainer, and NewContainerFromViper.

Type-safe accessors (GetString, GetInt, GetBool, GetDuration, GetTime, etc.) are exposed through Containable. For advanced use cases, [Containable.GetViper] provides direct access to the underlying Viper instance as an intentional power-user escape hatch.

Hot-reload is supported via the Observable interface, which allows consumers to register callbacks that fire when configuration files change on disk.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrNoFilesFound = errors.Newf("no configuration files found please run init, or provide a config file using the --config flag")
)

Functions

func LoadEnv

func LoadEnv(fs afero.Fs, logger logger.Logger)

LoadEnv loads environment variables from a .env file if it exists.

Types

type Containable

type Containable interface {
	Get(key string) any
	GetBool(key string) bool
	GetInt(key string) int
	GetFloat(key string) float64
	GetString(key string) string
	GetTime(key string) time.Time
	GetDuration(key string) time.Duration
	// GetViper returns the underlying Viper instance for advanced operations
	// not exposed by the Containable interface. This is an intentional escape
	// hatch for power users who need Viper's full API (e.g., MergeConfig,
	// BindPFlag, or direct access to config file watching).
	//
	// Prefer the typed accessor methods (GetString, GetInt, etc.) for standard
	// configuration reads. Use GetViper only when the Containable interface
	// does not cover your use case.
	GetViper() *viper.Viper
	Has(key string) bool
	IsSet(key string) bool
	Set(key string, value any)
	WriteConfigAs(dest string) error
	Sub(key string) Containable
	AddObserver(o Observable)
	AddObserverFunc(f func(Containable, chan error))
	ToJSON() string
	Dump(w io.Writer)
	// Validate checks the container's current values against the provided schema.
	// Returns a ValidationResult; callers should check result.Valid().
	Validate(schema *Schema) *ValidationResult
}

Containable is the primary configuration interface. It provides typed accessors, observation for hot-reload, and schema validation.

func Load

func Load(paths []string, fs afero.Fs, logger logger.Logger, allowEmptyConfig bool) (Containable, error)

Load reads configuration from the first available file in paths. Returns ErrNoFilesFound if no files exist and allowEmptyConfig is false.

func LoadEmbed

func LoadEmbed(paths []string, assets fs.FS, logger logger.Logger) (Containable, error)

LoadEmbed reads configuration from embedded filesystem assets and merges them.

func LoadFilesContainer

func LoadFilesContainer(l logger.Logger, fs afero.Fs, configFiles ...string) (Containable, error)

LoadFilesContainer loads configuration from files and returns a Containable. It returns an error if the first file specified does not exist.

func LoadFilesContainerWithSchema added in v1.8.0

func LoadFilesContainerWithSchema(l logger.Logger, fs afero.Fs, schema *Schema, configFiles ...string) (Containable, error)

LoadFilesContainerWithSchema loads config files and validates against the schema. Returns an error wrapping all validation errors if the config is invalid.

type Container

type Container struct {
	ID string
	// contains filtered or unexported fields
}

Container container for configuration.

func NewContainerFromViper

func NewContainerFromViper(l logger.Logger, v *viper.Viper) *Container

NewContainerFromViper creates a new Container from an existing Viper instance.

func NewFilesContainer

func NewFilesContainer(l logger.Logger, fs afero.Fs, configFiles ...string) *Container

NewFilesContainer Initialise configuration container to read files from the FS.

func NewReaderContainer

func NewReaderContainer(l logger.Logger, format string, configReaders ...io.Reader) *Container

NewReaderContainer Initialise configuration container to read config from ioReader.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/phpboyscout/go-tool-base/pkg/config"
	"github.com/phpboyscout/go-tool-base/pkg/logger"
)

func main() {
	l := logger.NewNoop()
	yaml := `
log:
  level: debug
server:
  port: 8080
`
	cfg := config.NewReaderContainer(l, "yaml", strings.NewReader(yaml))

	fmt.Println("Level:", cfg.GetString("log.level"))
	fmt.Println("Port:", cfg.GetInt("server.port"))
}
Output:
Level: debug
Port: 8080

func (*Container) AddObserver

func (c *Container) AddObserver(o Observable)

AddObserver attach observer to trigger on config update.

func (*Container) AddObserverFunc

func (c *Container) AddObserverFunc(f func(Containable, chan error))

AddObserverFunc attach function to trigger on config update.

func (*Container) Dump

func (c *Container) Dump(w io.Writer)

func (*Container) Get

func (c *Container) Get(key string) any

Get interface value from config.

func (*Container) GetBool

func (c *Container) GetBool(key string) bool

GetBool get Bool value from config.

func (*Container) GetDuration

func (c *Container) GetDuration(key string) time.Duration

GetDuration get duration value from config.

func (*Container) GetFloat

func (c *Container) GetFloat(key string) float64

GetFloat get Float value from config.

func (*Container) GetInt

func (c *Container) GetInt(key string) int

GetInt get Bool value from config.

func (*Container) GetObservers

func (c *Container) GetObservers() []Observable

GetObservers retrieve all currently attached Observers.

func (*Container) GetString

func (c *Container) GetString(key string) string

GetString get string value from config.

func (*Container) GetTime

func (c *Container) GetTime(key string) time.Time

GetTime get time value from config.

func (*Container) GetViper

func (c *Container) GetViper() *viper.Viper

GetViper retrieves the underlying Viper configuration.

func (*Container) Has

func (c *Container) Has(key string) bool

Has retrieves the underlying Viper configuration.

func (*Container) IsSet

func (c *Container) IsSet(key string) bool

IsSet checks if the key has been set.

func (*Container) Set

func (c *Container) Set(key string, value any)

Set sets the value for the given key.

func (*Container) SetSchema added in v1.8.0

func (c *Container) SetSchema(schema *Schema)

SetSchema attaches a validation schema to the container. When set, hot-reload will validate config changes before notifying observers.

func (*Container) Sub

func (c *Container) Sub(key string) Containable

Sub returns a subtree of the parent configuration.

func (*Container) ToJSON

func (c *Container) ToJSON() string

Dump return config as json string.

func (*Container) Validate added in v1.8.0

func (c *Container) Validate(schema *Schema) *ValidationResult

Validate checks the current configuration against the provided schema. Returns a ValidationResult; callers should check result.Valid().

Example
package main

import (
	"fmt"
	"strings"

	"github.com/phpboyscout/go-tool-base/pkg/config"
	"github.com/phpboyscout/go-tool-base/pkg/logger"
)

func main() {
	type AppConfig struct {
		Log struct {
			Level string `config:"log.level" enum:"debug,info,warn,error"`
		}
	}

	l := logger.NewNoop()
	cfg := config.NewReaderContainer(l, "yaml", strings.NewReader("log:\n  level: verbose\n"))

	schema, _ := config.NewSchema(config.WithStructSchema(AppConfig{}))

	result := cfg.Validate(schema)
	if !result.Valid() {
		fmt.Println(result.Error())
	}
}

func (*Container) WriteConfigAs

func (c *Container) WriteConfigAs(dest string) error

WriteConfigAs writes the current configuration to the given path.

type FieldSchema added in v1.8.0

type FieldSchema struct {
	// Type is the expected Go type: "string", "int", "float64", "bool", "duration".
	Type string
	// Required indicates the field must be present and non-zero.
	Required bool
	// Description is used in validation error messages.
	Description string
	// Default is the default value for documentation and error hints only.
	// The validation layer does not inject defaults — use embedded assets for that.
	Default any
	// Enum restricts the field to a set of allowed values.
	Enum []any
	// Children defines nested fields for map/object types.
	Children map[string]FieldSchema
}

FieldSchema describes a single configuration field.

type Observable

type Observable interface {
	Run(Containable, chan error)
}

Observable is the interface for config change observers. Implementations receive the updated config and an error channel when the config file changes.

type Observer

type Observer struct {
	// contains filtered or unexported fields
}

Observer is a simple Observable that wraps a handler function.

func (Observer) Run

func (o Observer) Run(c Containable, errs chan error)

Run invokes the observer's handler with the updated config.

type Schema added in v1.8.0

type Schema struct {
	// contains filtered or unexported fields
}

Schema defines the expected structure and constraints for configuration values.

func NewSchema added in v1.8.0

func NewSchema(opts ...SchemaOption) (*Schema, error)

NewSchema creates a Schema from the provided options.

Example
package main

import (
	"fmt"

	"github.com/phpboyscout/go-tool-base/pkg/config"
)

func main() {
	type AppConfig struct {
		Server struct {
			Port int    `config:"server.port" default:"8080"`
			Host string `config:"server.host"`
		}
		Log struct {
			Level string `config:"log.level" enum:"debug,info,warn,error" default:"info"`
		}
		Github struct {
			Token string `config:"github.token" validate:"required"`
		}
	}

	schema, err := config.NewSchema(config.WithStructSchema(AppConfig{}))
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	_ = schema // Use with container.Validate(schema)
}

func (*Schema) Fields added in v1.8.0

func (s *Schema) Fields() map[string]FieldSchema

Fields returns the schema field definitions.

type SchemaOption added in v1.8.0

type SchemaOption func(*schemaConfig)

SchemaOption configures schema construction.

func WithStrictMode added in v1.8.0

func WithStrictMode() SchemaOption

WithStrictMode treats unknown keys as errors instead of warnings.

func WithStructSchema added in v1.8.0

func WithStructSchema(v any) SchemaOption

WithStructSchema derives a schema from a tagged Go struct. Supported tags: `config:"key" validate:"required" enum:"a,b,c" default:"value"`.

type ValidationError added in v1.8.0

type ValidationError struct {
	// Key is the dot-separated config key.
	Key string
	// Message is a human-readable description of the failure.
	Message string
	// Hint is an actionable fix suggestion.
	Hint string
}

ValidationError contains details about a single validation failure.

func (ValidationError) String added in v1.8.0

func (e ValidationError) String() string

type ValidationResult added in v1.8.0

type ValidationResult struct {
	Errors   []ValidationError
	Warnings []ValidationError
}

ValidationResult holds the outcome of schema validation.

func (*ValidationResult) Error added in v1.8.0

func (r *ValidationResult) Error() string

Error returns a formatted multi-line error string, or empty string if valid.

func (*ValidationResult) Valid added in v1.8.0

func (r *ValidationResult) Valid() bool

Valid returns true if no errors were found. Warnings do not affect validity.

Jump to

Keyboard shortcuts

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