yamagiconf
The heavily opinionated YAML Magic Configuration framework for Go
keeps your configs simple and consistent
by being more restrictive than your regular YAML parser 🚷 allowing only a subset of YAML and enforcing some restrictions to the target Go type.
If you hate YAML, and you're afraid of
YAML documents from hell, and you can't stand complex configurations then yamagiconf is for you!
🪄 It's magic because it uses reflect to find recursively all
values of types that implement interface { Validate() error }
and calls them reporting
an error annotated with line and column in the YAML file if necessary.
(anti-)Features
-
Go restrictions:
- 🚫 Forbids recursive Go types.
- 🚫 Forbids the use of
any
, int
& uint
(unspecified width), and other types.
Only maps, slices, arrays and deterministic primitives are allowed. - ❗️ Requires
yaml
struct tags on all exported fields. - ❗️ Requires
env
struct tags to be POSIX-style. - 🚫 Forbids the use of
env
struct tag on non-primitive fields
(allows only floats, ints, strings, bool). - 🚫 Forbids the use of
env
on primitive fields implementing
the yaml.Unmarshaler
interface. - 🚫 Forbids the use of
yaml
and env
struct tags within implementations of
encoding.TextUnmarshaler
and/or yaml.Unmarshaler
. - 🚫 Forbids the use of YAML tag option
"inline"
for non-embedded structs and
requires embedded structs to use option "inline"
.
-
YAML restrictions:
- 🚫 Forbids the use of
no
, yes
, on
and off
for bool
,
allows only true
and false
. - 🚫 Forbids the use of
~
and Null
, allows only null
for nilables. - 🚫 Forbids assigning
null
to non-nilables (which normally would assign zero value). - 🚫 Forbids fields in the YAML file that aren't specified by the Go type.
- 🚫 Forbids the use of YAML tags.
- 🚫 Forbids redeclaration of anchors.
- 🚫 Forbids unused anchors.
- 🚫 Forbids anchors with implicit
null
value (no value) like foo: &bar
. - ❗️ Requires fields specified in the configuration type to be present in the YAML file.
- 🚫 Forbids assigning non-string values to Go types that implement
the
encoding.TextUnmarshaler
interface.
-
Features:
- 🪄 If any type within your configuration struct implements the
Validate
interface,
then its validation method will be called using reflection
(doesn't apply to unexported fields which are invisible to reflect
).
If it returns an error - the error will be reported.
Keeps your validation logic close to your configuration type definitions. - Reports errors by
line:column
when possible. - Supports github.com/go-playground/validator
struct validation tags.
- Implements
env
struct tags to overwrite fields from env vars if provided. - Supports
encoding.TextUnmarshaler
and yaml.Unmarshaler
(except for the root struct type). - Supports
time.Duration
.
Example
https://go.dev/play/p/PjV0aG7uIUH
list:
- foo: valid
bar: valid
- foo: valid
bar: valid
map:
valid: valid
secret: 'this will be overwritten from env var SECRET'
required: 'this must not be empty'
package main
import (
"fmt"
"github.com/romshark/yamagiconf"
)
type Config struct {
List []Struct `yaml:"list"`
Map map[ValidatedString]ValidatedString `yaml:"map"`
Secret string `yaml:"secret" env:"SECRET"`
Required string `yaml:"required" validate:"required"`
}
type Struct struct {
Foo string `yaml:"foo"`
Bar ValidatedString `yaml:"bar"`
}
func (v *Struct) Validate() error {
if v.Foo == "invalid" {
return fmt.Errorf("invalid foo")
}
if v.Bar == "invalid" {
return fmt.Errorf("invalid bar")
}
return nil
}
type ValidatedString string
func (v ValidatedString) Validate() error {
if v == "invalid" {
return fmt.Errorf("string is invalid")
}
return nil
}
func main() {
var c Config
if err := yamagiconf.LoadFile("./config.yaml", &c); err != nil {
fmt.Println("Whoops, something is wrong with your config!", err)
}
fmt.Printf("%#v\n", c)
}