configr

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jan 23, 2016 License: MIT Imports: 8 Imported by: 4

README

configr

Build Status Coverage Status GoDoc

Configr provides an abstraction above configuration sources, allowing you to use a single interface to expect and get all your configuration values.

Features:

  • Single interface for configuration values: Simple API (Get(), String(), Bool()...)
  • Extendable config sources: Load config from a file, database, environmental variables or any source you can get data from
  • Multiple source support: Add as many sources as you can manage, FILO merge strategy employed (first source added has highest priority)
  • Nested Key Support: production.payment_gateway.public_key production.payment_gateway.private_key
  • Value validation support: Any matching key from every source is validated by your custom validators
  • Required keys support: Ensure keys exist after parsing, otherwise error out
  • Blank config generator: Register as many keys as you need and use the blank config generator
  • Custom blank config encoder support: Implement an encoder for any data format and have a blank config generated in it
  • Type conversion support: Your config has string "5" but you want an int 5? No problem
  • Comes pre-baked with JSON and TOML file support
  • Satisfies github.com/yourheropaul/inj:Datasource: Allows you to bypass the manual wiring of config values to struct properties (see below)

Built for a project at HomeMade Digital, configrs primary goal was to eliminate user error when deploying projects with heavy configuration needs. The inclusion of required key support, value validators, descriptions and blank config generator allowed us to reduce pain for seperated client ops teams when deploying our apps. Our secondary goal was flexible configuration sources be it pulling from Mongo Document, DynamoDB Table, JSON or TOML files.

Example

Simple JSON File

Pre register some keys to expect from your configuration sources (typically via init() for small projects, or via your own object initiation system for larger projects):

	configr.RequireKey("email.fromAddress", "Email from address")
	configr.RequireKey("email.subject", "Email subject")
	configr.RegisterKey("email.retryOnFail", "Retry sending email if it fails", false)
	configr.RegisterKey("email.maxRetries", "How many times to retry email resending", 3)

Create some configuration:

{
	"email": {
		"fromAddress": "my@email.com",
		"subject": "A Subject",
		"retryOnFail": true,
		"maxRetries": 5
	}
}

Add a source:

	configr.AddSource(configr.NewFileSource("/tmp/config.json"))

Parse your config:

	if err := configr.Parse(); err != nil {
		...
	}

And use at your own leisure:

	fromAddress, err := configr.String("email.fromAddress")
	if err != nil {
		...
	}
Inj Datasource

Continuing from the simple JSON example above, you can use http://github.com/yourheropaul/inj to auto-wire in your configuration values, bypassing much of the typical config wiring boilerplate:

Pre register keys:

	configr.RequireKey("email.fromAddress", "Email from address")
	configr.RequireKey("email.subject", "Email subject")
	configr.RegisterKey("email.retryOnFail", "Retry sending email if it fails", false)
	configr.RegisterKey("email.maxRetries", "How many times to retry email resending", 3)

Add the relevant inj struct tags with their corresponding key paths:

type Email struct {
	FromAddress string `inj:"email.fromAddress"`
	Subject     string `inj:"email.subject"`
	MaxRetries  int    `inj:"email.maxRetries"`
	RetryOnFail bool   `inj:"email.retryOnFail"`
}

