xconf

package module
v1.8.0 Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2023 License: MIT Imports: 30 Imported by: 1

README

Xconf

Build Status License Coverage Status Goreportcard Go Reference


Package xconf provides a configuration registry for an application.
Configurations can be extracted from a file / env / flag set / remote system.
Supported formats are json, yaml, ini, (java) properties, toml, plain.

Installation
$ go get -u github.com/actforgood/xconf
Configuration loaders

You can create your own configuration retriever implementing Loader interface. Package provides these Loaders for you:

  • EnvLoader - loads environment variables.
  • DotEnvFileLoader, DotEnvReaderLoader - loads configuration from a .env file / io.Reader.
  • JSONFileLoader, JSONReaderLoader - loads json configuration from a file / io.Reader.
  • YAMLFileLoader, YAMLReaderLoader - loads yaml configuration from a file / io.Reader.
  • IniFileLoader - loads ini configuration from a file.
  • PropertiesFileLoader, PropertiesBytesLoader - loads java style properties configuration from a file / bytes slice.
  • TOMLFileLoader, TOMLReaderLoader - loads toml configuration from a file / io.Reader.
  • ConsulLoader - loads json/yaml/plain configuration from a remote Consul KV Store.
  • EtcdLoader - loads json/yaml/plain configuration from a remote Etcd KV Store.
  • PlainLoader - explicit configuration provider.
  • FileLoader - factory for <JSON|YAML|Ini|DotEnv|Properties|TOML>FileLoaders based on file extension.
  • FlagSetLoader - extracts configuration from a flag.FlagSet.
  • MultiLoader - loads (and merges, if configured) configuration from multiple loaders.

Upon above loaders there are available decorators which can help you achieve more sophisticated outcome:

  • FilterKVLoader - filters other loader's configurations (based on keys and or their values).
    Example of applicability: I load configurations from environment, but I only want the ones prefixed with "MY_APP_" - I can apply this loader with FilterKVWhitelistFunc(FilterKeyWithPrefix("MY_APP_") filter function.
  • AlterValueLoader - changes the value for a configuration key.
    Example of applicability: I load configurations from environment and for a given key I want its value to be a slice (not a string as envs are read/stored by default) - I can apply this loader with ToStringList altering function.
  • IgnoreErrorLoader - ignores the error returned by another loader.
    Example of applicability: I load configuration from environment and from file (using a MultiLoader), but it's not mandatory for that file to exist (file it's just an auxiliary source for my configurations, that may exist) - I can use this loader to ignore "file does not exist" error.
  • FileCacheLoader - caches configuration from a [X]FileLoader until file gets modified (to be used if loader is called multiple times).
  • FlattenLoader - creates easy to access nested configuration leaf keys symlinks.
  • AliasLoader - creates aliases for other keys.
Configuration contract

The main configuration contract this package provides looks like:

type Config interface {
	Get(key string, def ...any) any
}

with a default implementation obtained with:

// NewDefaultConfig instantiates a new default config object.
// The first parameter is the loader used as a source of getting the key-value configuration map.
// The second parameter represents a list of optional functions to configure the object.
func NewDefaultConfig(loader Loader, opts ...DefaultConfigOption) (*DefaultConfig, error)

The DefaultConfig has an option of reloading configurations (interval based), if you want to retrieve updated configuration at runtime. There are 2 (proposed) ways of working with it:

  • injecting a Config reference and calling Get(key) every time you need a configuration.
  • registering your class as an observer to get notified about config changes.

Example of usage (first case) (note: code does not compile):

// cart_service.go
const (
	defaultMaxQtyCfgVal uint = 100
	maxQtyCfgKey             = "MAX_ALLOWED_QTY_TO_ORDER"
)

type CartService struct {
	config xconf.Config
}

func NewCartService(config xconf.Config) *CartService {
	return &CartService{
		config: config,
	}
}

func (cartSvc *CartService) AddProduct(sku string, qty uint) error {
	// ...
	if customerType != B2B {
		totalQty := currentQty + qty
		maxQty := cartSvc.config.Get(maxQtyCfgKey, defaultMaxQtyCfgVal).(uint)
		if totalQty > maxQty {
			return ErrMaxQtyExceeded
		}
	}
	// ...
	return nil
}

func main() {
	// somewhere in the bootstrap of your application ...
	var (
		loader  xconf.Loader // = ... your desired source(s)
		config  xconf.Config
		cartSvc *CartService
	)
	config, err := xconf.NewDefaultConfig(
		loader,
		xconf.DefaultConfigWithReloadInterval(time.Minute), // reload every minute
	)
	if err != nil {
		panic(err)
	}
	cartSvc = NewCartService(config)

	// somewhere in the application business flow ...
	_ = cartSvc.AddProduct("IPHONE", 1)

	// somewhere in the shutdown of your application ...
	if closableConfig, ok := config.(io.Closer); ok {
		_ = closableConfig.Close()
	}
}

Example of usage (second case) (note: code does not compile):

// redis_wrapper.go
const (
	RedisHostCfgKey = "REDIS_HOST"
	DefaultRedisHostCfgVal = "127.0.0.1:6379"
)

type RedisClient interface {
	Ping() error
	Get(key string) (string, error)
	Set(key string, value any, expiration time.Duration) (string, error)
	Close() error
}

type RedisClientWrapper struct {
	client *redis.Client // official client
	mu     sync.RWMutex
}

func NewRedisClientWrapper(host string) *RedisClientWrapper {
	officialClient = ...
	return &RedisClientWrapper {
		client: officialClient,
	}
}

func (wrapper *RedisClientWrapper) Get(key string) (string, error) {
	wrapper.mu.RLock()
	defer wrapper.mu.RUnlock()

	return wrapper.client.Get(key).Result()
}

func (wrapper *RedisClientWrapper) OnConfigChange(config xconf.Config, changedKeys ...string) {
	for _, changedKey := range changedKeys {
		if changedKey == RedisHostCfgKey { // or use strings.EqualFold() if you enabled DefaultConfigWithIgnoreCaseSensitivity.
			wrapper.mu.Lock()
			_ = wrapper.client.Close() // close previous client
			newClient := ... // reinitialize client based on config.Get(RedisHostCfgKey).(string)
			wrapper.client = newClient
			wrapper.mu.Unlock()
		}
	}
}

