Documentation
¶
Overview ¶
Package conf provides a flexible configuration binding system for Go applications.
It enables declarative configuration binding from various file formats and providers into Go structs with automatic type conversion, placeholder resolution, and expression-based validation.
Core Concepts ¶
conf works by:
- Loading configuration data from a source via providers
- Flattening nested data into a key-value store
- Binding properties to struct fields using tagged annotations
- Resolving variable placeholders recursively
- Validating values using expressions
Tag Syntax ¶
Struct fields use the `value` tag to specify the configuration key:
value:"${key:=default}"
Where:
- `${key}` - references the configuration key
- `:=default` - optional default value if the key is not found
Features:
- Nested keys: `${db.host}`, `${service.endpoint}`
- Default values: `${DB_HOST:=localhost}`
- Chained defaults: `${A:=${B:=default}}` - if A is missing, try B, then fall back to default
- Nested references: `${prefix${suffix}}` - nested variable expansion
- Root binding: `${ROOT}` - binds from the root level (used at the top-level struct)
Example:
type ServerConfig struct {
Host string `value:"${host:=localhost}"`
Port int `value:"${port:=8080}"`
}
Supported Types ¶
The following types are supported out of the box:
- **Primitives**: string, all integer/float types, bool (automatic string conversion)
- **Time types**: time.Time, time.Duration (built-in converters)
- **Structs**: recursive binding of nested struct fields
- **Slices**: two input formats: - Indexed properties: `endpoints[0]=a`, `endpoints[1]=b` - Comma-separated: `endpoints=a,b,c`
- **Maps**: key-value binding from dot-notation subkeys: `users.alice=Alice`, `users.bob=Bob`
- **Custom types**: register custom converters with RegisterConverter
Value Resolution ¶
All property values undergo variable resolution before binding. Any `${key}` occurrences in the string are replaced with their resolved values from the configuration store. Resolution is recursive and handles nested placeholders.
Example:
host=localhost
port=8080
url=http://${host}:${port}/api
After resolution, `url` becomes `http://localhost:8080/api`
Validation ¶
Add validation to any field using the `expr` tag. The expression has access to:
- `$` - the current field's value
- All registered custom validation functions
Examples:
type Config struct {
Port int `value:"${port}" expr:"$ > 0 && $ < 65536"`
Email string `value:"${email}" expr:"contains($, '@')"`
MinLen string `value:"${data}" expr:"len($) > 3"`
}
Register custom validation functions:
// Register a validator that checks a time is in the future
conf.RegisterValidateFunc("future", func(t time.Time) bool {
return t.After(time.Now())
})
// Use it in validation:
type Event struct {
StartTime time.Time `value:"${start-time}" expr:"future($)"`
}
For more expression syntax see: https://github.com/expr-lang/expr
Loading Configuration ¶
conf.Load accepts a source URI in the format:
[optional:]<provider>:<location>
Examples:
// Load from YAML file (auto-detects format by extension)
props, err := conf.Load("config.yaml")
// Explicit file provider
props, err = conf.Load("file:config.yaml")
// Optional file - does not error if file not found
props, err = conf.Load("optional:file:config.yaml")
Register custom providers (e.g., etcd, Consul, environment variables) with RegisterProvider to support additional configuration sources.
Supported File Formats ¶
Built-in readers are registered for these extensions:
- JSON (.json)
- Java Properties (.properties)
- YAML (.yaml, .yml)
- TOML (.toml, .tml)
Register custom readers with RegisterReader for additional formats.
Quick Start ¶
package main
import (
"fmt"
"log"
"github.com/go-spring/spring-core/conf"
)
type Config struct {
Host string `value:"${host:=localhost}"`
Port int `value:"${port:=8080}"`
}
func main() {
// Load configuration from file
props, err := conf.Load("config.yaml")
if err != nil {
log.Fatal(err)
}
// Bind to struct (uses ${ROOT} by default - binds all keys from root)
var cfg Config
if err := conf.Bind(props, &cfg); err != nil {
log.Fatal(err)
}
// Use cfg.Host and cfg.Port
fmt.Printf("Server starting on %s:%d\n", cfg.Host, cfg.Port)
}
Bind can also bind only under a specific key prefix:
var cfg AppConfig
conf.Bind(props, &cfg, "${app}") // all fields look for keys under app.*
Extension Points ¶
All extensions must be registered during init():
- RegisterProvider - add new configuration source providers
- RegisterReader - add support for additional file formats
- RegisterConverter - add type conversion for custom types
- RegisterValidateFunc - add custom validation functions
Index ¶
- func Bind(p flatten.Storage, i any, tag ...string) error
- func BindValue(p flatten.Storage, v reflect.Value, t reflect.Type, param BindParam, ...) (RetErr error)
- func Load(source string) (*flatten.Properties, error)
- func RegisterConverter[T any](fn Converter[T])
- func RegisterProvider(name string, p provider.Provider)
- func RegisterReader(r reader.Reader, ext ...string)
- func RegisterValidateFunc[T any](name string, fn ValidateFunc[T])
- func Resolve(p flatten.Storage, s string) (string, error)
- type BindParam
- type Converter
- type Filter
- type ParsedTag
- type ValidateFunc
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Bind ¶ added in v1.3.0
Bind maps property values from storage into the target object. The target must be a pointer to a struct or a reflect.Value. An optional tag specifies the root property key using ${key} syntax. Supports default values: ${key:=default}. If no tag is provided, uses ${ROOT} (binds from the root). Supports binding to structs, maps, slices, and primitive types.
func BindValue ¶
func BindValue(p flatten.Storage, v reflect.Value, t reflect.Type, param BindParam, filter Filter) (RetErr error)
BindValue attempts to bind a property value from the property source `p` into the given reflect.Value `v`, based on metadata in `param`.
Supported binding targets:
- Primitive types (string, int, float, bool, etc.).
- Structs (recursively bound field by field).
- Maps (bound by iterating subkeys).
- Slices (bound by either indexed keys or split strings).
Errors:
- Validation failure if "expr" tag evaluation fails.
- Type mismatch (e.g., array instead of slice).
- Property not exist without default value.
- Type conversion errors during parsing.
- Custom converter function errors.
func Load ¶
func Load(source string) (*flatten.Properties, error)
Load creates a Properties instance from a configuration source. The source format is [optional:]<provider>:<path> or just <path>. Returns an error if the file type is not supported or parsing fails.
func RegisterConverter ¶
RegisterConverter registers a Converter for a type T, such as time.Time, time.Duration, or other user-defined types. Must be called in init functions only.
func RegisterProvider ¶ added in v1.3.0
RegisterProvider registers a Provider for a specific configuration source. For example, a file provider reads local files, an etcd provider fetches remote config. Must be called in init functions only.
func RegisterReader ¶
RegisterReader registers its Reader for some kind of file extension. For example, a YAML reader parses .yaml files into nested maps. Must be called in init functions only.
func RegisterValidateFunc ¶ added in v1.2.0
func RegisterValidateFunc[T any](name string, fn ValidateFunc[T])
RegisterValidateFunc registers a validation function with a specific name. Must be called in init functions only.
func Resolve ¶ added in v1.3.0
Resolve expands property references of the form ${key} inside a string, recursively resolving nested expressions.
Supported features: - Nested references: e.g. "${outer${inner}}" - Default values: "${key:=fallback}" - Arbitrary string concatenation around references.
Example:
Storage:
"host" = "localhost"
"port" = "8080"
Input:
"http://${host}:${port}"
Output:
"http://localhost:8080"
Errors: - Returns invalid syntax if braces are unbalanced.
Types ¶
type BindParam ¶
type BindParam struct {
Key string // full property key
Path string // full property path
Tag ParsedTag // parsed tag
Validate reflect.StructTag // original struct field tag for validation
}
BindParam holds metadata needed to bind a single configuration value to a Go struct field, slice element, or map entry.
func (*BindParam) BindTag ¶
BindTag parses the tag string, stores the ParsedTag in BindParam, and resolves nested key expansion.
Special cases: - "${:=default}" -> Key is empty, only default is set. - "${ROOT}" -> explicitly resets Key to an empty string.
If a BindParam already has a Key, new keys are appended hierarchically, e.g. parent Key="db", tag="${host}" -> final Key="db.host".
Errors: - Returns invalid syntax if the tag string is malformed or empty without default.
type Filter ¶ added in v1.1.2
Filter defines an interface for filtering configuration fields during binding. Dynamic configuration fields have the same syntax, they need to be filtered out.
type ParsedTag ¶
type ParsedTag struct {
Key string // short property key
Def string // default value string
HasDef bool // indicates whether a default value exists
}
ParsedTag represents a parsed configuration tag that encodes metadata for binding configuration values from property sources.
A tag string generally follows the pattern:
${key:=default}
- "key": the property key used to look up a value. - "default": optional fallback value if the key does not exist.
Examples:
"${db.host:=localhost}" -> key=db.host, default=localhost
"${ports:=8080,9090}" -> key=ports, default=8080,9090
"${:=foo}" -> empty key, only default value "foo"
The parsing logic is strict; malformed tags will result in invalid syntax.
type ValidateFunc ¶ added in v1.2.0
ValidateFunc defines a type for validation functions.