Documentation ¶
Overview ¶
Package config implements a very opinionated config utility. It relies on a "default spec", i.e. a structure that defines all existing configuration keys, their types and their initial default values. This is used as fallback and source of validation. The idea is similar to python's configobj (albeit much smaller). Surprisingly I didn't find any similar library in Go.
Note that passing invalid keys to a few methods will cause a panic - on purpose. Using a wrong config key is seen as a bug and should be corrected immediately. This allows this package to skip error handling on Get() and Set() entirely.
In short: This config does a few things different than the ones I saw for Go. Instead of providing numerous possible sources and formats to save your config it simply relies on YAML out of the box. The focus is not on ultimate convinience but on:
- Providing meaningful validation and default values.
- Providing built-in documentation for all config values.
- Making it able to react on changed config values.
- Being usable from several go routines.
- In future: Provide an easy way to migrate configs.
Index ¶
- Constants
- Variables
- func DurationValidator() func(val interface{}) error
- func EnumValidator(options ...string) func(val interface{}) error
- func FloatRangeValidator(min, max float64) func(val interface{}) error
- func IntRangeValidator(min, max int64) func(val interface{}) error
- func ListValidator(fn func(val interface{}) error) func(val interface{}) error
- func MigrateKeys(oldCfg, newCfg *Config, fn func(key string, err error) error) error
- func ToYamlFile(path string, cfg *Config) error
- type Config
- func (cfg *Config) AddEvent(key string, fn func(key string)) int
- func (cfg *Config) Bool(key string) bool
- func (cfg *Config) Bools(key string) []bool
- func (cfg *Config) Cast(key, val string) (interface{}, error)
- func (cfg *Config) ClearEvents()
- func (cfg *Config) Duration(key string) time.Duration
- func (cfg *Config) Durations(key string) []time.Duration
- func (cfg *Config) Float(key string) float64
- func (cfg *Config) Floats(key string) []float64
- func (cfg *Config) Get(key string) interface{}
- func (cfg *Config) GetDefault(key string) DefaultEntry
- func (cfg *Config) Int(key string) int64
- func (cfg *Config) Ints(key string) []int64
- func (cfg *Config) IsDefault(key string) bool
- func (cfg *Config) IsValidKey(key string) bool
- func (cfg *Config) Keys() []string
- func (cfg *Config) Merge(other *Config) error
- func (cfg *Config) Reload(dec Decoder) error
- func (cfg *Config) RemoveEvent(id int)
- func (cfg *Config) Reset(key string) error
- func (cfg *Config) Save(enc Encoder) error
- func (cfg *Config) Section(section string) *Config
- func (cfg *Config) Set(key string, val interface{}) error
- func (cfg *Config) SetBool(key string, val bool) error
- func (cfg *Config) SetBools(key string, val []bool) error
- func (cfg *Config) SetDuration(key string, val time.Duration) error
- func (cfg *Config) SetDurations(key string, val []time.Duration) error
- func (cfg *Config) SetFloat(key string, val float64) error
- func (cfg *Config) SetFloats(key string, val []float64) error
- func (cfg *Config) SetInt(key string, val int64) error
- func (cfg *Config) SetInts(key string, val []int64) error
- func (cfg *Config) SetString(key string, val string) error
- func (cfg *Config) SetStrings(key string, val []string) error
- func (cfg *Config) String(key string) string
- func (cfg *Config) Strings(key string) []string
- func (cfg *Config) Uncast(key string) string
- func (cfg *Config) Version() Version
- type Decoder
- type DefaultEntry
- type DefaultMapping
- type Encoder
- type Migrater
- type Migration
- type Strictness
- type Version
Examples ¶
Constants ¶
const ( // StrictnessIgnore silently ignores any programmer error StrictnessIgnore = Strictness(iota) // StrictnessWarn will log a complaint via log.Println() StrictnessWarn // StrictnessPanic will panic when a programmer error was made. StrictnessPanic )
Variables ¶
var ( // ErrNotVersioned is returned by Migrate() when it can't find the version tag. // If that happens you can still try to Open() the config normally. ErrNotVersioned = errors.New("config has no valid version tag") )
Functions ¶
func DurationValidator ¶ added in v0.2.0
func DurationValidator() func(val interface{}) error
DurationValidator asserts that the config value is a valid duration that can be parsed by time.ParseDuration.
func EnumValidator ¶
EnumValidator checks if the supplied string value is in the `options` list.
func FloatRangeValidator ¶
FloatRangeValidator checks if the supplied float value lies in the inclusive boundaries of `min` and `max`.
func IntRangeValidator ¶
IntRangeValidator checks if the supplied integer value lies in the inclusive boundaries of `min` and `max`.
func ListValidator ¶ added in v0.2.0
ListValidator takes any other validator and applies it to a list value. If `fn` is nil it only checks if the value is indeed a list.
func MigrateKeys ¶
MigrateKeys is a helper function to write migrations easily. It takes the old and new config and copies all keys that are compatible (i.e. same key, same type). If calls fn() on any key that exists in the new config and not in the old config (i.e. new keys). If any error occurs during set (e.g. wrong type) fn is also called. If fn returns a non-nil error this method stops and returns the error.
func ToYamlFile ¶
ToYamlFile saves `cfg` as YAML at a file located at `path`.
Types ¶
type Config ¶
type Config struct {
// contains filtered or unexported fields
}
Config is a helper that is built around a representation defined by a Encoder/Decoder. It supports typed gets and sets, change notifications and basic validation with defaults.
func FromYamlFile ¶
func FromYamlFile(path string, defaults DefaultMapping, strictness Strictness) (*Config, error)
FromYamlFile creates a new config from the YAML file located at `path`
func Open ¶
func Open(dec Decoder, defaults DefaultMapping, strictness Strictness) (*Config, error)
Open creates a new config from the data in `r`. The mapping in `defaults ` tells the config which keys to expect and what type each of it should have. It is allowed to pass `nil` as decoder. In this case a config purely with default values will be created.
Example ¶
Basic usage example:
// You either open it via existing data - or in case of the initial write, // you just let it take over the defaults by passing nil as decoder. Open() // is also the step where the initial validation happens. If this // validation fails, an error is returned. cfg, err := Open(nil, ExampleDefaultsV0, StrictnessPanic) if err != nil { log.Fatalf("Failed to open config: %v", err) } // Fetching keys is easy now and requires no error handling: cfg.String("backend.name") // -> the_good_one cfg.Int("backend.workers") // -> 10 cfg.Bool("ui.show_tooltips") // -> true // You can set also set keys: This one will return an error though because, // "the_great_one" is no valid enum value (see defaults) Note that using a // bad key is considered a programming error and will cause a panic. This // is on purpose and might seem radical, but should help you to catch // errors early in the development. cfg.SetString("backend.name", "the_great_one") // If you'd like to print an overview over all config keys, // you can just get a list of all default entries: for _, key := range cfg.Keys() { entry := cfg.GetDefault(key) fmt.Printf("%s: %s (restart: %t)\n", key, entry.Docs, entry.NeedsRestart) } // If you have only a string (e.g. from a cmdline config set), // you can ask the config to convert it to the right type: // Note that you need to check if it's an valid key, otherwise cfg.Set() might panic. alienKey, alienVal := "backend.workers", "15" if cfg.IsValidKey(alienKey) { safeVal, err := cfg.Cast(alienKey, alienVal) if err != nil { log.Fatalf("Uh, oh, could not cast key to the right type: %v", err) } // safeVal is an integer now: cfg.Set(alienKey, safeVal) } // Want to know if something changed? // Just register a callback for it. If you pass an empty string, // you'll get callbacks for every set. The respective key is then // passed to the callback. cid := cfg.AddEvent("backend.workers", func(key string) { fmt.Println("Key was changed:", key) }) // You can get rid of callbacks too of course: cfg.RemoveEvent(cid) // One nifty feature is to pass only a sub section of the config // to specific parts of the program - Which saves you from typing // the full keys and disallowing them to remove other parts. backendCfg := cfg.Section("backend") backendCfg.Get("name") // -> the_good_one // When you're done, you can always serialize the config // and save it wherever you like. buf := &bytes.Buffer{} if err := cfg.Save(NewYamlEncoder(buf)); err != nil { log.Fatalf("Failed to save config: %v", err) } fmt.Println(buf.String())
Output: backend.name: Choose what backend you want to use. (restart: true) backend.workers: How many workers to start. (restart: false) ui.show_tooltips: Show tooltips when you least expect them (restart: false) # version: 0 (DO NOT MODIFY THIS LINE) backend: name: the_good_one workers: 15 ui: show_tooltips: true
func (*Config) AddEvent ¶
AddEvent registers a callback to be called when `key` is changed. Special case: if key is the empy string, the registered callback will get called for every change (with the respective key) This function supports registering several callbacks for the same `key`. The returned id can be used to unregister a callback with RemoveEvent() Note: This function will panic when using an invalid key.
func (*Config) Bool ¶
Bool returns the boolean value (or default) at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used. If an error happens it will return the zero value.
func (*Config) Bools ¶ added in v0.2.0
Bools returns the boolean list value (or default) at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used. If an error happens it will return the zero value.
func (*Config) Cast ¶
Cast takes `val` and reads the type of `key`. It then tries to convert it to one of the supported types (and possibly fails due to that)
This cast assumes that `val` is always a string, which is useful for data coming fom the client. Note: This function will panic if the key does not exist.
func (*Config) ClearEvents ¶
func (cfg *Config) ClearEvents()
ClearEvents removes all registered events.
func (*Config) Duration ¶ added in v0.2.0
Duration returns the duration value (or default) at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used. If an error happens it will return the zero value.
func (*Config) Durations ¶ added in v0.2.0
Durations returns the duration value (or default) at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used. If an error happens it will return the zero value.
func (*Config) Float ¶
Float returns the float value (or default) at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used. If an error happens it will return the zero value.
func (*Config) Floats ¶ added in v0.2.0
Floats returns the float list value (or default) at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used. If an error happens it will return the zero value.
func (*Config) Get ¶
Get returns the raw value at `key`. Do not use this method when possible, use the typeed convinience methods. Note: This function might panic when they key does not exist and StrictnessPanic is used. If an error happens it will return the zero value.
func (*Config) GetDefault ¶
func (cfg *Config) GetDefault(key string) DefaultEntry
GetDefault retrieves the default for a certain key. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) Int ¶
Int returns the int value (or default) at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used. If an error happens it will return the zero value.
func (*Config) Ints ¶ added in v0.2.0
Ints returns the int list value (or default) at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used. If an error happens it will return the zero value.
func (*Config) IsDefault ¶ added in v0.2.0
IsDefault will return true if this key was not explicitly set, but taken over from the defaults. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) IsValidKey ¶
IsValidKey can be checked to see if untrusted keys actually are valid. It should not be used to check keys from string literals.
func (*Config) Merge ¶ added in v0.2.0
Merge takes all values from `other` that were set explicitly and sets them in `cfg`. If any key changes, the respective event callback will be called.
func (*Config) Reload ¶
Reload re-sets all values in the config to the data in `dec`. If `dec` is nil, all default values will be returned. All keys that changed will trigger a signal, if registered.
Note that you cannot pass different defaults on Reload, since this might alter the structure of the config, potentially causing incompatibillies. Use the migration interface if you really need to change the layout.
func (*Config) RemoveEvent ¶
RemoveEvent removes a previously registered callback.
func (*Config) Reset ¶ added in v0.2.0
Reset reverts a key or a section to its defaults. If key points to a value, only this value is reset. If key points to a section, all keys in it are reset. If key is an empty string, the whole config is reset to defaults.
When calling reset on parts of the config that includes __many__ sections, those will be totally cleared and won't be serialized when calling Save(). Retrieving values from those will yield default values as if they were not set.
func (*Config) Save ¶
Save will write a representation defined by `enc` of the current config to `w`.
func (*Config) Set ¶
Set creates or sets the `val` at `key`. Please only use this function only if you have an interface{} that you do not want to cast yourself. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) SetBool ¶
SetBool creates or sets the `val` at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) SetBools ¶ added in v0.2.0
SetBools creates or sets the `val` at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) SetDuration ¶ added in v0.2.0
SetDuration creates or sets the `val` at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) SetDurations ¶ added in v0.2.0
SetDurations creates or sets the `val` at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) SetFloat ¶
SetFloat creates or sets the `val` at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) SetFloats ¶ added in v0.2.0
SetFloats creates or sets the `val` at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) SetInt ¶
SetInt creates or sets the `val` at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) SetInts ¶ added in v0.2.0
SetInts creates or sets the `val` at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) SetString ¶
SetString creates or sets the `val` at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) SetStrings ¶ added in v0.2.0
SetStrings creates or sets the `val` at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used.
func (*Config) String ¶
String returns the string value (or default) at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used. If an error happens it will return the zero value.
func (*Config) Strings ¶ added in v0.2.0
Strings returns the string list value (or default) at `key`. Note: This function might panic when they key does not exist and StrictnessPanic is used. If an error happens it will return the zero value.
func (*Config) Uncast ¶ added in v0.2.0
Uncast is the opposite of Cast. It works like Get(), but always returns a stringified version of a value, even in case of slices. The representation returned by Uncast() can be later read in again by using Cast().
Cast() and Uncast() are mainly useful in systems where you can only use strings, e.g. when building an API between different programming languages. Note: Slice items are separated by " ;; ".
type Decoder ¶
type Decoder interface { // Decode takes a byte stream from when the decoder was created // and parses the version and the internal representation out of it. Decode() (Version, map[interface{}]interface{}, error) }
Decoder defines how a byte stream can be parsed to a config
func NewYamlDecoder ¶
NewYamlDecoder creates a new Decoder that parses the data in `r`. It will look at the first line of the input to get the version.
type DefaultEntry ¶
type DefaultEntry struct { // Default is the fallback value for this config key. // The confg type will be inferred from its literal type. Default interface{} // NeedsRestart indicates that we need to restart the daemon // to have an effect here. NeedsRestart bool // Docs describes the meaning of the configuration value. Docs string // Function that can be used to check Validator func(val interface{}) error }
DefaultEntry represents the metadata for a default value in the config. Every possible key has to have a DefaultEntry, otherwise Get() and Set() will panic at you since this is considered a programmer error.
type DefaultMapping ¶
type DefaultMapping map[interface{}]interface{}
DefaultMapping is a container to hold all required DefaultEntries. It is a nested map with sections as string keys.
type Encoder ¶
type Encoder interface { // Encode takes the version and the internal config representation // and turns it to a byte stream that was passed when creatin the encoder. Encode(version Version, data map[interface{}]interface{}) error }
Encoder defines how the config can be serialized to a byte stream
func NewYamlEncoder ¶
NewYamlEncoder creates a new Encoder that writes a YAML file with the config data. The file will start with a comment indicating the version, so pay attention to not remove it by accident.
type Migrater ¶
type Migrater struct {
// contains filtered or unexported fields
}
Migrater is a factory for creating version'd configs. See NewMigrater() for more details.
Example ¶
// This config package optionally supports versioned configs. // Whenever you decide to change the layout of the config, // you can bump the version and register a new migration func // that will be run over older config upon opening them. mgr := NewMigrater(CurrentVersion, StrictnessPanic) // Add a migration - the first one for version "0" has no func attached. mgr.Add(0, nil, ExampleDefaultsV0) // For version "1" we gonna need a function that transforms the config: migrateToV1 := func(oldCfg, newCfg *Config) error { // Use the helpful MigrateKeys method to migrate most of the keys. // It will call you back on every missing key or any errors. return MigrateKeys(oldCfg, newCfg, func(key string, err error) error { switch key { case "backend.accuracy": // Do something based on the old config key: return newCfg.SetFloat( "backend.accuracy", float64(oldCfg.Int("backend.workers"))+0.5, ) default: return fmt.Errorf("Incomplete migration for key: %v", key) } }) } // Add it with the new respective results: mgr.Add(1, migrateToV1, ExampleDefaultsV1) rawConfig := `# version: 0 ui: show_tooltips: true backend: name: the_good_one workers: 10 ` // The Migrate call works like a factory method. // It creates the config in a versioned way: cfg, err := mgr.Migrate(NewYamlDecoder(strings.NewReader(rawConfig))) if err != nil { // Handle errors... } cfg.Version() // -> 1 now. // If you print it, you will notice a changed version tag: buf := &bytes.Buffer{} cfg.Save(NewYamlEncoder(buf)) fmt.Println(buf.String())
Output: # version: 1 (DO NOT MODIFY THIS LINE) backend: accuracy: 10.5 name: the_good_one ui: show_tooltips: true
func NewMigrater ¶
func NewMigrater(currentVersion Version, strictness Strictness) *Migrater
NewMigrater returns a new Migrater.
It can be seen as an migration capable version of config.Open(). Instead of directly passing the defaults you register a number of migrations (each with their own migration func, defaults and version). The actual work is done by the migration functions which are written by the caller of this API. The caller defined migration method will likely call MigrateKeys() though.
Call Migrate() on the migrater will read the current version and try to migrate to the most recent one.
type Migration ¶
Migration is a function that is executed to replicate the changes between to versions. It should modify `newCfg` in a way so all keys from `oldCfg` that were portable are transferred.
type Strictness ¶ added in v0.2.0
type Strictness int
Strictness defines how the API of the config reacts when it thinks that the programmer made a mistake. These kind of mistakes usually fall into one of the following categories:
- A wrong key was used for a Get(), Set() etc. - The wrong type getter was used for a certain key (bool for a string e.g.) - The defaults are faulty (wrong types, __many__ in the wrong place etc.)
All of those errors should be catched early in the development and should not make it to the user. Therefore StrictnessPanic is recommended for developing. When building the software in release mode, one might want to switch to StrictnessWarn, which just logs when it found something awful.