Skip to content

Latest commit

 

History

History
120 lines (94 loc) · 4.7 KB

File metadata and controls

120 lines (94 loc) · 4.7 KB

config

Tier: Foundational · Status: Full · Java original: Spring Boot @ConfigurationProperties · .NET project: IConfiguration + IOptions<T>

Overview

config brings Spring Boot–style typed, layered configuration binding to Go. Application authors declare a Go struct with yaml-tagged fields and call config.Load[T](ctx, sources...); the loader merges the sources in precedence order, resolves the active profile, and binds the flat dot-keyed map onto the struct.

type AppCfg struct {
    Web struct {
        Port int    `yaml:"port"`
        Host string `yaml:"host"`
    } `yaml:"web"`
    Cache struct { Adapter string `yaml:"adapter"` } `yaml:"cache"`
}

cfg, err := config.Load[AppCfg](ctx,
    config.FromYAML("application.yaml"),
    config.FromEnv("FIREFLY"),
)

Source precedence

config.NewLayered(s1, s2, ...) merges from left to right — last write wins. The canonical chain is:

  1. Defaults (Static{Entries: ...})
  2. Base YAML (FromOptionalYAML("application.yaml"))
  3. Profile YAML (FromOptionalYAML("application-prod.yaml"))
  4. Environment (FromEnv("FIREFLY")FIREFLY_WEB_PORTweb.port)
  5. CLI flags (NewFlagSource()flags.Set("web.port", "9090"))

So an environment override always beats a YAML file, and a CLI override always beats both.

Profile selection

FIREFLY_PROFILE selects the profile-specific YAML file. The canonical helper:

cfg, _ := config.LoadFromProfile[AppCfg](ctx, "/etc/firefly", "application", "dev")

reads application.yaml, then application-{FIREFLY_PROFILE,fallback}.yaml, then FIREFLY_* env vars.

Public surface

Symbol Purpose
Source interface Anything producing a flat map[string]string
Static{NameValue, Entries} Hard-coded source
FromYAML(path) *YAMLSource Required YAML file
FromOptionalYAML(path) *YAMLSource Tolerates absent file
FromEnv(prefix) *EnvSource Reads <PREFIX>_FOO_BARfoo.bar
NewFlagSource() + .Set(key, value) Programmatic / CLI overrides
NewLayered(sources...).Map(ctx) Compute the merged map
Load[T](ctx, sources...) (T, error) Merge + bind onto a fresh T
LoadFromProfile[T](ctx, dir, app, fallback) (T, error) Profile-aware convenience
Bind(flat, &target) Bind a pre-merged map onto a pointer
ActiveProfile(fallback) string FIREFLY_PROFILE lookup
ProfileSources(dir, app, profile) []Source Build the YAML chain for a profile

Supported leaf kinds (struct binder)

string, bool, all int* / uint*, float32 / float64, and []string (comma-separated). Use time.Duration via an int64 field plus your own conversion (time.Duration(cfg.TimeoutMs) * time.Millisecond) — keeps the binder dependency-free.

YAML subset

The embedded YAML scanner accepts: mappings, scalars, nested mappings, sequences of scalars (rendered as comma-joined). It rejects flow sequences, anchors, aliases, multi-doc, and tags — bring your own parser (gopkg.in/yaml.v3) if you need them. The .NET port uses Microsoft's YAML reader; the parser surface here matches what every production application.yaml actually uses.

Quick start

import (
    "context"
    "github.com/fireflyframework/fireflyframework-go/config"
)

type AppCfg struct {
    Web struct {
        Port int `yaml:"port"`
    } `yaml:"web"`
    Cache struct {
        Adapter string `yaml:"adapter"`
        TTL     int64  `yaml:"ttl"`
    } `yaml:"cache"`
    Tags []string `yaml:"tags"`
}

cfg, err := config.LoadFromProfile[AppCfg](ctx, "/etc/orders", "application", "dev")
if err != nil { panic(err) }
fmt.Println(cfg.Web.Port)

Testing

cd config
go test ./...

Suite covers static + YAML + env merge order, profile selection, optional-YAML absence tolerance, and leaf-kind binding for strings, bools, ints, floats, and []string.