Created
February 14, 2017 03:38
-
-
Save Raynos/9c1134ed514cf32353f703a057be98de to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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