Skip to content

romshark/yamagiconf

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

yamagiconf

GoDoc GoReportCard Coverage Status

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.
  • Features:

    • 🪄 If any type within your configuration struct implements the Validate interface, then its validation method will be called. 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 will be overwritten if env var SECRET is set.
	Secret string `yaml:"secret" env:"SECRET"`

	// See https://github.com/go-playground/validator
	// for all available validation tags
	Required string `yaml:"required" validate:"required"`
}

type Struct struct {
	Foo string          `yaml:"foo"`
	Bar ValidatedString `yaml:"bar"`
}

// Validate will automatically be called by yamagiconf
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

// Validate will automatically be called by yamagiconf
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)
}