Add and setup your source (assume we're using the same config json as above):

	configr.AddSource(configr.NewFileSource("/tmp/config.json"))

Parse your config:

	if err := configr.Parse(); err != nil {
		...
	}

Setup inj with configr as its Datasource and commence the magic:

	email := Email{}
	inj.Provide(&email) // Informs inj to perform DI on given instance
	inj.AddDatasource(configr.GetConfigr()) // Provides inj with a datasource to query

	if valid, errors := inj.Assert(); !valid { // Triggers the inj DI process
		...
	}

Marvel at the ease of auto-wiring:

	fmt.Println("> Email Address:", email.FromAddress)
	fmt.Println("> Subject:", email.Subject)
	fmt.Println("> Max Retries:", email.MaxRetries)
	fmt.Println("> Retry on Fail:", email.RetryOnFail)
> Email Address: my@email.com
> Subject: A Subject
> Max Retries: 5
> Retry on Fail: true

More examples can be found in the examples/ dir.

Changes

v0.3.0

  • File source now supports registering encoders/decoders at a distance, check out the json and toml packages for examples
  • API Change NewFileSource() -> NewFile()

v0.2.0

  • Add support for inj datasource

TODO:

  • Concurrent safety, particularly in multi Parse()'ing systems and when adding sources (will allow for hot reloads)
  • FileSource needs to be refactored to reduce dependency needs, something similar to sql package with a central register and blank importing the flavour you need
  • More available sources, Env vars, Flags... etc
  • Decide wether or not to ditch errors on the key getter methods (String, Get, Bool...). Alternative solution is to provide a 'Errored() bool' and 'Errors() []error or chan error' methods to Config interface. Arguments for: - Simpler interface when all you want is values Arguments against: - Error swallowing, decoupling of cause and effect (try to fetch key that cannot be converted to type ("aaa" -> Int()), user never checks configr for errors, system starts behaving weirdly) - Internal error managing will get funky in a concurrent environment, would have to use an error channel to pump the errors into, wouldn't be able to guarentee ordering or sacrafice performance for co-ordination
  • Wrap validation errors
  • Provide all primary types as getter methods
  • Add 'Keys' method to Source interface to accept keys and key name splitting func as parameters, provides keys for lookup for Sources that don't have 'scan' style interfaces, and potential performance improvements

Documentation

Overview

Configr provides an abstraction above configuration sources, allowing you to use a single interface to get all your configuration values

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrKeyNotFound          = errors.New("configr: Key not found")
	ErrParseHasntBeenCalled = errors.New("configr: Trying to get values before calling Parse()")
	ErrNoRegisteredValues   = errors.New("configr: No registered values to generate")
)
View Source
var (
	RegisteredFileEncoders = make(map[string]Encoder)
	RegisteredFileDecoders = make(map[string]FileDecoder)
	ExtensionToDecoderName = make(map[string]string)
	ExtensionToEncoderName = make(map[string]string)

	ErrUnknownEncoding = errors.New("configr: Unable to determine file encoding, please set manually")
)

Functions

func AddSource

func AddSource(p Source)

AddSource registers Sources with the Configr instance to Unmarshal() when Parse() is called. Sources are parsed in a FILO order, meaning the first source added is considered the highest priority, and any keys from lower priority sources that are present in a higher will be overwritten

func Bool

func Bool(key string) (bool, error)

Bool wraps Get() and will attempt to cast the resulting value to a bool or error

func Float64

func Float64(key string) (float64, error)

Float64 wraps Get() and will attempt to cast the resulting value to a float64 or error

func GenerateBlank

func GenerateBlank(e Encoder) ([]byte, error)

GenerateBlank generates a 'blank' configuration using the passed Encoder, it will honour nested keys, use default values where possible and when not fall back to placing the description as the value.

func Get

func Get(key string) (interface{}, error)

Get can only be called after a Parse() has been done. Keys support the nested notation format:

"user.age.month"

If a key is not found but has been registered with a default, the default will be returned

func Int

func Int(key string) (int, error)

Int wraps Get() and will attempt to cast the resulting value to a int or error

func MustParse

func MustParse()

MustParse wraps Parse() and will panic if there are any resulting errors

func Parse

func Parse() error

Parse calls Unmarshal on all registered sources, and caches the subsequent key/value's. Additional calls to Parse can be made to add additional config from sources.

Sources are called in a FILO order, meaning the first source added is considered the highest priority, any keys set from lower priority sources found in higher priority will be overwritten.

func Parsed

func Parsed() bool

Parsed lets the caller know if a Parse() call has been made or not

func RegisterFileDecoder added in v0.3.0

func RegisterFileDecoder(name string, source FileDecoder, fileExtensions ...string)

func RegisterFileEncoder added in v0.3.0

func RegisterFileEncoder(name string, encoder Encoder, fileExtensions ...string)

func RegisterKey

func RegisterKey(name, description string, defaultVal interface{}, validators ...Validator)

RegisterKey registers a configuration key (name) along with a description of what the configuration key is for, a default value and optional validators

name supports nested notation in the form of '.' delimitered keys (unless changed) e.g.

"user.age.month"

func RequireKey

func RequireKey(name, description string, validators ...Validator)

RequireValue wraps the RegisterValue() call but upon parsing sources, if the configuration key (name) is not found, Parse() will return a ErrRequiredValuesMissing error

func SetConfigr

func SetConfigr(c *Configr)

func String

func String(key string) (string, error)

String wraps Get() and will attempt to cast the resulting value to a string or error

Types

type Config

type Config interface {
	Parse() error
	Parsed() bool
	MustParse()

	Get(string) (interface{}, error)

	String(string) (string, error)
	Bool(string) (bool, error)
	Int(string) (int, error)
	Float64(string) (float64, error)
}

type Configr

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

func GetConfigr

func GetConfigr() *Configr

func New

func New() *Configr

func (*Configr) AddSource

func (c *Configr) AddSource(p Source)

func (*Configr) Bool

func (c *Configr) Bool(key string) (bool, error)

func (*Configr) Float64

func (c *Configr) Float64(key string) (float64, error)

func (*Configr) GenerateBlank

func (c *Configr) GenerateBlank(e Encoder) ([]byte, error)

func (*Configr) Get

func (c *Configr) Get(key string) (interface{}, error)

func (*Configr) Int

func (c *Configr) Int(key string) (int, error)

func (*Configr) MustParse

func (c *Configr) MustParse()

func (*Configr) Parse

func (c *Configr) Parse() error

func (*Configr) Parsed

func (c *Configr) Parsed() bool

func (*Configr) Read added in v0.2.0

func (c *Configr) Read(key string) (interface{}, error)

Satisfies github.com/yourheropaul/inj:DatasourceReader interface, not intended for regular configr usage. Use `configr.Get(string)` instead.

func (*Configr) RegisterKey

func (c *Configr) RegisterKey(name, description string, defaultVal interface{}, validators ...Validator)

func (*Configr) RequireKey

func (c *Configr) RequireKey(name, description string, validators ...Validator)

func (*Configr) SetDescriptionWrapper

func (c *Configr) SetDescriptionWrapper(wrapper string)

func (*Configr) SetIsCaseSensitive

func (c *Configr) SetIsCaseSensitive(isCaseSensitive bool)

func (*Configr) SetKeyPathDelimeter

func (c *Configr) SetKeyPathDelimeter(delimeter string)

func (*Configr) String

func (c *Configr) String(key string) (string, error)

type Encoder

type Encoder interface {
	Marshal(interface{}) ([]byte, error)
}

Encoder would be used to encode registered and required values (along with their defaults or descriptions) into bytes.

type EncoderAdapter added in v0.2.0

type EncoderAdapter func(interface{}) ([]byte, error)

EncoderAdapter allows you to convert a func:

func(interface{}) ([]byte, error)

into a type that satisfies the Encoder interface

func (EncoderAdapter) Marshal added in v0.2.0

func (f EncoderAdapter) Marshal(v interface{}) ([]byte, error)

type ErrRequiredKeysMissing

type ErrRequiredKeysMissing []string

func (ErrRequiredKeysMissing) Error

func (e ErrRequiredKeysMissing) Error() string

type File added in v0.3.0

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

func NewFile added in v0.3.0

func NewFile(path string) *File

func (*File) Marshal added in v0.3.0

func (f *File) Marshal(v interface{}) ([]byte, error)

func (*File) Path added in v0.3.0

func (f *File) Path() string

func (*File) SetEncodingName added in v0.3.0

func (f *File) SetEncodingName(name string)

func (*File) SetPath added in v0.3.0

func (f *File) SetPath(path string)

func (*File) Unmarshal added in v0.3.0

func (f *File) Unmarshal() (map[string]interface{}, error)

type FileDecoder added in v0.3.0

type FileDecoder interface {
	Unmarshal([]byte, interface{}) error
}

type FileDecoderAdapter added in v0.3.0

type FileDecoderAdapter func([]byte, interface{}) error

func (FileDecoderAdapter) Unmarshal added in v0.3.0

func (f FileDecoderAdapter) Unmarshal(b []byte, v interface{}) error

type Manager

type Manager interface {
	RegisterKey(string, string, interface{}, ...Validator)
	RequireKey(string, string, ...Validator)

	AddSource(Source)

	GenerateBlank(Encoder) ([]byte, error)
	SetIsCaseSensitive(bool)
}

type Source

type Source interface {
	Unmarshal() (map[string]interface{}, error)
}

Source is a source of configuration keys and values, calling unmarshal should return a map[string]interface{} of all key/value pairs (nesting is supported) with multiple types.

type SourceAdapter

type SourceAdapter func() (map[string]interface{}, error)

SourceAdapter allows you to convert a func:

func() (map[string]interface{}, error)

into a type that satisfies the Source interface

func (SourceAdapter) Unmarshal

func (f SourceAdapter) Unmarshal() (map[string]interface{}, error)

type Validator

type Validator func(interface{}) error

Validator is a validation function which would be coupled with a configuration key, anytime the config key is found in a Source it's value is validated.

Directories

Path Synopsis
examples
sources

Jump to

Keyboard shortcuts

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