func main() {
	// somewhere in the bootstrap of your application ...
	var (
		loader      xconf.Loader // = ... your desired source(s)
		config      xconf.Config
		redisClient RedisClient
	)
	config, err := xconf.NewDefaultConfig(
		loader,
		xconf.DefaultConfigWithReloadInterval(30 * time.Second), // reload every 30 seconds
	)
	if err != nil {
		panic(err)
	}
	redisHost := config.Get(RedisHostCfgKey, DefaultRedisHostCfgVal).(string)
	redisClient = NewRedisClient(redisHost)
	config.RegisterObserver(redisClient.OnConfigChange) // register redis wrapper as an observer

	// somewhere in the application business flow ...
	_, _ = redisClient.Get("something")

	// somewhere in the shutdown of your application ...
	if closableConfig, ok := config.(io.Closer); ok {
		_ = closableConfig.Close()
	}
	_ = redisClient.Close()
}
Unmarshal configuration map to structs

This is not the subject of this package, but as a mention, you can achieve that if needed, with a package like github.com/mitchellh/mapstructure.
Example:

package main

import (
	"bytes"
	"fmt"

	"github.com/actforgood/xconf"
	"github.com/mitchellh/mapstructure"
)

type DBConfig struct {
	Host string
	Port int
	Auth Auth
}

type Auth struct {
	Username string
	Password string
}

func main() {
	var (
		jsonConfig = `{
	"db": {
		"host": "127.0.0.1",
		"port": 3306,
		"auth": {
			"username": "JohnDoe",
			"password": "verySecretPwd"
		}
	}		
}`
		dbConfig    DBConfig               // the struct to populate with configuration
		dbConfigMap map[string]any // the configuration map for "db" key
		loader      = xconf.JSONReaderLoader(bytes.NewReader([]byte(jsonConfig)))
	)

	// example using directly a Loader:
	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	dbConfigMap = configMap["db"].(map[string]any)
	if err := mapstructure.Decode(dbConfigMap, &dbConfig); err != nil {
		panic(err)
	}
	fmt.Printf("%+v", dbConfig)

	// example using the Config contract:
	config, err := xconf.NewDefaultConfig(loader)
	if err != nil {
		panic(err)
	}
	dbConfigMap = config.Get("db").(map[string]any)
	if err := mapstructure.Decode(dbConfigMap, &dbConfig); err != nil {
		panic(err)
	}
	fmt.Printf("%+v", dbConfig)

	// both Printf will produce: {Host:127.0.0.1 Port:3306 Auth:{Username:JohnDoe Password:verySecretPwd}}
}
TODOs

Things that can be added to package, extended:

  • Support more formats (like HCL)
  • Add also a writer/persister functionality (currently you can only read configurations) to different sources and formats (JSONFileWriter/YAMLFileWriter/EtcdWriter/ConsulWriter/...) implementing a common contract like:
type ConfigWriter interface {
	Write(configMap map[string]any) error
}
  • Add a typed struct with methods like GetString, GetInt...
Misc
  • Feel free to use this pkg if you like it and fits your needs. Check also other packages like spf13/viper ...
  • To run unit tests: make test / make cover .
  • To run integration tests: make test-integration / make cover-integration : will setup Consul and Etcd docker containers with some keys in them, run ./scripts/teardown_dockers.sh at the end to stop and remove containers).
  • To run benchmarks: make bench.
  • Project's class diagram can be found here.
License

This package is released under a MIT license. See LICENSE.
Other 3rd party packages directly used by this package are released under their own licenses.

Documentation

Overview

Package xconf provides a configuration registry for an application. Configurations can be extracted from a file / env / flag set / remote system. Supported formats are json, yaml, ini, (java) properties, toml, plain.

Index

Examples

Constants

View Source
const (
	// RemoteValueJSON indicates that content under a key is in JSON format.
	RemoteValueJSON = "json"
	// RemoteValueYAML indicates that content under a key is in YAML format.
	RemoteValueYAML = "yaml"
	// RemoteValuePlain indicates that content under a key is plain text.
	RemoteValuePlain = "plain"
)
View Source
const (

	// ConsulHeaderAuthToken is the header name for setting a token.
	// See also [Consul API Ref].
	//
	// [Consul API Ref]: https://www.consul.io/api-docs#authentication
	ConsulHeaderAuthToken = "X-Consul-Token"
)

Variables

View Source
var ErrAliasPairBroken = errors.New("alias - missing key")

ErrAliasPairBroken is an error returned by AliasLoader when the variadic list of aliases and their keys consists of odd no. of elements.

View Source
var ErrConsulKeyNotFound = errors.New("404 - Consul Key Not Found")

ErrConsulKeyNotFound is thrown when a Consul read key request responds with 404.

View Source
var ErrUnknownConfigFileExt = errors.New("unknown configuration file extension")

ErrUnknownConfigFileExt is an error returned by FileLoader if file extension does not match any supported format.

Functions

func DeepCopyConfigMap

func DeepCopyConfigMap(src map[string]any) map[string]any

DeepCopyConfigMap is a utility function to make a deep "copy"/clone of a config map.

func FilterEmptyValue

func FilterEmptyValue(_ string, value any) bool

FilterEmptyValue returns true if a value is nil or "". It can be used as a FilterKV like:

xconf.FilterKVBlacklistFunc(xconf.FilterEmptyValue)
Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	origLoader := xconf.PlainLoader(map[string]any{
		"redis_dial_timeout": "5s",
		"redis_dsn":          "",
	})
	loader := xconf.FilterKVLoader(
		origLoader,
		xconf.FilterKVBlacklistFunc(xconf.FilterEmptyValue),
	)

	configMap, _ := loader.Load()

	fmt.Println(configMap)

}
Output:

map[redis_dial_timeout:5s]

func FilterExactKeys

func FilterExactKeys(keys ...string) func(key string, _ any) bool

FilterExactKeys returns true if a key is present in the provided list. It can be used as a FilterKV like:

xconf.FilterKVWhitelistFunc(xconf.FilterExactKeys(key1, key2))
xconf.FilterKVBlacklistFunc(xconf.FilterExactKeys(key1, key2))
Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	origLoader := xconf.PlainLoader(map[string]any{
		"FOO": "foo value",
		"BAR": "bar value",
		"BAZ": "baz value",
	})
	loader := xconf.FilterKVLoader(
		origLoader,
		xconf.FilterKVWhitelistFunc(xconf.FilterExactKeys("FOO", "BAR")),
	)

	configMap, _ := loader.Load()
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

FOO: foo value
BAR: bar value

func FilterKeyWithPrefix

func FilterKeyWithPrefix(prefix string) func(key string, _ any) bool

FilterKeyWithPrefix returns true if a key has given prefix. It can be used as a FilterKV like:

xconf.FilterKVWhitelistFunc(xconf.FilterKeyWithPrefix(prefix))
xconf.FilterKVBlacklistFunc(xconf.FilterKeyWithPrefix(prefix))
Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	origLoader := xconf.PlainLoader(map[string]any{
		"APP_FOO_1": "bar 1",
		"APP_FOO_2": "bar 2",
		"OS":        "Windows",
	})
	loader := xconf.FilterKVLoader(
		origLoader,
		xconf.FilterKVWhitelistFunc(xconf.FilterKeyWithPrefix("APP_")),
	)

	configMap, _ := loader.Load()
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

APP_FOO_1: bar 1
APP_FOO_2: bar 2

func FilterKeyWithSuffix

func FilterKeyWithSuffix(suffix string) func(key string, _ any) bool

FilterKeyWithSuffix returns true if a key has given suffix. It can be used as a FilterKV like:

xconf.FilterKVWhitelistFunc(xconf.FilterKeyWithSuffix(suffix))
xconf.FilterKVBlacklistFunc(xconf.FilterKeyWithSuffix(suffix))
Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	origLoader := xconf.PlainLoader(map[string]any{
		"REDIS_SERVICE_HOST": "10.0.0.11",
		"REDIS_SERVICE_PORT": "6379",
		"OS":                 "Windows",
	})
	loader := xconf.FilterKVLoader(
		origLoader,
		// K8s style accessible Services
		xconf.FilterKVWhitelistFunc(xconf.FilterKeyWithSuffix("_SERVICE_HOST")),
		xconf.FilterKVWhitelistFunc(xconf.FilterKeyWithSuffix("_SERVICE_PORT")),
	)

	configMap, _ := loader.Load()
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

REDIS_SERVICE_HOST: 10.0.0.11
REDIS_SERVICE_PORT: 6379

func LogErrorHandler

func LogErrorHandler(loggerGetter func() xlog.Logger) func(error)

LogErrorHandler is a handler which can be used in a xconf.DefaultConfig object as a reload error handler. It logs the error with a xlog.Logger. Passed parameter is a function that returns the logger (Logger and Config depend one of each other, this way we can instantiate them separately...)

Example
package main

import (
	"fmt"
	"os"
	"time"

	"github.com/actforgood/xconf"
	"github.com/actforgood/xlog"
)

func main() {
	// initialize the logger.
	logger := xlog.NewSyncLogger(os.Stdout)
	defer logger.Close()

	// initialize Config object,
	// loader can be any other Loader, used this for the sake of simplicity and readability.
	loader := xconf.PlainLoader(map[string]any{
		"foo": "bar",
	})
	loggerGetter := func() xlog.Logger { return logger }
	config, _ := xconf.NewDefaultConfig( // treat the error on live code!
		loader,
		xconf.DefaultConfigWithReloadInterval(time.Second),
		xconf.DefaultConfigWithReloadErrorHandler(xconf.LogErrorHandler(loggerGetter)),
	)
	defer config.Close()

	foo := config.Get("foo", "default foo").(string)
	fmt.Println(foo)

}
Output:

bar

func LogLevelProvider

func LogLevelProvider(
	config Config,
	lvlKey string,
	defaultLvl string,
	levelLabels map[xlog.Level]string,
) xlog.LevelProvider

LogLevelProvider provides a level read from a Config object. It can be used to configure log level for a xlog.Logger. If the level configuration key is not found, the default provided level is returned. If the reload option is present on the config object, you may change during application run the underlying key without restarting the app, and new configured value will be used in place, if suitable.

Example
package main

import (
	"os"
	"time"

	"github.com/actforgood/xconf"
	"github.com/actforgood/xlog"
)

func main() {
	const logLevelKey = "APP_LOG_LEVEL"
	const defaultLogLevel = "WARN"

	// initialize Config object,
	// loader can be any other Loader, used this for the sake of simplicity and readability.
	loader := xconf.PlainLoader(map[string]any{
		logLevelKey: "INFO",
	})
	config, _ := xconf.NewDefaultConfig( // treat the error on live code!
		loader,
		xconf.DefaultConfigWithReloadInterval(time.Second),
	)
	defer config.Close()

	// initialize the logger with min level taken from Config.
	opts := xlog.NewCommonOpts()
	opts.MinLevel = xconf.LogLevelProvider(config, logLevelKey, defaultLogLevel, opts.LevelLabels)
	opts.Time = func() any { // mock time for output check
		return "2022-06-21T17:17:20Z"
	}
	opts.Source = xlog.SourceProvider(4, 1) // keep only filename for output check
	logger := xlog.NewSyncLogger(
		os.Stdout,
		xlog.SyncLoggerWithOptions(opts),
	)
	defer logger.Close()

	logger.Info(xlog.MessageKey, "log level is taken from xconf.Config")
	logger.Debug(xlog.MessageKey, "this message should not end up being logged as min level is INFO")

}
Output:

{"date":"2022-06-21T17:17:20Z","lvl":"INFO","msg":"log level is taken from xconf.Config","src":"/xlog_adapter_test.go:49"}

Types

type AlterValueFunc

type AlterValueFunc func(value any) any

AlterValueFunc is a function that manipulates a config's value.

func ToIntList

func ToIntList(sep string) AlterValueFunc

ToIntList makes a slice of integers from a string value, who's items are separated by given separator parameter.

If the original value is not a string, the value remains unaltered.

Example: "10,100,1000" => [10, 100, 1000].

func ToStringList

func ToStringList(sep string) AlterValueFunc

ToStringList makes a slice of strings from a string value, who's items are separated by given separator parameter.

If the original value is not a string, the value remains unaltered.

Example: "bread,eggs,milk" => ["bread", "eggs", "milk"].

type Config

type Config interface {
	// Get returns a configuration value for a given key.
	// The first parameter is the key to return the value for.
	// The second parameter is optional, and represents a default
	// value in case key is not found. It also has a role in inferring
	// the type of key's value (if it exists) and thus key's value
	// will be casted to default's value type.
	Get(key string, def ...any) any
}

Config provides prototype for returning configurations.

type ConfigObserver

type ConfigObserver func(cfg Config, changedKeys ...string)

ConfigObserver gets called to notify about changed keys on Config reload.

type ConsulLoader

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

ConsulLoader loads configuration from Consul Key-Value Store.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	// load all keys starting with "APP_"
	host := "http://127.0.0.1:8500"
	loader := xconf.NewConsulLoader(
		"APP_",
		xconf.ConsulLoaderWithHost(host),
		xconf.ConsulLoaderWithPrefix(),
	)

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}
}
Output:

func NewConsulLoader

func NewConsulLoader(key string, opts ...ConsulLoaderOption) ConsulLoader

NewConsulLoader instantiates a new ConsulLoader object that loads configuration from Consul.

func (ConsulLoader) Load

func (loader ConsulLoader) Load() (map[string]any, error)

Load returns a configuration key-value map from Consul KV Store, or an error if something bad happens along the process.

type ConsulLoaderOption

type ConsulLoaderOption func(*ConsulLoader)

ConsulLoaderOption defines optional function for configuring a Consul Loader.

func ConsulLoaderWithCache

