⛩️ Spiritual protection and hydration for your Go configurations.
Fuda is a lightweight, struct-tag-first configuration library for Go — with built-in defaults, environment overrides, secret references, and validation.
The name comes from 御札 (ofuda) — traditional Japanese talismans inscribed with sacred characters to ward off evil and bring protection. Just as an ofuda guards a home, fuda guards your application:
- 📜 Inscribe your struct fields with tags like
default,env, andref - 🔮 Summon configuration from files, environment, or remote URLs
- 🛡️ Protect integrity with validation rules
- 🔐 Guard secrets by resolving them securely at runtime (Docker secrets, vaults)
No more scattered os.Getenv() calls. No more manual YAML wrangling. Just declare your config struct, add the sacred tags, and let fuda perform the ritual.
go get github.com/arloliu/fudapackage main
import (
"fmt"
"log"
"time"
"github.com/arloliu/fuda"
)
type Config struct {
Host string `yaml:"host" default:"localhost" env:"APP_HOST"`
Port int `yaml:"port" default:"8080" env:"APP_PORT"`
Timeout time.Duration `yaml:"timeout" default:"30s"`
Debug bool `yaml:"debug" default:"false"`
}
func main() {
var cfg Config
// Load from file
if err := fuda.LoadFile("config.yaml", &cfg); err != nil {
log.Fatal(err)
}
fmt.Printf("Server: %s:%d\n", cfg.Host, cfg.Port)
}- YAML/JSON parsing with struct tag support
- Default values via
defaulttag - Environment overrides via
envtag with optional prefix - Dotenv file loading via
WithDotEnv()with overlay and override support - External references via
refandrefFromtags (file://, http://, https://, vault://) - DSN composition via
dsntag for building connection strings from fields - HashiCorp Vault integration via
fuda/vaultpackage (Token, Kubernetes, AppRole auth) - Hot-reload configuration via
fuda/watcherpackage with fsnotify - Template processing via Go's
text/templatefor dynamic configuration - Testable filesystem via afero abstraction for easy testing with in-memory filesystems
- Custom type conversion via
Scannerinterface - Dynamic defaults via
Setterinterface - Duration type (
fuda.Duration) with human-friendly parsing (e.g.,"30s","5m","1h30m","7d") - ByteSize type (
fuda.ByteSize) for human-readable byte sizes (e.g.,"64KiB","10MiB","2GB") - Byte size parsing for integer fields (e.g.,
"64KiB","10MiB","2GB") - Preprocessing toggles for duration/size strings via builder options
- RawMessage type for deferred/polymorphic JSON/YAML unmarshaling
- Validation using go-playground/validator
- User Guide - Complete guide with examples for all features
- Tag Specification - Complete reference for all struct tags
- Setter & Scanner - Custom type conversion and dynamic defaults
- Custom Resolvers - Implementing custom reference resolvers
- Vault Resolver - HashiCorp Vault integration (separate module:
go get github.com/arloliu/fuda/vault) - Config Watcher - Hot-reload configuration watching
loader, err := fuda.New().
FromFile("config.yaml"). // or FromBytes(), FromReader()
WithEnvPrefix("APP_"). // optional: prefix for env vars
WithFilesystem(memFs). // optional: custom filesystem (afero)
WithDotEnv(".env"). // optional: load .env file
WithTimeout(10 * time.Second). // optional: timeout for ref resolution
WithValidator(customValidator). // optional: custom validator
WithRefResolver(customResolver). // optional: custom ref resolver
WithTemplate(templateData). // optional: template processing
WithDurationPreprocess(true). // optional: enable/disable duration preprocessing
WithSizePreprocess(true). // optional: enable/disable size preprocessing
Build()
if err != nil {
log.Fatal(err)
}
var cfg Config
if err := loader.Load(&cfg); err != nil {
log.Fatal(err)
}Process configuration as a Go template before parsing:
type TemplateData struct {
Env string
Host string
}
data := TemplateData{Env: "prod", Host: "api.example.com"}
loader, _ := fuda.New().
FromFile("config.yaml").
WithTemplate(data).
Build()With config.yaml:
host: "{{ .Host }}"
Custom delimiters can be set if your config contains literal `{{` sequences:
```go
WithTemplate(data, fuda.WithDelimiters("<{", "}>"))Load environment variables from .env files before processing:
// Single file
loader, _ := fuda.New().
FromFile("config.yaml").
WithDotEnv(".env").
Build()
// Multiple files (overlay pattern)
loader, _ := fuda.New().
FromFile("config.yaml").
WithDotEnvFiles([]string{".env", ".env.local", ".env.production"}).
Build()
// Override mode (dotenv values override existing env vars)
loader, _ := fuda.New().
FromFile("config.yaml").
WithDotEnv(".env", fuda.DotEnvOverride()).
Build()Missing files are silently ignored, making this safe for optional overlays like .env.local.
Build connection strings from config fields and secrets using the dsn tag:
type Config struct {
DBHost string `yaml:"host" default:"localhost"`
DBUser string `env:"DB_USER"`
DBPassword string `ref:"vault:///secret/data/db#password"`
// Compose DSN from fields above, or inline secrets/env vars
DSN string `dsn:"postgres://${.DBUser}:${.DBPassword}@${.DBHost}:5432/app"`
}Inline secret and environment variable resolution:
// Inline vault secret
DSN string `dsn:"postgres://${ref:vault:///db#user}:${ref:vault:///db#pass}@host:5432/db"`
// Inline environment variables
DSN string `dsn:"redis://${env:REDIS_HOST}:${env:REDIS_PORT}/0"`// Load from file
fuda.LoadFile("config.yaml", &cfg)
// Load from bytes
fuda.LoadBytes(yamlData, &cfg)
// Load from reader
fuda.LoadReader(reader, &cfg)| Topic | Details |
|---|---|
| Timeout | Default is 0 (no timeout). Set explicitly with WithTimeout() for network refs. |
| Blocking I/O | Load() blocks during file/network operations. Run in a goroutine if needed. |
| File URIs | Supports file:///absolute/path and file://relative/path formats. |
| Component | Thread-Safe? |
|---|---|
Loader |
✅ Yes — safe to call Load() from multiple goroutines after Build() |
RefResolver |
Loader is shared |
A Loader instance does not mutate state after construction and can be safely reused.
Implement Setter for dynamic defaults that can't be expressed as static tag values:
type Config struct {
RequestID string
CreatedAt time.Time
}
func (c *Config) SetDefaults() {
if c.RequestID == "" {
c.RequestID = uuid.New().String()
}
if c.CreatedAt.IsZero() {
c.CreatedAt = time.Now()
}
}Implement Scanner for custom string-to-value conversion in default tags:
type LogLevel int
const (Debug LogLevel = iota; Info; Warn; Error)
func (l *LogLevel) Scan(src any) error {
s, _ := src.(string)
switch strings.ToLower(s) {
case "debug": *l = Debug
case "info": *l = Info
case "warn": *l = Warn
case "error": *l = Error
default: return fmt.Errorf("unknown level: %s", s)
}
return nil
}
// Usage
type Config struct {
Level LogLevel `default:"info"`
}The library provides typed errors for inspection:
| Error Type | When Returned |
|---|---|
*FieldError |
Invalid tag value, type conversion failure, or ref resolution failure |
*LoadError |
Multiple field errors during a single load operation |
*ValidationError |
Validation rules from validate tag failed |
All errors support errors.Is() and errors.Unwrap() for error chain inspection.
var fieldErr *fuda.FieldError
if errors.As(err, &fieldErr) {
fmt.Printf("Field: %s, Tag: %s\n", fieldErr.Path, fieldErr.Tag)
}
var validationErr *fuda.ValidationError
if errors.As(err, &validationErr) {
// Handle validation failures
}Working examples are available in the examples/ directory:
| Example | Description |
|---|---|
| basic | Loading config with defaults, env overrides, and validation |
| dotenv | Loading environment variables from .env files |
| dsn | Composing connection strings using dsn tag |
| refs | External references with ref and refFrom tags |
| scanner | Custom type conversion via Scanner interface |
| setter | Dynamic defaults via Setter interface |
| template | Go template processing for dynamic config |
| validation | Struct validation with validate tag |
| watcher | Hot-reload configuration with fsnotify |
