appconf

package module
v0.0.0-...-fe7e06c Latest Latest
Warning

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

Go to latest
Published: Mar 27, 2022 License: Apache-2.0 Imports: 12 Imported by: 0

README

appconf

CI Status Go Report Card Package Doc Releases

Opinonated configuration loader for golang applications.

Usage

Installation

This module uses golang modules and can be installed with

go get github.com/halimath/appconf@main

Basic Usage

appconf provides a type AppConfig which collects configuration input from different loaders, merges them together and provides a type-safe API to query fields from the config. Consider this example:

c, err := appconf.New(
	appconf.Static(map[string]interface{}{
		"web.address": "http://example.com",
		"DB": map[string]interface{}{
			"Timeout": 2 * time.Second,
		},
	}),
	appconf.JSONFile("./testdata/config.json", true),
)
if err != nil {
	panic(err)
}

fmt.Println(c.HasKey("Web"))
fmt.Println(c.HasKey("Foo"))
fmt.Println(c.GetString("Web.Address"))
fmt.Println(c.GetDuration("db.timeout"))
Loaders

The example uses two loaders to provide input for the configuration: a Static loader providing default values and a JSONFile loader overriding some (or all of the values).

The module contains loaders for the following kind of input:

  • JSON (both from a Reader and from a file)
  • YAML (both from a Reader and from a file)
  • TOML (both from a Reader and from a file)
  • environment variables

You can create your own loader by implementing the Loader interface. See below for details.

Once the configuration is loaded the individual values can be queried using their key.

Keys

Keys are used to access configuration values. Keys are considered case-insensitive due to the fact that not all loaders are able to deliver case-sensitive keys (such as environment variables). Keys can be nested. When queriying nested keys use a single dot to separate the parts (this is called a key path).

Getters

When queriying values you can use different getters to convert the value to a desired type. The following getters are available:

  • GetString
  • GetInt
  • GetInt64
  • GetUint
  • GetUint64
  • GetFloat32
  • GetFloat64
  • GetComplex128
  • GetBool
  • GetDuration

Each of these getters always returns a valid value. If the key is not defined or if the underlying value can not be converted to the given type, they return the type's default value. There is a corresponding Get...E version of the getter, which returns an error in addition to the value.

Using sub-configurations

You can call the configuration's Sub method to query a key and return the configuration structure rooted at that key. This is usefull if you want to pass only parts of the configuration to downstream code, such as the database configuration rooted at key db: Simply call conf.Sub("db").

As with the getters described above, Sub always returns a valid configuration. It is empty when the given key is not found. There is a corresponding SubE method, which returns a configuration and an optional error.

Binding

appconf supports binding configuration to struct values. This is done using reflection and it works similar to unmarshaling of i.e. JSON code:

c, _ := appconf.Load(...)

var config ConfigStruct

if err := c.Bind(&config); err != nil {
	panic(err)
}

The above code shows how to bind to a ConfigStruct value. By default each struct field is assigned the value of the config value with a key formed by converting the field name to lower case. If you want to bind a different key, add a field tag of the form

type Config struct {
	SomeValue string `appconf:"somekey"`
}

If you want to ignore a struct field during binding add the field tag appconf:",ignore". Note the comma before ignore which is important as otherwise the field would be bound to a key named ignore.

Bindings works with nested structs and nested slices. The keys for slice elements are formed by putting the index as a single key path element, i.e. db.hosts.0.name.

You can also bind the configuration to a map[string]interface{}. Keep in mind, that all leaf values are added as strings.

Implementing a custom loader

To implement a custom configuration loader you create a type which implements the Loader interface. This interface contains a single method which loads the configuration and returns it as a Node in addition to any error. Nodes form a tree with keys annotated to each Node. Leaf Nodes carry the configuration values. This is the internal representation this modules uses to store, merge and query values. Trees of Nodes can be constructed manually or by using the factory function ConvertToNode which accepts a map[string]interface{}.

License

Copyright 2022 Alexander Metzner.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Documentation