func ConsulLoaderWithCache() ConsulLoaderOption

ConsulLoaderWithCache enables cache.

func ConsulLoaderWithContext

func ConsulLoaderWithContext(ctx context.Context) ConsulLoaderOption

ConsulLoaderWithContext sets request 's context. By default, a context.Background() is used.

func ConsulLoaderWithHTTPClient

func ConsulLoaderWithHTTPClient(client *http.Client) ConsulLoaderOption

ConsulLoaderWithHTTPClient sets the http client used for calls. A default one is provided if you don't use this option.

func ConsulLoaderWithHost

func ConsulLoaderWithHost(host string) ConsulLoaderOption

ConsulLoaderWithHost sets Consul's base url. By default, is set to "http://127.0.0.1:8500". Consul host can also be set through CONSUL_HTTP_ADDR and CONSUL_HTTP_SSL ENV as in official hashicorp's client.

Example:

xconf.ConsulLoaderWithHost("http://consul.example.com:8500")

func ConsulLoaderWithPrefix

func ConsulLoaderWithPrefix() ConsulLoaderOption

ConsulLoaderWithPrefix specifies if the lookup should be recursive and the "key" treated as a prefix instead of a literal match.

func ConsulLoaderWithQueryDataCenter

func ConsulLoaderWithQueryDataCenter(dc string) ConsulLoaderOption

ConsulLoaderWithQueryDataCenter specifies the datacenter to query. This will default to the datacenter of the agent being queried. See also official doc. Example:

xconf.ConsulLoaderWithQueryDataCenter("my-dc")

func ConsulLoaderWithQueryNamespace

func ConsulLoaderWithQueryNamespace(ns string) ConsulLoaderOption

ConsulLoaderWithQueryNamespace specifies the namespace to query (enterprise). If not provided, the namespace will be inferred from the request's ACL token, or will default to the default namespace. For recursive lookups, the namespace may be specified as '*' and then results will be returned for all namespaces. Added in Consul 1.7.0. See also official doc.

Example:

xconf.ConsulLoaderWithQueryNamespace("my-ns")

func ConsulLoaderWithRequestHeader

func ConsulLoaderWithRequestHeader(hName, hValue string) ConsulLoaderOption

ConsulLoaderWithRequestHeader adds a request header. You can set the auth token for example:

xconf.ConsulLoaderWithRequestHeader(xconf.ConsulHeaderAuthToken, "someSecretToken")

or some basic auth header:

xconf.ConsulLoaderWithRequestHeader(
	"Authorization",
	"Basic " + base64.StdEncoding.EncodeToString([]byte(usr + ":" + pwd)),
)

func ConsulLoaderWithValueFormat

func ConsulLoaderWithValueFormat(valueFormat string) ConsulLoaderOption

ConsulLoaderWithValueFormat sets the value format for a key.

If is set to RemoteValueJSON, the key's value will be treated as JSON and configuration will be loaded from it.

If is set to RemoteValueYAML, the key's value will be treated as YAML and configuration will be loaded from it.

If is set to RemoteValuePlain, the key's value will be treated as plain content and configuration will contain the key and its plain value.

By default, is set to RemoteValuePlain.

type DefaultConfig

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

DefaultConfig is the default implementation for the Config contract. It is based on a Loader to retrieve configuration from. Is implements io.Closer and thus Close should be called at your application shutdown in order to avoid memory leaks.

Example
package main

import (
	"fmt"
	"time"

	"github.com/actforgood/xconf"
)

func main() {
	loader := xconf.NewMultiLoader(
		true,
		xconf.EnvLoader(),
		xconf.JSONFileLoader("testdata/config.json"),
	)
	cfg, err := xconf.NewDefaultConfig(
		loader,
		xconf.DefaultConfigWithIgnoreCaseSensitivity(),
		xconf.DefaultConfigWithReloadInterval(time.Minute),
	)
	if err != nil {
		panic(err)
	}
	defer cfg.Close()

	fmt.Println(cfg.Get("json_foo", "baz"))

}
Output:

bar

func NewDefaultConfig

func NewDefaultConfig(loader Loader, opts ...DefaultConfigOption) (*DefaultConfig, error)

NewDefaultConfig instantiates a new default config object. The first parameter is the loader used as a source of getting the key-value configuration map. The second parameter represents a list of optional functions to configure the object.

func (*DefaultConfig) Close

func (cfg *DefaultConfig) Close() error

Close stops the underlying ticker used to reload config, avoiding memory leaks. It should be called at your application shutdown. It implements io.Closer and the returned error can be disregarded (is nil all the time).

func (DefaultConfig) Get

func (cfg DefaultConfig) Get(key string, def ...any) any

Get returns a configuration value for a given key. The first parameter is the key to return the value for. The second parameter is optional, and represents a default value in case key is not found. It so has a role in inferring the type of key's value (if it exists) and thus key's value will be casted to default's value type. Only basic types (string, bool, int, uint, float, and their flavours), time.Duration, time.Time, []int, []string are covered. If a cast error occurs, the defaultValue is returned.

func (DefaultConfig) RegisterObserver

func (cfg DefaultConfig) RegisterObserver(observer ConfigObserver)

RegisterObserver adds a new observer that will get notified of keys changes.

type DefaultConfigOption

type DefaultConfigOption func(*DefaultConfig)

DefaultConfigOption defines optional function for configuring a DefaultConfig object.

func DefaultConfigWithIgnoreCaseSensitivity

func DefaultConfigWithIgnoreCaseSensitivity() DefaultConfigOption

DefaultConfigWithIgnoreCaseSensitivity disables case sensitivity for keys.

For example, if the configuration map contains a key "Foo", calling Get() with "foo" / "FOO" / etc. will return Foo's value.

Usage example:

cfg, err := xconf.NewDefaultConfig(loader, xconf.DefaultConfigWithIgnoreCaseSensitivity())
if err != nil {
	panic(err)
}
value1 := cfg.Get("foo")
value2 := cfg.Get("FOO")
value3 := cfg.Get("foO")
// all values are equal

func DefaultConfigWithReloadErrorHandler

func DefaultConfigWithReloadErrorHandler(errHandler func(error)) DefaultConfigOption

DefaultConfigWithReloadErrorHandler sets the handler for errors that may occur during reloading configuration, if DefaultConfigWithReloadInterval was applied. If reload fails, "old"/previous configuration is active.

You can choose to log the error, for example.

By default, error is simply ignored.

func DefaultConfigWithReloadInterval

func DefaultConfigWithReloadInterval(reloadInterval time.Duration) DefaultConfigOption

DefaultConfigWithReloadInterval sets interval to reload configuration. Passing a value <= 0 disables the config reload.

By default, configuration reload is disabled.

Usage example:

// enable config reload at an interval of 5 minutes:
cfg, err := xconf.NewDefaultConfig(loader, xconf.DefaultConfigWithReloadInterval(5 * time.Minute))

type EtcdLoader

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

EtcdLoader loads configuration from etcd. Close it if watcher option is enabled, in order to properly release resources.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	// load all keys starting with "APP_"
	hosts := []string{"127.0.0.1:2379"}
	loader := xconf.NewEtcdLoader(
		"APP_",
		xconf.EtcdLoaderWithEndpoints(hosts),
		xconf.EtcdLoaderWithPrefix(),
	)

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}
}
Output:

func NewEtcdLoader

func NewEtcdLoader(key string, opts ...EtcdLoaderOption) EtcdLoader

NewEtcdLoader instantiates a new EtcdLoader object that loads configuration from etcd.

func (EtcdLoader) Close

func (loader EtcdLoader) Close() error

Close needs to be called in case watch key changes were enabled. It releases associated resources.

func (EtcdLoader) Load

func (loader EtcdLoader) Load() (map[string]any, error)

Load returns a configuration key-value map from etcd, or an error if something bad happens along the process.

type EtcdLoaderOption

type EtcdLoaderOption func(*EtcdLoader)

EtcdLoaderOption defines optional function for configuring an Etcd Loader.

func EtcdLoaderWithAuth

func EtcdLoaderWithAuth(username, pwd string) EtcdLoaderOption

EtcdLoaderWithAuth sets the authentication username and password.

func EtcdLoaderWithContext

func EtcdLoaderWithContext(ctx context.Context) EtcdLoaderOption

EtcdLoaderWithContext sets request's context. By default, a context.Background() is used.

func EtcdLoaderWithEndpoints

func EtcdLoaderWithEndpoints(endpoints []string) EtcdLoaderOption

EtcdLoaderWithEndpoints sets the etcd host(s) for the client. By default, is set to "127.0.0.1:2379". Etcd hosts can also be set through ETCD_ENDPOINTS ENV (comma separated, if there is more than 1 ep).

func EtcdLoaderWithPrefix

func EtcdLoaderWithPrefix() EtcdLoaderOption

EtcdLoaderWithPrefix sets the WithPrefix() option on etcd client. The loaded key will be treated as a prefix, and thus all the keys having that prefix will be returned.

func EtcdLoaderWithTLS added in v1.5.0

func EtcdLoaderWithTLS(tlsCfg *tls.Config) EtcdLoaderOption

EtcdLoaderWithTLS sets the TLS configuration for secure communication between client and server.

func EtcdLoaderWithValueFormat

func EtcdLoaderWithValueFormat(valueFormat string) EtcdLoaderOption

EtcdLoaderWithValueFormat sets the value format for a key.

If is set to RemoteValueJSON, the key's value will be treated as JSON and configuration will be loaded from it.

If is set to RemoteValueYAML, the key's value will be treated as YAML and configuration will be loaded from it.

If is set to RemoteValuePlain, the key's value will be treated as plain content and configuration will contain the key and its plain value.

By default, is set to RemoteValuePlain.

func EtcdLoaderWithWatcher

func EtcdLoaderWithWatcher() EtcdLoaderOption

EtcdLoaderWithWatcher enables watch for keys changes. Use this if you intend to load configuration intensively, multiple times. If you plan to load configuration only once, or rarely, don't use this feature. If you use this feature, call Close() method on the loader to gracefully release resources (at your application shutdown).

type FileCacheLoader

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

FileCacheLoader decorates another "file" loader to load configuration only if the file was modified. If the file was not modified since the previous load, the file won't be read and parsed again. You can improve performance this way, if you plan to load configuration multiple times (like using it in DefaultConfig with reload enabled).

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	var (
		filePath = "testdata/config.json"
		loader   = xconf.NewFileCacheLoader(
			xconf.JSONFileLoader(filePath),
			filePath,
		)
		configMap map[string]any
		err       error
	)

	for i := 0; i < 3; i++ {
		// 1st time original loader will be called,
		// 2nd and 3rd time, config will be retrieved from cache.
		configMap, err = loader.Load()
		if err != nil {
			panic(err)
		}
	}

	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

json_foo: bar
json_year: 2022
json_temperature: 37.5
json_shopping_list: [bread milk eggs]

func NewFileCacheLoader

func NewFileCacheLoader(loader Loader, filePath string) FileCacheLoader

NewFileCacheLoader instantiates a new FileCacheLoader object that loads and caches the configuration from the original "file" loader. The second parameter should be the same file as the original loader's one.

func (FileCacheLoader) Load

func (decorator FileCacheLoader) Load() (map[string]any, error)

Load returns decorated loader's key-value configuration map. If the file was modified since last load, that file will be read and parsed again, if not, the previous, already processed, configuration map will be returned.

type FilterKV

type FilterKV interface {
	// IsAllowed returns true if a key-value is eligible to be returned
	// in the configuration map.
	IsAllowed(key string, value any) bool

	// Type returns filter's type (FilterTypeWhitelist / FilterTypeBlacklist).
	Type() FilterType
}

FilterKV is the contract for a key-value filter.

type FilterKVBlacklistFunc

type FilterKVBlacklistFunc func(key string, value any) bool

The FilterKVBlacklistFunc type is an adapter to allow the use of ordinary functions as FilterKV of "blacklist" type. If fn is a function with the appropriate signature, FilterKVBlacklistFunc(fn) is a FilterKV that calls fn and has type FilterTypeBlacklist. fn should return true if the KV is blacklisted.

Example:

xconf.FilterKVBlacklistFunc(func(key string, _ any) bool {
	return key == "DENY_ME_1" || key == "DENY_ME_2"
})

func (FilterKVBlacklistFunc) IsAllowed

func (filter FilterKVBlacklistFunc) IsAllowed(key string, value any) bool

IsAllowed returns false if a key-value is blacklisted.

func (FilterKVBlacklistFunc) Type

func (filter FilterKVBlacklistFunc) Type() FilterType

Type returns filter's type (FilterTypeBlacklist).

type FilterKVWhitelistFunc

type FilterKVWhitelistFunc func(key string, value any) bool

The FilterKVWhitelistFunc type is an adapter to allow the use of ordinary functions as FilterKV of "whitelist" type. If fn is a function with the appropriate signature, FilterKVWhitelistFunc(fn) is a FilterKV that calls fn of type FilterTypeWhitelist. fn should return true if the KV is whitelisted.

Example:

xconf.FilterKVWhitelistFunc(func(key string, _ any) bool {
	return key == "KEEP_ME_1" || key == "KEEP_ME_2"
})

func (FilterKVWhitelistFunc) IsAllowed

func (filter FilterKVWhitelistFunc) IsAllowed(key string, value any) bool

IsAllowed returns true if a key-value is whitelisted.

func (FilterKVWhitelistFunc) Type

func (filter FilterKVWhitelistFunc) Type() FilterType

Type returns filter's type (FilterTypeWhitelist).

type FilterType

type FilterType byte

FilterType is just an alias for byte.

const (
	// FilterTypeWhitelist represents a whitelist filter.
	FilterTypeWhitelist FilterType = 1
	// FilterTypeBlacklist represents a blacklist filter.
	FilterTypeBlacklist FilterType = 2
)

type FlattenLoader

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

FlattenLoader decorates another loader to add shortcuts to leaves' information in a nested configuration key.

Example, given the configuration:

{
  "mysql": {
    "host": "127.0.0.1",
    "port": 3306
  }
}

2 additional flat keys will be added to above standard configuration: "mysql.host", "mysql.port" for easy access of leaf-keys. Note: original nested configuration is still kept by default, if you want to remove it, apply FlattenLoaderWithFlatKeysOnly option.

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	jsonConfig := []byte(`{
	"db": {
		"mysql": {
			"host": "192.168.10.10",
			"port": 3306
		},
		"adapter": "mysql"
	},
	"foo": "bar"
}`)
	origLoader := xconf.JSONReaderLoader(bytes.NewReader(jsonConfig))
	loader := xconf.NewFlattenLoader(origLoader)

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}

	fmt.Println(configMap["foo"])
	fmt.Println(configMap["db"].(map[string]any)["mysql"].(map[string]any)["host"])
	fmt.Println(configMap["db.mysql.host"]) // much easier way to access information compared to previous statement.

}
Output:

bar
192.168.10.10
192.168.10.10

func NewFlattenLoader

func NewFlattenLoader(loader Loader, opts ...FlattenLoaderOption) FlattenLoader

NewFlattenLoader instantiates a new FlattenLoader object that adds flat version for nested keys for easily access.

func (FlattenLoader) Load

func (decorator FlattenLoader) Load() (map[string]any, error)

Load returns a configuration key-value map from original loader, enriched with shortcuts to leaves' information in nested configuration key(s).

type FlattenLoaderOption

type FlattenLoaderOption func(*FlattenLoader)

FlattenLoaderOption defines optional function for configuring a Flatten Loader.

func FlattenLoaderWithFlatKeysOnly

func FlattenLoaderWithFlatKeysOnly() FlattenLoaderOption

FlattenLoaderWithFlatKeysOnly triggers nested keys to be removed, and only their flat version to be kept.

func FlattenLoaderWithSeparator

func FlattenLoaderWithSeparator(keySeparator string) FlattenLoaderOption

FlattenLoaderWithSeparator sets the separator for the new, flat keys. By default, is set to "."(dot).

type IniFileLoader

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

IniFileLoader is a loader that returns configuration from an INI content based file.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	loader := xconf.NewIniFileLoader("testdata/config.ini")

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	fmt.Println(configMap["ini_foo"])
	fmt.Println(configMap["temperature"].(map[string]any)["ini_celsius"])
	fmt.Println(configMap["temperature"].(map[string]any)["ini_fahrenheit"])

}
Output:

bar
37.5
99.5

func NewIniFileLoader

func NewIniFileLoader(filePath string, opts ...IniFileLoaderOption) IniFileLoader

NewIniFileLoader instantiates a new IniFileLoader object that loads INI configuration from a file. The location of INI content based file is given as parameter.

func (IniFileLoader) Load

func (loader IniFileLoader) Load() (map[string]any, error)

Load returns a configuration key-value map from a INI file, or an error if something bad happens along the process.

type IniFileLoaderOption

type IniFileLoaderOption func(*IniFileLoader)

IniFileLoaderOption defines optional function for configuring an INI File Loader.

func IniFileLoaderWithLoadOptions

func IniFileLoaderWithLoadOptions(iniLoadOpts ini.LoadOptions) IniFileLoaderOption

IniFileLoaderWithLoadOptions sets given ini load options on the loader. By default, an empty object is used.

type KeyConflictError

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

KeyConflictError is an error returned by MultiLoader in case of a duplicate key. If key overwrite is allowed, this error will not be returned.

func NewKeyConflictError

func NewKeyConflictError(key string) KeyConflictError

NewKeyConflictError instantiates a new KeyConflictError. The duplicate key must be provided.

func (KeyConflictError) Error

func (e KeyConflictError) Error() string

Error returns string representation of the KeyConflictError. It implements standard go error interface.

type Loader

type Loader interface {
	// Load returns a configuration key value map or an error.
	//
	// It's Loader's responsibility to return a map that is safe for
	// an eventual later mutation (decorator pattern can be used to
	// modify a loader's returned configuration key value map and
	// that's why this must/should be accomplished safely; safely
	// from concurrency point of view / data integrity point of view;
	// in other words, Loader should return a disposable config map -
	// see also DeepCopyConfigMap utility and current usages as example).
	Load() (map[string]any, error)
}

Loader is responsible for loading a configuration key value map.

func AliasLoader

func AliasLoader(loader Loader, aliasKeyKey ...string) Loader

AliasLoader decorates another loader to set aliases for keys. The aliases will be added to decorated loader's configuration map. The second parameter represents a list of alias and keys they're for under the form "aliasForKey1, key1, aliasForKey2, key2".

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	origLoader := xconf.PlainLoader(map[string]any{
		"foo": "foo val",
		"bar": "bar val",
		"baz": "baz val",
	})
	loader := xconf.AliasLoader(
		origLoader,
		"NEW_FOO", "foo",
		"NEW_BAZ", "baz",
	)

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

foo: foo val
bar: bar val
baz: baz val
NEW_FOO: foo val
NEW_BAZ: baz val

func AlterValueLoader

func AlterValueLoader(loader Loader, transformation AlterValueFunc, keys ...string) Loader

AlterValueLoader decorates another loader to manipulate a config's value. The transformation function is applied to all passed keys.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	origLoader := xconf.PlainLoader(map[string]any{
		"foo":           "foo val",
		"bar":           100,
		"shopping_list": "bread,eggs,milk",
		"weekend_days":  "friday,saturday,sunday",
	})
	loader := xconf.AlterValueLoader(
		origLoader,
		xconf.ToStringList(","),
		"shopping_list", "weekend_days",
	)

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

foo: foo val
bar: 100
shopping_list: [bread eggs milk]
weekend_days: [friday saturday sunday]

func DotEnvFileLoader

func DotEnvFileLoader(filePath string) Loader

DotEnvFileLoader loads .env configuration from a file. The location of .env content based file is given as parameter.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	loader := xconf.DotEnvFileLoader("testdata/.env")

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

DOTENV_FOO: bar
DOTENV_YEAR: 2022
DOTENV_TEMPERATURE: 37.5
DOTENV_SHOPPING_LIST: bread,milk,eggs

func DotEnvReaderLoader

func DotEnvReaderLoader(reader io.Reader) Loader

DotEnvReaderLoader loads .env configuration from an io.Reader.

func EnvLoader

func EnvLoader() Loader

EnvLoader loads configuration from OS's ENV.

Example
package main

import (
	"crypto/rand"
	"fmt"
	"math/big"
	"os"
	"strconv"

	"github.com/actforgood/xconf"
)

// setUpEnv sets OS env with provided value.
// Returns the previous value, if env name already exists.
func setUpEnv(envName, value string) string {
	prevValue := os.Getenv(envName)
	_ = os.Setenv(envName, value)

	return prevValue
}

// tearDownEnv unsets the OS env provided or restores previous value.
func tearDownEnv(envName, prevValue string) {
	if prevValue != "" {
		_ = os.Setenv(envName, prevValue)
	} else {
		_ = os.Unsetenv(envName)
	}
}

// getRandomEnvName returns a "XCONF_TEST_ENV_LOADER_FOO_<randomInt>" env name.
func getRandomEnvName() string {
	nBig, err := rand.Int(rand.Reader, big.NewInt(9999999))
	if err != nil {
		return ""
	}
	randInt := nBig.Int64()

	return "XCONF_TEST_ENV_LOADER_FOO_" + strconv.FormatInt(randInt, 10)
}

func main() {
	// setup an env
	envName := getRandomEnvName()
	prevValue := setUpEnv(envName, "bar")
	defer tearDownEnv(envName, prevValue)

	loader := xconf.EnvLoader()

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	fmt.Println(configMap[envName])

}
Output:

bar

func FileLoader added in v1.3.0

func FileLoader(filePath string) Loader

FileLoader is a factory for appropriate XFileLoader based on file's extension. This is useful when you don't want to tie an application to a certain config format. Supported extensions are: .json, .yml, .yaml, .ini, .properties, .env, .toml.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	exampleFiles := []string{
		"testdata/config.json",
		"testdata/config.yaml",
		"testdata/config.yml",
		"testdata/.env",
		"testdata/config.ini",
		"testdata/config.properties",
		"testdata/config.toml",
	}
	for _, filePath := range exampleFiles {
		loader := xconf.FileLoader(filePath)
		configMap, err := loader.Load()
		if err != nil {
			panic(err)
		}
		fmt.Println(len(configMap))
	}

}
Output:

4
4
4
4
3
4
7

func FilterKVLoader

func FilterKVLoader(loader Loader, filters ...FilterKV) Loader

FilterKVLoader decorates another loader to whitelist/blacklist key-values.

A blacklist filter has more weight than a whitelist filter, as if a blacklist denies a KV and a whitelist allows it, that KV will not be returned in the configuration map.

If there are only whitelist filters, a KV will be returned into the configuration map if at least one filter allows it.

If there are only blacklist filters, a KV will be returned into the configuration map if no filter denies it.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	// in this example we assume our application's configs are
	// prefixed with APP_ and we want to allow them,
	// we also want to allow K8s services,
	// we also want to get rid of empty value configs.
	origLoader := xconf.PlainLoader(map[string]any{
		"APP_FOO_1":          "bar 1",     // whitelisted
		"APP_FOO_2":          "bar 2",     // whitelisted
		"APP_FOO_3":          "",          // blacklisted
		"REDIS_SERVICE_HOST": "10.0.0.11", // whitelisted
		"REDIS_SERVICE_PORT": "6379",      // whitelisted
		"MYSQL_SERVICE_HOST": "10.0.0.12", // whitelisted
		"MYSQL_SERVICE_PORT": "3306",      // whitelisted
		"OS":                 "darwin",
		"HOME":               "/Users/JohnDoe",
		"USER":               "JohnDoe",
	})
	loader := xconf.FilterKVLoader(
		origLoader,
		xconf.FilterKVWhitelistFunc(xconf.FilterKeyWithPrefix("APP_")),
		xconf.FilterKVWhitelistFunc(xconf.FilterKeyWithSuffix("_SERVICE_HOST")),
		xconf.FilterKVWhitelistFunc(xconf.FilterKeyWithSuffix("_SERVICE_PORT")),
		xconf.FilterKVBlacklistFunc(xconf.FilterEmptyValue),
	)

	configMap, _ := loader.Load()
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

APP_FOO_1: bar 1
APP_FOO_2: bar 2
REDIS_SERVICE_HOST: 10.0.0.11
REDIS_SERVICE_PORT: 6379
MYSQL_SERVICE_HOST: 10.0.0.12
MYSQL_SERVICE_PORT: 3306

func FlagSetLoader added in v1.5.0

func FlagSetLoader(flgSet *flag.FlagSet, visitAll ...bool) Loader

FlagSetLoader reduces flags to a configuration map. The first parameter is the flag.FlagSet holding flags. The second, optional, parameter indicates if all flags (even those not explicitly set) should be taken into consideration; by default, is true.

Example
package main

