configkit

package
v0.0.0-...-5d01d84 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2025 License: MIT Imports: 14 Imported by: 0

README

Config Module

Simple, tag-based configuration loader for Fx applications. Built on top of uber/config and go-playground/validator.

Features

  • Layered YAML Loading: Automatically loads and merges multiple YAML files.
  • Environment Variable Expansion: Supports ${VAR:default} syntax in config files.
  • Automatic Validation: Recursively validates structs using validate tags.
  • Fx Integration: Provides strongly-typed configuration to the Fx container.
  • Sub-Tree Loading: Easily provide nested parts of your configuration to different modules.

Configuration Loading

The module loads configuration with the following precedence (from lowest to highest, with later values overriding earlier ones):

  1. Custom Sources: Any sources provided via configkit.WithSources() or configkit.WithEmbeddedBytes().
  2. Base Config: config/config.yml
  3. Local Overrides: config/config.local.yml (ideal for development, should be in .gitignore).
  4. Service-Specific Overrides: config/<service-name>.yml (uses the name from the runtimeinfo package).
  5. Environment Variables: Any ${...} placeholders are expanded.
CLI-oriented loader

For tooling and one-off inspection, configkit.NewYAML provides a minimal loader that reuses the same internals but applies a simpler precedence geared towards CLIs:

  • Default file: config/config.yml (if present)
  • Env override: CONFIG=/path/to/file.yml (must exist)
  • CLI flag: pass an explicit file via configkit.WithSources(configkit.File(path)) (highest precedence)

The CLI loader always applies environment expansion and never logs secrets. Use configkit.Redact(key, value) to render a redacted view for display.

On Fx boot via configfx.Module, a single line is emitted:

msg="config.loaded" env="$ENV" service="$runtimeinfo.Name" version="$runtimeinfo.Version"

Only these fields are logged; configuration values are never included.


Usage

1. Define Your Configuration Structs

Create Go structs that mirror your YAML structure. Use yaml tags for mapping and validate tags for validation rules.

// myapp/config.go

// HTTPConfig defines the settings for the web server.
type HTTPConfig struct {
  Addr string `yaml:"addr" validate:"required,hostname_port"`
}

// AppConfig is the root configuration for the application.
type AppConfig struct {
  HTTP HTTPConfig `yaml:"http"`
  // ... other configs
}

2. Provide Configuration in Fx

In your main.go, add configkit.Module() to enable the loader. Then, use a configkit.Provide function to load, validate, and provide your typed config struct to the application.

Provide the entire config
// main.go
app := fx.New(
	// 1. Enable the configuration loader.
	configkit.Module(),

	// 2. Load the YAML into AppConfig, validate it, and provide *AppConfig.
	fx.Provide(configkit.Provide[AppConfig]()),

	// 3. Now you can inject *AppConfig anywhere.
	fx.Invoke(func(cfg *AppConfig) {
		fmt.Println("HTTP server will run on:", cfg.HTTP.Addr)
	}),
)
Provide a specific sub-tree

This is useful for modularity. A module can ask for just the part of the config it needs.

// http/module.go
func Module() fx.Option {
	return fx.Options(
		// This provider loads only the "http" key from the YAML
		// into an HTTPConfig struct and provides *HTTPConfig.
		fx.Provide(configkit.ProvideFromKey[HTTPConfig]("http")),
		// ... other providers that can now inject *HTTPConfig
	)
}

Advanced Usage

Adding Custom Configuration Sources

You can add extra configuration sources, like an embedded default config, at the lowest precedence.

import "embed"

//go:embed defaults.yml
var default_config []byte

func main() {
  app := fx.New(
    configkit.Module(
      // This embedded config will be loaded first.
      configkit.WithEmbeddedBytes(default_config),
      // You can also add other custom files.
      configkit.WithSources(uber.File("another_config.yml")),
    ),
    // ... other providers
  )
}
Config Discovery and Validation

This package can automatically discover which config subtrees your app uses and validate them.

  • Discovery is triggered whenever you call configkit.ProvideFromKey[T](key) or configkit.Provide[T]().
  • At runtime, you can list and validate the discovered requirements:
// List discovered requirements
reqs := configkit.Requirements()

// Validate them against the current provider
results := configkit.Check(provider)
for _, r := range results {
  if !r.OK {
    log.Error("config invalid", zap.String("key", r.Key), zap.String("type", r.Type), zap.Error(r.Err))
  }
}

// Optionally, get a field spec for documentation (uses yaml/json + validate tags)
fields, _ := configkit.Spec(reqs[0])

CLI helper (optional):

go run github.com/froppa/stackkit/cmd/stackctl config check --all

Notes:

  • The CLI registers modules you pass via --with.
  • Field specs use yaml tags primarily and fall back to json. Required is inferred from validate:"required".

Documentation

Overview

Package config provides a simple, tag-based configuration loader for an Fx application, built on uber/config and go-playground/validator.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func KnownType

func KnownType(key string) (reflect.Type, bool)

KnownType returns the reflect.Type for a known module key, if registered.

func Module

func Module(opts ...ModuleOption) fx.Option

Module wires the core uber/config YAML provider into an Fx application.

This is the foundational component that enables configuration loading. It must be included in your Fx app for `Provide` and `ProvideFromKey` to work.

Configuration is loaded with the following precedence (from lowest to highest, with later values overriding earlier ones): 1. Custom Sources: Provided via `WithSources()` or `WithEmbeddedBytes()`. 2. Base Config: `config/config.yml` 3. Local Overrides: `config/config.local.yml` 4. Service-Specific Overrides: `config/<service-name>.yml` (from the runtimeinfo package). 5. Environment Variables: Any `${...}` placeholders are expanded.

func Provide

func Provide[T any]() func(*uber.YAML) (*T, error)

Provide returns an Fx provider that loads the entire configuration into type T, validates it, and provides a pointer to it (`*T`) to the Fx container.

It is a convenient shorthand for `ProvideFromKey[T](uber.Root)`.

func ProvideFromKey

func ProvideFromKey[T any](key string) func(provider *uber.YAML) (*T, error)

ProvideFromKey returns an Fx provider that loads a specific configuration subtree (identified by `key`) into type T, validates it, and provides a pointer to it (`*T`) to the Fx container.

If validation fails based on the `validate` tags in the struct, the Fx application will fail to start with a descriptive error.

func Redact

func Redact(_ string, v any) any

Redact masks secret-looking values within v for safe logging/display. The key parameter can be used for future, key-specific redaction nuances.

func RegisterKnown

func RegisterKnown(key string, sample any)

RegisterKnown registers a known module key and its config type, so tools can activate requirements without referencing the type directly. Typical usage from a module's init():

config.RegisterKnown("http", (*http.Config)(nil))

func RegisterRequirement

func RegisterRequirement(key string, sample any)

RegisterRequirement registers a configuration requirement for a given key and sample type. The sample may be a value or a typed nil pointer to the desired type. This is useful for programmatic activation without generics.

func RegisterRequirementType

func RegisterRequirementType(key string, tt reflect.Type)

RegisterRequirementType registers a requirement using a reflect.Type.

func ResetDiscoveryForTests

func ResetDiscoveryForTests()

ResetDiscoveryForTests clears the internal registry. Exported for tests; do not use in application code.

func Skeleton

func Skeleton(req Requirement) (string, error)

Skeleton renders an example YAML snippet for the requirement key.

Types

type CheckResult

type CheckResult struct {
	Key     string
	Type    string
	OK      bool
	Err     error
	Issues  []string // formatted validator issues: yaml.path: rule
	Unknown []string // unknown keys detected in YAML subtree
}

CheckResult represents the outcome of validating a single requirement against a configuration provider.

func Check

func Check(p *uber.YAML) []CheckResult

Check validates all discovered requirements against the provided YAML provider. It attempts to populate and validate each config subtree using the same rules as ProvideFromKey (including `validate` struct tags).

type FieldSpec

type FieldSpec struct {
	Path     string // YAML dot path relative to Requirement.Key
	Type     string // Go kind or type name
	Required bool   // true if validate tag contains "required"
}

FieldSpec describes a single field in a config struct for documentation purposes.

func Spec

func Spec(req Requirement) ([]FieldSpec, error)

Spec returns a best-effort field specification for the given requirement. It infers YAML field names from `yaml` tags when present, falling back to lowercased field names. Embedded/inline fields are flattened.

type ModuleOption

type ModuleOption func(*moduleOpts)

ModuleOption customizes the behavior of the config Module by adding extra sources.

func WithEmbeddedBytes

func WithEmbeddedBytes(b []byte) ModuleOption

WithEmbeddedBytes adds an embedded YAML payload (e.g., from `//go:embed`) as a low-precedence source for default values.

func WithSources

func WithSources(srcs ...uber.YAMLOption) ModuleOption

WithSources injects additional uber/config sources at the lowest precedence. This is useful for providing default configurations from code.

type Requirement

type Requirement struct {
	// Key is the YAML subtree key, e.g. "http" or "telemetry". Root is "".
	Key string
	// Type is a human-readable Go type string, e.g. "http.Config".
	Type string
	// PkgPath is the import path for the type's package.
	PkgPath string
}

Requirement describes a config requirement declared via ProvideFromKey[T](key).

It identifies the YAML subtree key and the Go type expected to be populated from that subtree.

func Known

func Known() []Requirement

Known returns a snapshot of all known modules.

func Requirements

func Requirements() []Requirement

Requirements returns a snapshot of all discovered configuration requirements registered so far in this process.

type Source

type Source = uber.YAMLOption

Source is an alias for uber/config YAML options (file, reader, expand, etc.).

func DefaultSources

func DefaultSources() []Source

DefaultSources returns the default, low-precedence sources for CLI usage. Precedence (lowest -> highest) when combined by NewYAML:

  1. Default file: config/config.yml (if present)
  2. Env override: CONFIG=file.yml (if set, must exist)
  3. CLI flag: passed via opts (highest precedence)

Note: Services should continue using Module(); DefaultSources is intended for CLIs.

func File

func File(path string) Source

File returns a Source that loads YAML from the given path.

type YAMLProvider

type YAMLProvider = uber.YAML

YAMLProvider is the concrete provider type used throughout the repo. It aliases the underlying uber/config YAML type for convenience.

func NewYAML

func NewYAML(_ context.Context, opts ...ModuleOption) (*YAMLProvider, error)

NewYAML builds a YAML provider using the same underlying primitives as Module, but with a CLI-friendly precedence model:

default config file -> $CONFIG override -> explicit sources via opts (highest)

Environment expansion is always applied. If $CONFIG is set but the file is missing, an error is returned.

Jump to

Keyboard shortcuts

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