Overview

Package appconf provides a flexible configuration loader for applications that wish to load configuration values from different sources, such as files or the environment. It supports nested data structures, type-safe conversions and binding using reflection.

Example
package main

import (
	"fmt"
	"time"

	"github.com/halimath/appconf"
)

func main() {
	c, err := appconf.New(
		appconf.Static(map[string]interface{}{
			"web.address": "http://example.com",
			"DB": map[string]interface{}{
				"Timeout": 2 * time.Second,
			},
		}),
		appconf.JSONFile("./testdata/config.json", true),
	)
	if err != nil {
		panic(err)
	}

	fmt.Println(c.HasKey("Web"))
	fmt.Println(c.HasKey("Foo"))
	fmt.Println(c.GetString("Web.Address"))
	fmt.Println(c.GetDuration("db.timeout"))

}
Output:

true
false
localhost:8080
2s

Index

Examples

Constants

View Source
const (
	FieldTagKey            = "appconf"
	FieldTagValueSeparator = ","
	FieldTagIgnore         = "ignore"
)
View Source
const (
	KeySeparator = "."
)

Variables

View Source
var (
	ErrUnsupportedValue = errors.New("unsupported value")
	ErrNoSuchKey        = errors.New("no such key")
	ErrNotAScalar       = errors.New("not a scalar value")
)
View Source
var (
	// ErrInvalidBindingType is returned from binding when a value's type is not supported for binding.
	ErrInvalidBindingType = errors.New("invalid binding type")
)

Functions

This section is empty.

Types

type AppConfig

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

AppConf is the main data type used to interact with configuration values.

func New

func New(loaders ...Loader) (*AppConfig, error)

New creates a new AppConfig using the given loaders. The loaders are executed in given order with values from later loaders overwriting values from earlier ones (put most significant loaders last).

func (*AppConfig) Bind

func (c *AppConfig) Bind(v interface{}) error

Bind binds the configuration to the data structure v and returns any error that occured during binding. v must be a pointer to either a struct value or a map[string]interface{}. Other values are not supported and are rejected by an error. See the README for an explanation of how to use and customize the binding.

Example (Map)
package main

import (
	"fmt"

	"github.com/halimath/appconf"
)

func main() {
	c, err := appconf.New(appconf.JSONFile("./testdata/config.json", true))
	if err != nil {
		panic(err)
	}

	var config map[string]interface{}
	if err := c.Bind(&config); err != nil {
		panic(err)
	}

	fmt.Printf("%v\n", config)

}
Output:

map[backends:map[0:map[host:alpha port:8080 tags:map[0:a 1:1]] 1:map[host:beta port:8081 tags:map[0:b 1:2]]] db:map[host:localhost password:secret port:3306 type:mysql user:test] web:map[address:localhost:8080 authorize:true timeout:2s]]
Example (Struct)
package main

import (
	"fmt"
	"time"

	"github.com/halimath/appconf"
)

func main() {
	type Config struct {
		DB struct {
			Engine string `appconf:"type"`
			Host   string
			Port   int
			User   string `appconf:",ignore"`
		}
		Web struct {
			Address   string
			Timeout   time.Duration
			Authorize bool
		}
		Backends []struct {
			Host string
			Port int
			Tags []string
		}
	}

	c, err := appconf.New(appconf.JSONFile("./testdata/config.json", true))
	if err != nil {
		panic(err)
	}

	var config Config
	if err := c.Bind(&config); err != nil {
		panic(err)
	}

	fmt.Printf("%v\n", config)

}
Output:

{{mysql localhost 3306 } {localhost:8080 2s true} [{alpha 8080 [a 1]} {beta 8081 [b 2]}]}

func (*AppConfig) GetBool

func (c *AppConfig) GetBool(key string) bool

GetBool returns the bool value stored under key.

func (*AppConfig) GetBoolE

func (c *AppConfig) GetBoolE(key string) (bool, error)

GetBoolE returns the bool value stored under key or an error if the key is not defined or the value cannot be converted to an bool.

func (*AppConfig) GetComplex128

func (c *AppConfig) GetComplex128(key string) complex128

GetComplex128 returns the complex128 value stored under key.

func (*AppConfig) GetComplex128E

func (c *AppConfig) GetComplex128E(key string) (complex128, error)

GetComplex128E returns the complex128 value stored under key or an error if the key is not defined or the value cannot be converted to an complex128.

func (*AppConfig) GetDuration

func (c *AppConfig) GetDuration(key string) time.Duration

GetDuration returns the duration value stored under key.

func (*AppConfig) GetDurationE

func (c *AppConfig) GetDurationE(key string) (time.Duration, error)

GetDurationE returns the duration value stored under key or an error if the key is not found or the string value cannot be parsed into a Duration.

func (*AppConfig) GetFloat32

func (c *AppConfig) GetFloat32(key string) float32

GetFloat32 returns the float32 value stored under key.

func (*AppConfig) GetFloat32E

func (c *AppConfig) GetFloat32E(key string) (float32, error)

GetFloat32E returns the float32 value stored under key or an error if the key is not defined or the value cannot be converted to an float32.

func (*AppConfig) GetFloat64

func (c *AppConfig) GetFloat64(key string) float64

GetFloat64 returns the float64 value stored under key.

func (*AppConfig) GetFloat64E

func (c *AppConfig) GetFloat64E(key string) (float64, error)

GetFloat64E returns the float64 value stored under key or an error if the key is not defined or the value cannot be converted to an float64.

func (*AppConfig) GetInt

func (c *AppConfig) GetInt(key string) int

GetInt returns the int value stored under key.

func (*AppConfig) GetInt64

func (c *AppConfig) GetInt64(key string) int64

GetInt64 returns the int64 value stored under key.

func (*AppConfig) GetInt64E

func (c *AppConfig) GetInt64E(key string) (int64, error)

GetInt64E returns the int64 value stored under key or an error if the key is not defined or the value cannot be converted to an int64.

func (*AppConfig) GetIntE

func (c *AppConfig) GetIntE(key string) (int, error)

GetIntE returns the int value stored under key or an error if the key is not defined or the value cannot be converted to an int.

func (*AppConfig) GetString

func (c *AppConfig) GetString(key string) string

GetString returns the string value stored under key.

func (*AppConfig) GetStringE

func (c *AppConfig) GetStringE(key string) (string, error)

GetStringE returns the string value stored under key or an error if the key is not defined.

func (*AppConfig) GetUint

func (c *AppConfig) GetUint(key string) uint

GetUint returns the uint value stored under key.

func (*AppConfig) GetUint64

func (c *AppConfig) GetUint64(key string) uint64

GetUint64 returns the uint64 value stored under key.

func (*AppConfig) GetUint64E

func (c *AppConfig) GetUint64E(key string) (uint64, error)

GetUint64E returns the uint64 value stored under key or an error if the key is not defined or the value cannot be converted to an uint64.

func (*AppConfig) GetUintE

func (c *AppConfig) GetUintE(key string) (uint, error)

GetUintE returns the uint value stored under key or an error if the key is not defined or the value cannot be converted to an uint.

func (*AppConfig) HasKey

func (c *AppConfig) HasKey(key string) bool

HasKey returns whether c contains key which may be nested key.

func (*AppConfig) Sub

func (c *AppConfig) Sub(key string) *AppConfig

Sub returns the sub-structure of c rooted at key.

func (*AppConfig) SubE

func (c *AppConfig) SubE(key string) (*AppConfig, error)

SubE returns the sub-structure of c rooted at key or an error if the key does not exist.

type Key

type Key string

func NormalizeKey

func NormalizeKey(k string) Key

type KeyPath

type KeyPath []Key

func ParseKeyPath

func ParseKeyPath(s string) KeyPath

func (KeyPath) Join

func (p KeyPath) Join() string

type Loader

type Loader interface {
	// Load loads the configuration values and returns them as a tree of Nodes. If the loading was not
	// successful an error should be returned.
	Load() (*Node, error)
}

Loader defines the interface implemented by all loaders.

func Env

func Env(prefix string) Loader

Env creates a Loader which reads configuration values from the environment. Only env variables with a name starting with prefix are considered. Use the empty string to select all variables.

func File

func File(filename string, mandatory bool, l ReaderLoaderFunc) Loader

File creates a Loader that reads the file named filename and forwards the content to l. If mandatory is set to false, an empty configuration will be returned when filename does not exist. Otherwise this is is reported as an error.

func JSONFile

func JSONFile(name string, mandatory bool) Loader

JSONFile creates a Loader which loads JSON configuration from a file name.

func Static

func Static(m map[string]interface{}) Loader

Static creates a Loader that returns static configuration values from the given map structure. The map's values are limited to strings, map[string]interface{} (with the same value constraints applied) or slices of either strings or maps.

func TOMLFile

func TOMLFile(name string, mandatory bool) Loader

TOMLFile creates a Loader which loads TOML configuration from a file name.

func YAMLFile

func YAMLFile(name string, mandatory bool) Loader

YAMLFile creates a Loader which loads YAML configuration from a file name.

type LoaderFunc

type LoaderFunc func() (*Node, error)

LoaderFunc is a convenience type to convert a function to a Loader.

func (LoaderFunc) Load

func (l LoaderFunc) Load() (*Node, error)

type Node

type Node struct {
	Value    string
	Children map[Key]*Node
}

func ConvertToNode

func ConvertToNode(m map[string]interface{}) (*Node, error)

func JSON

func JSON(r io.Reader) (*Node, error)

JSON parses the r's content as JSON and converts it to a Node tree.

func NewNode

func NewNode(v string) *Node

func TOML

func TOML(r io.Reader) (*Node, error)

TOML loads the content from r and converts it to a Node tree.

func YAML

func YAML(r io.Reader) (*Node, error)

YAML loades the content from r and converts it to a Node tree.

func (*Node) Dump

func (n *Node) Dump(indent int)

func (*Node) GetBool

func (n *Node) GetBool() bool

func (*Node) GetBoolE

func (n *Node) GetBoolE() (bool, error)

func (*Node) GetComplex128

func (n *Node) GetComplex128() complex128

func (*Node) GetComplex128E

func (n *Node) GetComplex128E() (complex128, error)

func (*Node) GetDuration

func (n *Node) GetDuration() time.Duration

func (*Node) GetDurationE

func (n *Node) GetDurationE() (time.Duration, error)

func (*Node) GetFloat32

func (n *Node) GetFloat32() float32

func (*Node) GetFloat32E

func (n *Node) GetFloat32E() (float32, error)

func (*Node) GetFloat64

func (n *Node) GetFloat64() float64

func (*Node) GetFloat64E

func (n *Node) GetFloat64E() (float64, error)

func (*Node) GetInt

func (n *Node) GetInt() int

func (*Node) GetInt64

func (n *Node) GetInt64() int64

func (*Node) GetInt64E

func (n *Node) GetInt64E() (int64, error)

func (*Node) GetIntE

func (n *Node) GetIntE() (int, error)

func (*Node) GetString

func (n *Node) GetString() string

func (*Node) GetStringE

func (n *Node) GetStringE() (string, error)

func (*Node) GetUint

func (n *Node) GetUint() uint

func (*Node) GetUint64

func (n *Node) GetUint64() uint64

func (*Node) GetUint64E

func (n *Node) GetUint64E() (uint64, error)

func (*Node) GetUintE

func (n *Node) GetUintE() (uint, error)

func (*Node) OverwriteWith

func (n *Node) OverwriteWith(o *Node)

type ReaderLoaderFunc

type ReaderLoaderFunc func(io.Reader) (*Node, error)

ReaderLoaderFunc is function type to implement Loaders that consume an io.Reader.

Jump to

Keyboard shortcuts

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