yamagiconf

package module
v0.10.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2024 License: MIT Imports: 14 Imported by: 0

README

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)
}

Documentation

Overview

Package yamagiconf provides an opinionated configuration file parser using a subset of YAML and Go struct type restrictions to allow for consistent configuration files, easy validation and good error reporting. Supports primitive `env` struct tags used to overwrite fields from env vars. Also supports github.com/go-playground/validator struct tags.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrConfigNil            = errors.New("cannot load into nil config")
	ErrValidation           = errors.New("validation")
	ErrValidateTagViolation = errors.New("violates validation rule")

	ErrYAMLEmptyFile       = errors.New("empty file")
	ErrYAMLMalformed       = errors.New("malformed YAML")
	ErrYAMLInlineNonAnon   = errors.New("inline yaml on non-embedded field")
	ErrYAMLInlineOpt       = errors.New("use `yaml:\",inline\"` for embedded fields")
	ErrYAMLTagOnUnexported = errors.New("yaml tag on unexported field")
	ErrYAMLTagRedefined    = errors.New("a yaml tag must be unique")
	ErrYAMLAnchorRedefined = errors.New("yaml anchors must be unique throughout " +
		"the whole document")
	ErrYAMLAnchorUnused   = errors.New("yaml anchors must be referenced at least once")
	ErrYAMLAnchorNoValue  = errors.New("don't use anchors with implicit null value")
	ErrYAMLMissingConfig  = errors.New("missing field in config file")
	ErrYAMLBadBoolLiteral = errors.New("must be either false or true, " +
		"other variants of boolean literals of YAML are not supported")
	ErrYAMLTagUsed          = errors.New("avoid using YAML tags")
	ErrYAMLNullOnNonPointer = errors.New("cannot assign null to non-pointer type")
	ErrYAMLBadNullLiteral   = errors.New("must be null, " +
		"any other variants of null are not supported")

	ErrTypeRecursive   = errors.New("recursive type")
	ErrTypeIllegalRoot = errors.New("root type must be a struct type and must not " +
		"implement encoding.TextUnmarshaler and yaml.Unmarshaler")
	ErrTypeMissingYAMLTag     = errors.New("missing yaml struct tag")
	ErrTypeEnvTagOnUnexported = errors.New("env tag on unexported field")
	ErrTypeTagOnInterfaceImpl = errors.New("implementations of interfaces " +
		"yaml.Unmarshaler or encoding.TextUnmarshaler must not " +
		"contain yaml and env tags")
	ErrTypeEnvOnYAMLUnmarsh = errors.New("env var on yaml.Unmarshaler implementation")
	ErrTypeNoExportedFields = errors.New("no exported fields")
	ErrTypeInvalidEnvTag    = fmt.Errorf("invalid env struct tag: "+
		"must match the POSIX env var regexp: %s", regexEnvVarPOSIXPattern)
	ErrTypeEnvVarOnUnsupportedType = errors.New("env var on unsupported type")
	ErrTypeUnsupported             = errors.New("unsupported type")
	ErrTypeUnsupportedPtrType      = errors.New("unsupported pointer type")

	ErrEnvInvalidVar = errors.New("invalid env var")
)

All possible errors.

Functions

func Load

func Load[T any, S string | []byte](yamlSource S, config *T) error

Load reads and validates the configuration of type T from yamlSource. Load behaves similar to LoadFile.

func LoadFile added in v0.2.0

func LoadFile[T any](yamlFilePath string, config *T) error

LoadFile reads and validates the configuration of type T from a YAML file. Will return an error if:

  • ValidateType returns an error for T.
  • the yaml file is empty or not found.
  • the yaml file doesn't contain a field specified by T.
  • the yaml file is missing a field specified by T.
  • the yaml file contains values that don't pass validation.
  • the yaml file contains boolean literals other than `true` and `false`.
  • the yaml file contains null values other than `null` (`~`, etc.).
  • the yaml file assigns `null` to a non-pointer Go type.
  • the yaml file contains any YAML tags (https://yaml.org/spec/1.2.2/#3212-tags).
  • the yaml file contains any redeclared anchors.
  • the yaml file contains any unused anchors.
  • the yaml file contains any anchors with implicit null value (no value).

func Validate added in v0.7.0

func Validate[T any](t T) error

Validate behaves similar to Load and LoadFile just without parsing YAML and instead performing the same type and value checks on t. Validate will obviously not report line:column error location. Validate first validates type T, then validates t according to go-playground/validator struct tags, then recursively invokes all Validate methods returning an error if any.

func ValidateType added in v0.2.0

func ValidateType[T any]() error

ValidateType returns an error if...

  • T contains any struct field without a "yaml" struct tag.
  • T contains any struct field with an invalid "env" struct tag.
  • T is recursive.
  • T contains any unsupported types (signed and unsigned integers with unspecified width, interface (including `any`), function, channel, unsafe.Pointer, pointer to pointer, pointer to slice, pointer to map).
  • T is not a struct or implements yaml.Unmarshaler or encoding.TextUnmarshaler.
  • T contains any structs with no exported fields.
  • T contains any structs with yaml and/or env tags assigned to unexported fields.
  • T contains any struct implementing either yaml.Unmarshaler or encoding.TextUnmarshaler that contains fields with yaml or env struct tags.
  • T contains any fields with env tag on a type that implements yaml.Unmarshaler.
  • T contains any struct containing multiple fields with the same yaml tag.

Types

type Validator

type Validator interface{ Validate() error }

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL