Skip to content

Instantly share code, notes, and snippets.

@Raynos
Created February 14, 2017 03:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Raynos/9c1134ed514cf32353f703a057be98de to your computer and use it in GitHub Desktop.
Save Raynos/9c1134ed514cf32353f703a057be98de to your computer and use it in GitHub Desktop.
package staticConfig
import (
"io/ioutil"
"reflect"
"encoding/json"
"github.com/buger/jsonparser"
"github.com/pkg/errors"
)
// TODO: func Inspect() map[string]interface{}
// ConfigValue represents a json serialized string.
type ConfigValue struct {
bytes []byte
dataType jsonparser.ValueType
}
// StaticConfig allows accessing values of out of json config files
type StaticConfig struct {
seedConfig map[string]interface{}
files []string
configValues map[string]ConfigValue
frozen bool
destroyed bool
}
// NewConfig allocates a static config instance
// StaticConfig takes two args, files and seedConfig.
//
// files is required and must be a list of file paths.
//
// The seedConfig is optional and will be used to overwrite
// configuration key value pairs if present.
//
// The files must be a list of JSON files. Each file must be a flat object of
// key, value pairs. It's recommended that you use keys like:
//
// {
// "name": "my-name",
// "clients.thingy": {
// "some client": "config"
// },
// "server.my-port": 9999
// }
//
// To organize your configuration file.
func NewConfig(
files []string, seedConfig map[string]interface{},
) *StaticConfig {
config := &StaticConfig{
files: files,
seedConfig: seedConfig,
}
config.initializeConfigValues()
return config
}
// GetBoolean returns the value as a boolean or panics.
func (conf *StaticConfig) GetBoolean(key string) bool {
if value, contains := conf.seedConfig[key]; contains {
return value.(bool)
}
if value, contains := conf.configValues[key]; contains {
v, err := jsonparser.GetBoolean(value.bytes)
if err != nil {
panic(errors.Wrapf(err, "Key (%s) is wrong type: ", key))
}
return v
}
panic(errors.Errorf("Key (%s) not available", key))
}
// GetFloat returns the value as a float or panics.
func (conf *StaticConfig) GetFloat(key string) float64 {
if value, contains := conf.seedConfig[key]; contains {
return value.(float64)
}
if value, contains := conf.configValues[key]; contains {
v, err := jsonparser.GetFloat(value.bytes)
if err != nil {
panic(errors.Wrapf(err, "Key (%s) is wrong type: ", key))
}
return v
}
panic(errors.Errorf("Key (%s) not available", key))
}
// GetInt returns the value as a float or panics.
func (conf *StaticConfig) GetInt(key string) int64 {
if value, contains := conf.seedConfig[key]; contains {
return value.(int64)
}
if value, contains := conf.configValues[key]; contains {
v, err := jsonparser.GetInt(value.bytes)
if err != nil {
panic(errors.Wrapf(err, "Key (%s) is wrong type: ", key))
}
return v
}
panic(errors.Errorf("Key (%s) not available", key))
}
// GetString returns the value as a float or panics.
func (conf *StaticConfig) GetString(key string) string {
if value, contains := conf.seedConfig[key]; contains {
return value.(string)
}
if value, contains := conf.configValues[key]; contains {
v, err := jsonparser.GetString(value.bytes)
if err != nil {
panic(errors.Wrapf(err, "Key (%s) is wrong type: ", key))
}
return v
}
panic(errors.Errorf("Key (%s) not available", key))
}
// GetStruct reads the value into an interface{} or panics.
// Recommended that this is used with pointers to structs
// GetStruct() will call json.Unmarshal(bytes, ptr) under the hood.
func (conf *StaticConfig) GetStruct(key string, ptr interface{}) {
if v, contains := conf.seedConfig[key]; contains {
rv := reflect.ValueOf(ptr)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
panic(errors.Errorf("Cannot GetStruct (%s) into nil ptr", key))
}
rv.Set(reflect.ValueOf(v))
return
}
if v, contains := conf.configValues[key]; contains {
err := json.Unmarshal(v.bytes, ptr)
if err != nil {
panic(errors.Wrapf(err, "Key (%s) is wrong type: ", key))
}
return
}
panic(errors.Errorf("Key (%s) not available", key))
}
// Set a value in the config, useful for tests.
// Keys you set must not exist in the JSON files
// Set() will panic if the key exists or if frozen.
// Strongly recommended not to be used for production code.
func (conf *StaticConfig) Set(key string, value interface{}) {
if conf.frozen {
panic(errors.Errorf("Cannot set(%s) because frozen", key))
}
if _, contains := conf.configValues[key]; contains {
panic(errors.Errorf("Key (%s) already exists", key))
}
if _, contains := conf.seedConfig[key]; contains {
panic(errors.Errorf("Key (%s) already exists", key))
}
conf.seedConfig[key] = value
}
// Freeze the configuration store.
// Once you freeze the config any further calls to config.set() will panic.
// This allows you to make the static config immutable
func (conf *StaticConfig) Freeze() {
conf.frozen = true
}
// Destroy will make Get() calls fail with a panic.
// This allows you to terminate the configuration phase and gives you
// confidence that your application is now officially bootstrapped.
func (conf *StaticConfig) Destroy() {
conf.frozen = true
conf.configValues = map[string]ConfigValue{}
conf.seedConfig = map[string]interface{}{}
}
func (conf *StaticConfig) initializeConfigValues() {
values := conf.collectConfigMaps()
conf.assignConfigValues(values)
}
func (conf *StaticConfig) collectConfigMaps() []map[string]ConfigValue {
var maps = []map[string]ConfigValue{}
for i := 0; i < len(conf.files); i++ {
fileObject := conf.parseFile(conf.files[i])
if fileObject != nil {
maps = append(maps, fileObject)
}
}
return maps
}
func (conf *StaticConfig) assignConfigValues(values []map[string]ConfigValue) {
for i := 0; i < len(values); i++ {
configObject := values[i]
for key, value := range configObject {
conf.configValues[key] = value
}
}
}
func (conf *StaticConfig) parseFile(fileName string) map[string]ConfigValue {
bytes, err := ioutil.ReadFile(fileName)
if err != nil {
// Ignore missing files
return nil
}
var object = map[string]ConfigValue{}
err = jsonparser.ObjectEach(bytes, func(
key []byte,
value []byte,
dataType jsonparser.ValueType,
offset int,
) error {
object[string(key)] = ConfigValue{
bytes: value,
dataType: dataType,
}
return nil
})
if err != nil {
// If the JSON is not valid then just panic out.
panic(err)
}
return object
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment