config

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Mar 23, 2020 License: MIT Imports: 8 Imported by: 0

README

config

godoc Licence Latest version

Build Status Code quality Code coverage

A simple yet powerful configuration package.

Motivation

On any project I've made personnally or for a company, except if the project was really (really) small, I always needed at some point to be able to configure a component in the project (the http listening port, the database credentials, a business configuration, ...). I've been using viper for some times now, but I was not really happy about it for some reasons (usage of strings keys to get configuration, globally defined configuration which are a p.i.t.a. in big project to understand what's used where, and to concurently use and modify in test, ...). I also used confita from which this project was inspired.

From my point of view a configuration package should:

  • have a priorization of "sources" (for example file < env < cli args)
  • be strongly typed (it should never use string keys to get a value, or return interface{} left for the call to cast)
  • be modulable (add a new "source" to retrieve configuration from vauld or consul for example)
  • handle defaults values (without string keys, and as close as the configuration definition)
  • have a clear and easy to use API
  • be light
  • encourage and follow the best practices

That's what I tried to do in this configuration package which is made of 4 components:

  • the default setter which handles defaults
  • the sources (anything that implements one of the two sources interfaces) responsible for the retrieval of the configuration
  • the "loader" which is responsible to set the default if any and to call each sources
  • additionally, a validation can be made to make sure configuration is valid

Usage / examples

// let's define a structure that hold our http configuration, for example
type HTTPConfig struct {
    Debug          bool
    ListenAddress  string
    RequestTimeout time.Duration 
    MACSecret      []byte
}

// SetDefault sets sane default for http config.
func (c *HTTPConfig) SetDefault() {
    c.ListenAddress = ":8080"
    c.RequestTimeout = 3 * time.Second
}

// Validate checks whenever the config is properly set.
func (c *HTTPConfig) Validate() error {
    if c.RequestTimeout < time.Second {
        return errors.New("request timeout is too short (min 1s)")
    }
}

func main() {
    // export PREFIX_DEBUG="true"
    // export PREFIX_MACSECRET="secret"
    // echo "{ "listen-address": ":8082" }" > ./conf.json

    var cfg HTTPConfig

    if err := config.Load(&cfg, config.WithSources(config.Source{
        sourcefile.New("./conf.json"),
        sourceenv.New("prefix"),
    })); err != nil {
        panic(err)
    }

    if err := config.Validate(&cfg); err != nil {
        panic(err)
    }

    // cfg.Debug          = "true"
    // cfg.ListenAddress  = ":8082"
    // cfg.RequestTimeout = "3s"
    // cfg.MACSecret      = "secret"
}

More doc and examples in the config's godoc

License

This project is under the MIT licence, please see the LICENCE file.

Documentation

Overview

Package config is a light yet powerful config loader.

Load

Configuration loading is as easy as:

if err := config.Load(&cfg, config.WithSources(
	sourcefile.New("./config.yaml"),
	sourceenv.New("myapp"),
)); err != nil {
	panic(err)
}

See each sources to get more details on how to use them.

Defaults

Set default recursively by walking through any types and try to apply defaults.

A default is applied if the type implement the setDefaultFunc interface and the actual value is the zero value of the type.

Applying defaults

Considering the following structure:

type Furniture struct{
	Color       Color
	Weight      float64
	IsAvailable bool
}

func (f *Furniture) SetDefault() {
	f.IsAvailable = true
}

type (
	Color string
)

const (
	ColorUnknown = Color("unknown")
	ColorBlack   = Color("black")
	ColorGreen   = Color("green")
)

func (c *Color) SetDefault() {
	*c = ColorUnknown
}

If we want to apply defaults to an instance of furniture, we just have to:

var f Furniture
defaulter.SetDefault(&f)
// f has an unknown color and is available

Special cases when using defaults

We already see that the SetDefault method will be call if any value is the zero value and value's type implements SetDefault. There is two exception:

If the type is a pointer:

type Furniture struct{
	IsAvailable  bool
	OwnerAddress *Address
}

type Address string

func (a *Address) SetDefault() {
	*a = "nowhere"
}

func foo() {
	var f Funiture

	defaulter.SetDefault(&f)
	// even if Address type implements SetDefault, the method will not be call
	// as f.OwnerAddress is nil, and we may want to keep it that way
}

If it's a struct field with the `default:"-"` tag:

type Furniture struct{
	IsAvailable  bool
	OwnerAddress Address `default:"-"`
}

func (a *Address) SetDefault() {
	*a = "nowhere"
}

func (f *Furniture) SetDefault() {
	f.IsAvailable = true
}

funf foo() {
	var f Furniture

	defaulter.SetDefault(&f)
	// here f.IsAvailable is true
	// but f.OwnderAddress still contains the zero value, because of the `nodefault` tag
}

Validation

Configuration validation can be made to validate types implementing validateFunc. It recursively walk through any types and call validateFunc if defined, for any of them.

Considering the following structure:

type Furniture struct{
	Color       Color
	Weight      float64
	IsAvailable bool
}

func (f Furniture) Validate() error {
	if !f.IsAvailable {
		return fmt.Errorf("furniture is not available")
	}
	return nil
}

type (
	Color string
)

const (
	ColorUnknown = Color("unknown")
	ColorBlack   = Color("black")
	ColorGreen   = Color("green")
)

func (c Color) Validate() error {
	if *c == ColorUnknown {
		return fmt.Errorf("unknown color")
	}
	return nil
}

If we want to validate to an instance of furniture, we just have to:

f := getFurniture()
validator.Validate(f)
// which will fail if furniture is not available or has an unknown color

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func InitializeNewValueOfTypeWithJSON

func InitializeNewValueOfTypeWithJSON(typ reflect.Type, jsonData []byte) (*reflect.Value, error)

InitializeNewValueOfTypeWithJSON . nolint: gocyclo

func InitializeNewValueOfTypeWithString

func InitializeNewValueOfTypeWithString(typ reflect.Type, str string) (*reflect.Value, error)

InitializeNewValueOfTypeWithString .

func Load

func Load(cfg interface{}, opts ...Option) error

Load creates a new instance (see New), and call the Load method of it (see Config.Load).

Example
package main

import (
	"fmt"
	"os"

	"github.com/krostar/config"
	sourceenv "github.com/krostar/config/source/env"
)

type Config struct {
	NoDefault string
	Some      OtherConfig
	SomeOther *OtherConfig
	SomeLast  *OtherConfig
}

type OtherConfig struct {
	UniversalAnswer int
}

func (c *OtherConfig) SetDefault() {
	c.UniversalAnswer = 42
}

func main() {
	var cfg Config

	os.Setenv("MYAPP_SOMEOTHER_UNIVERSALANSWER", "1010") // nolint: errcheck, gosec
	defer os.Unsetenv("MYAPP_SOMEOTHER_UNIVERSALANSWER") // nolint: errcheck, gosec

	if err := config.Load(&cfg, config.WithSources(
		sourceenv.New("myapp"),
	)); err != nil {
		panic(err)
	}

	fmt.Printf("cfg.NoDefault                 = %q\n", cfg.NoDefault)
	fmt.Printf("cfg.Some.UniversalAnswer      = %d\n", cfg.Some.UniversalAnswer)
	fmt.Printf("cfg.SomeOther.UniversalAnswer = %d\n", cfg.SomeOther.UniversalAnswer)
	fmt.Printf("cfg.SomeLast                  = %v\n", cfg.SomeLast)

}
Output:

cfg.NoDefault                 = ""
cfg.Some.UniversalAnswer      = 42
cfg.SomeOther.UniversalAnswer = 1010
cfg.SomeLast                  = <nil>

func SetDefault

func SetDefault(i interface{}) error

SetDefault tries to set default to the provided interface. For a default to be applied, the interface needs to implement the `setDefaultFunc` interface, or to contain any fields (in case of a struct) that implement it. The SetDefault method will be called only if the value is the zero value.

Example
package main

import (
	"fmt"

	"github.com/krostar/config"
)

type HTTPConfig struct {
	Debug         bool
	ListenAddress string
}

func (c *HTTPConfig) SetDefault() {
	c.ListenAddress = "127.0.0.1:8080"
}

func (c HTTPConfig) Validate() error {
	if c.ListenAddress == "" {
		return fmt.Errorf("listening address can't be empty")
	}
	return nil
}

func main() {
	var cfg HTTPConfig

	if err := config.SetDefault(&cfg); err != nil {
		panic("unable to set defaults")
	}

	fmt.Println("debug:", cfg.Debug)
	fmt.Println("listen-address:", cfg.ListenAddress)

}
Output:

debug: false
listen-address: 127.0.0.1:8080

func SetNewValue

func SetNewValue(oldValue, newValue *reflect.Value) (bool, error)

SetNewValue .

func Validate

func Validate(i interface{}) error

Validate calls validateFunc for all types that implements it.

Example
package main

import (
	"fmt"

	"github.com/krostar/config"
)

type HTTPConfig struct {
	Debug         bool
	ListenAddress string
}

func (c *HTTPConfig) SetDefault() {
	c.ListenAddress = "127.0.0.1:8080"
}

func (c HTTPConfig) Validate() error {
	if c.ListenAddress == "" {
		return fmt.Errorf("listening address can't be empty")
	}
	return nil
}

func main() {
	cfg := HTTPConfig{
		Debug:         false,
		ListenAddress: "",
	}

	err := config.Validate(&cfg)

	fmt.Println("failed:", err != nil)
	fmt.Println("reason:", err.Error())

}
Output:

failed: true
reason: validation error: listening address can't be empty

Types

type Config

type Config struct {
	// contains filtered or unexported fields
}

Config stores the source configuration applied through options.

func New

func New(opts ...Option) (*Config, error)

New creates a new config instance configured through options.

func (*Config) Load

func (c *Config) Load(cfg interface{}, opts ...Option) error

Load tries to apply defaults to the provided interface and call all sources to load the configuration.

type Option

type Option func(c *Config) error

Option defines the function signature to apply options.

func WithRawSources

func WithRawSources(s ...Source) Option

WithRawSources appends the given initializd source to the list of configuration sources.

func WithSources

func WithSources(sf ...SourceCreationFunc) Option

WithSources appends the given source to the list of configuration sources.

type Source

type Source interface {
	Name() string
}

Source defines the interface any source must implements.

type SourceCreationFunc

type SourceCreationFunc func() (Source, error)

SourceCreationFunc defines how to creates a new source.

type SourceSetValueFromConfigTreePath

type SourceSetValueFromConfigTreePath interface {
	Source
	SetValueFromConfigTreePath(v *reflect.Value, treePath string) (bool, error)
}

SourceSetValueFromConfigTreePath defines a way to set explicitly each values from each configuration paths.

type SourceUnmarshal

type SourceUnmarshal interface {
	Source
	Unmarshal(cfg interface{}) error
}

SourceUnmarshal defines a way to apply a source to a config via unmarshalling directly to it.

type ValidationError

type ValidationError map[string]error

ValidationError contains all validation errors. Keys are field in error (with arboresence) and value the error. Key can be empty if the root interface failed.

func (ValidationError) Error

func (v ValidationError) Error() string

Error implements error.

func (ValidationError) String

func (v ValidationError) String() string

String implements Stringer for ValidationError.

Directories

Path Synopsis
internal
trivialerr
Package trivialerr helps to define and use trivial (not critical) errors.
Package trivialerr helps to define and use trivial (not critical) errors.
source
env
Package sourceenv sources configuration from env.
Package sourceenv sources configuration from env.
file
Package sourcefile sources configuration from file.
Package sourcefile sources configuration from file.

Jump to

Keyboard shortcuts

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