safing-portbase/config/layers.go
2018-08-14 16:01:09 +02:00

230 lines
5 KiB
Go

package config
import (
"errors"
"sync"
"fmt"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
var (
configLock sync.RWMutex
userConfig = ""
defaultConfig = ""
// ErrInvalidJSON is returned by SetConfig and SetDefaultConfig if they receive invalid json.
ErrInvalidJSON = errors.New("json string invalid")
// ErrInvalidOptionType is returned by SetConfigOption and SetDefaultConfigOption if given an unsupported option type.
ErrInvalidOptionType = errors.New("invalid option value type")
)
// SetConfig sets the (prioritized) user defined config.
func SetConfig(json string) error {
if !gjson.Valid(json) {
return ErrInvalidJSON
}
configLock.Lock()
defer configLock.Unlock()
userConfig = json
resetValidityFlag()
return nil
}
// SetDefaultConfig sets the (fallback) default config.
func SetDefaultConfig(json string) error {
if !gjson.Valid(json) {
return ErrInvalidJSON
}
configLock.Lock()
defer configLock.Unlock()
defaultConfig = json
resetValidityFlag()
return nil
}
func validateValue(name string, value interface{}) error {
optionsLock.RLock()
defer optionsLock.RUnlock()
option, ok := options[name]
if !ok {
switch value.(type) {
case string:
return nil
case []string:
return nil
case int:
return nil
case bool:
return nil
default:
return ErrInvalidOptionType
}
}
switch v := value.(type) {
case string:
if option.OptType != OptTypeString {
return fmt.Errorf("expected type string for option %s, got type %T", name, v)
}
if option.compiledRegex != nil {
if !option.compiledRegex.MatchString(v) {
return fmt.Errorf("validation failed: string \"%s\" did not match regex for option %s", v, name)
}
}
return nil
case []string:
if option.OptType != OptTypeStringArray {
return fmt.Errorf("expected type string for option %s, got type %T", name, v)
}
if option.compiledRegex != nil {
for pos, entry := range v {
if !option.compiledRegex.MatchString(entry) {
return fmt.Errorf("validation failed: string \"%s\" at index %d did not match regex for option %s", entry, pos, name)
}
}
}
return nil
case int:
if option.OptType != OptTypeInt {
return fmt.Errorf("expected type int for option %s, got type %T", name, v)
}
return nil
case bool:
if option.OptType != OptTypeBool {
return fmt.Errorf("expected type bool for option %s, got type %T", name, v)
}
return nil
default:
return ErrInvalidOptionType
}
}
// SetConfigOption sets a single value in the (prioritized) user defined config.
func SetConfigOption(name string, value interface{}) error {
configLock.Lock()
defer configLock.Unlock()
var err error
var newConfig string
if value == nil {
newConfig, err = sjson.Delete(userConfig, name)
} else {
err = validateValue(name, value)
if err == nil {
newConfig, err = sjson.Set(userConfig, name, value)
}
}
if err == nil {
userConfig = newConfig
resetValidityFlag()
}
return err
}
// SetDefaultConfigOption sets a single value in the (fallback) default config.
func SetDefaultConfigOption(name string, value interface{}) error {
configLock.Lock()
defer configLock.Unlock()
var err error
var newConfig string
if value == nil {
newConfig, err = sjson.Delete(defaultConfig, name)
} else {
err = validateValue(name, value)
if err == nil {
newConfig, err = sjson.Set(defaultConfig, name, value)
}
}
if err == nil {
defaultConfig = newConfig
resetValidityFlag()
}
return err
}
// findValue find the correct value in the user or default config.
func findValue(name string) (result gjson.Result) {
configLock.RLock()
defer configLock.RUnlock()
result = gjson.Get(userConfig, name)
if !result.Exists() {
result = gjson.Get(defaultConfig, name)
}
return result
}
// findStringValue validates and returns the value with the given name.
func findStringValue(name string, fallback string) (value string) {
result := findValue(name)
if !result.Exists() {
return fallback
}
if result.Type != gjson.String {
return fallback
}
return result.String()
}
// findStringArrayValue validates and returns the value with the given name.
func findStringArrayValue(name string, fallback []string) (value []string) {
result := findValue(name)
if !result.Exists() {
return fallback
}
if !result.IsArray() {
return fallback
}
results := result.Array()
for _, r := range results {
if r.Type != gjson.String {
return fallback
}
value = append(value, r.String())
}
return value
}
// findIntValue validates and returns the value with the given name.
func findIntValue(name string, fallback int64) (value int64) {
result := findValue(name)
if !result.Exists() {
return fallback
}
if result.Type != gjson.Number {
return fallback
}
return result.Int()
}
// findBoolValue validates and returns the value with the given name.
func findBoolValue(name string, fallback bool) (value bool) {
result := findValue(name)
if !result.Exists() {
return fallback
}
switch result.Type {
case gjson.True:
return true
case gjson.False:
return false
default:
return fallback
}
}