import (
	"flag"
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	// setup a test flag set
	flgSet := flag.NewFlagSet("example-flag-set", flag.ContinueOnError)
	_ = flgSet.String("flag_foo", "baz", "foo description")
	_ = flgSet.Int("flag_year", 2022, "year description")
	_ = flgSet.Float64("flag_temperature", 37.5, "temperature description")
	_ = flgSet.String("flag_shopping_list", "bread,milk,eggs", "shopping list description")
	args := []string{"-flag_foo=bar", "-flag_temperature=37.5"} // you will usually pass os.Args[1:] here
	if err := flgSet.Parse(args); err != nil {
		panic(err)
	}

	loader := xconf.FlagSetLoader(flgSet)
	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

flag_foo: bar
flag_year: 2022
flag_temperature: 37.5
flag_shopping_list: bread,milk,eggs

func IgnoreErrorLoader

func IgnoreErrorLoader(loader Loader, errs ...error) Loader

IgnoreErrorLoader decorates another loader to ignore the error returned by it, if error is present in the list of errors passed as second parameter. You can ignore, for example, os.ErrNotExist for a file based Loader if that file is not mandatory to exist, or Consul's ErrConsulKeyNotFound, etc.

Example
package main

import (
	"fmt"
	"os"

	"github.com/actforgood/xconf"
)

func main() {
	// in this example we assume we want to load configs from
	// a main source (OS Env for example - here we provide a PlainLoader)
	// and eventually from a JSON configuration file.
	loader := xconf.NewMultiLoader(
		true, // allow keys overwrite
		xconf.PlainLoader(map[string]any{
			"APP_FOO_1": "bar 1",
			"APP_FOO_2": "bar 2",
		}),
		xconf.IgnoreErrorLoader(
			xconf.JSONFileLoader("/this/path/might/not/exist/config.json"),
			os.ErrNotExist,
		),
	)

	configMap, _ := loader.Load()
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

APP_FOO_1: bar 1
APP_FOO_2: bar 2

func JSONFileLoader

func JSONFileLoader(filePath string) Loader

JSONFileLoader loads JSON configuration from a file. The location of JSON content based file is given as parameter.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	loader := xconf.JSONFileLoader("testdata/config.json")

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

json_foo: bar
json_year: 2022
json_temperature: 37.5
json_shopping_list: [bread milk eggs]

func JSONReaderLoader

func JSONReaderLoader(reader io.Reader) Loader

JSONReaderLoader loads JSON configuration from an io.Reader.

func PlainLoader

func PlainLoader(configMap map[string]any) Loader

PlainLoader is an explicit go configuration map retriever. It simply returns a copy of the given config map parameter.

It can be used for example:

- in a MultiLoader (with allowing keys overwrite) as the first loader in order to specify default configurations.

- to provide any application hardcoded configs.

func PropertiesBytesLoader

func PropertiesBytesLoader(propertiesContent []byte) Loader

PropertiesBytesLoader loads Properties configuration from bytes.

func PropertiesFileLoader

func PropertiesFileLoader(filePath string) Loader

PropertiesFileLoader loads Java Properties configuration from a file. The location of properties content based file is given as parameter.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	loader := xconf.PropertiesFileLoader("testdata/config.properties")

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

properties_foo: bar
properties_baz: bar
properties_year: 2022
properties_temperature: 37.5

func TOMLFileLoader added in v1.4.0

func TOMLFileLoader(filePath string) Loader

TOMLFileLoader loads TOML configuration from a file. The location of TOML content based file is given as parameter.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	loader := xconf.TOMLFileLoader("testdata/config.toml")

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	fmt.Println("toml_foo:", configMap["toml_foo"])
	fmt.Println("toml_year:", configMap["toml_year"])
	fmt.Println("toml_temperature:", configMap["toml_temperature"])
	fmt.Println("toml_shopping_list:", configMap["toml_shopping_list"])

}
Output:

toml_foo: bar
toml_year: 2022
toml_temperature: 37.5
toml_shopping_list: [bread milk eggs]

func TOMLReaderLoader added in v1.4.0

func TOMLReaderLoader(reader io.Reader) Loader

TOMLReaderLoader loads TOML configuration from an io.Reader.

func YAMLFileLoader

func YAMLFileLoader(filePath string) Loader

YAMLFileLoader loads YAML configuration from a file. The location of YAML content based file is given as parameter.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	loader := xconf.YAMLFileLoader("testdata/config.yaml")

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

yaml_foo: bar
yaml_year: 2022
yaml_temperature: 37.5
yaml_shopping_list: [bread milk eggs]

func YAMLReaderLoader

func YAMLReaderLoader(reader io.Reader) Loader

YAMLReaderLoader loads YAML configuration from an io.Reader.

type LoaderFunc

type LoaderFunc func() (map[string]any, error)

The LoaderFunc type is an adapter to allow the use of ordinary functions as Loaders. If fn is a function with the appropriate signature, LoaderFunc(fn) is a Loader that calls fn.

func (LoaderFunc) Load

func (fn LoaderFunc) Load() (map[string]any, error)

Load calls fn().

type MockConfig

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

MockConfig is a mock for xconf.Config contract, to be used in UT.

func NewMockConfig

func NewMockConfig(kv ...any) *MockConfig

NewMockConfig instantiates new mocked Config with given key-values configuration. Make sure you pass an even number of elements and that the keys are strings.

Usage example:

mock := xconf.NewMockConfig(
	"foo", "bar",
	"year", 2022,
)

func (*MockConfig) Get

func (mock *MockConfig) Get(key string, def ...any) any

Get mock logic.

func (*MockConfig) GetCallsCount

func (mock *MockConfig) GetCallsCount() int

GetCallsCount returns the no. of times Get() method was called.

func (*MockConfig) SetGetCallback

func (mock *MockConfig) SetGetCallback(callback func(key string, def ...any))

SetGetCallback sets the given callback to be executed inside Get() method. You can inject yourself to make assertions upon passed parameter(s) this way.

Usage example:

mock.SetGetCallback(func(key string, def ...any) {
	switch mock.GetCallsCount() {
	case 1:
		if key != "expectedKeyAtCall1" {
			t.Error("...")
		}
	case 2:
		if key != "expectedKeyAtCall2" {
			t.Error("...")
		}
	}
})

func (*MockConfig) SetKeyValues

func (mock *MockConfig) SetKeyValues(kv ...any)

SetKeyValues sets/resets given key-values. Make sure you pass an even number of elements and that the keys are strings.

type MultiLoader

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

MultiLoader is a composite loader that returns configurations from multiple loaders.

Example
package main

import (
	"fmt"

	"github.com/actforgood/xconf"
)

func main() {
	loader := xconf.NewMultiLoader(
		true, // allow key overwrite
		xconf.PlainLoader(map[string]any{
			"json_foo":  "bar from plain, will get overwritten",
			"yaml_foo":  "bar from plain, will get overwritten",
			"plain_key": "plain value",
		}),
		xconf.JSONFileLoader("testdata/config.json"),
		xconf.YAMLFileLoader("testdata/config.yaml"),
	)

	configMap, err := loader.Load()
	if err != nil {
		panic(err)
	}
	for key, value := range configMap {
		fmt.Println(key+":", value)
	}

}
Output:

json_foo: bar
json_year: 2022
json_temperature: 37.5
json_shopping_list: [bread milk eggs]
yaml_foo: bar
yaml_year: 2022
yaml_temperature: 37.5
yaml_shopping_list: [bread milk eggs]
plain_key: plain value

func NewMultiLoader

func NewMultiLoader(allowKeyOverwrite bool, loaders ...Loader) MultiLoader

NewMultiLoader instantiates a new MultiLoader object that loads and merges configuration from multiple loaders. The first parameter is a flag indicating whether a key is allowed to be overwritten, if found more than once. If not, a KeyConflictError will be returned. If yes, the order of loaders matters, meaning a later provided loader, will overwrite a previous provided loader's same found key. The rest of the parameters consist of the list of loaders configuration should be retrieved from.

func (MultiLoader) Load

func (loader MultiLoader) Load() (map[string]any, error)

Load returns a merged configuration key-value map of all encapsulated loaders, or an error if something bad happens along the process.

type NopConfig

type NopConfig struct{}

NopConfig is a no-operation xconf.Config.

func (NopConfig) Get

func (NopConfig) Get(_ string, def ...any) any

Get returns default value, if present, or nil.

Jump to

Keyboard shortcuts

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