mirror of
https://github.com/safing/portbase
synced 2025-09-01 10:09:50 +00:00
Add perspective, improve getters
This commit is contained in:
parent
be456c9d64
commit
e349fb4f5b
11 changed files with 583 additions and 230 deletions
|
@ -5,6 +5,8 @@ package config
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/tevino/abool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Expertise Level constants
|
// Expertise Level constants
|
||||||
|
@ -21,7 +23,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
expertiseLevel *int32
|
expertiseLevel *int32
|
||||||
|
expertiseLevelOption *Option
|
||||||
|
expertiseLevelOptionFlag = abool.New()
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -32,7 +36,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerExpertiseLevelOption() {
|
func registerExpertiseLevelOption() {
|
||||||
err := Register(&Option{
|
expertiseLevelOption = &Option{
|
||||||
Name: "Expertise Level",
|
Name: "Expertise Level",
|
||||||
Key: expertiseLevelKey,
|
Key: expertiseLevelKey,
|
||||||
Description: "The Expertise Level controls the perceived complexity. Higher settings will show you more complex settings and information. This might also affect various other things relying on this setting. Modified settings in higher expertise levels stay in effect when switching back. (Unlike the Release Level)",
|
Description: "The Expertise Level controls the perceived complexity. Higher settings will show you more complex settings and information. This might also affect various other things relying on this setting. Modified settings in higher expertise levels stay in effect when switching back. (Unlike the Release Level)",
|
||||||
|
@ -46,15 +50,31 @@ func registerExpertiseLevelOption() {
|
||||||
|
|
||||||
ExternalOptType: "string list",
|
ExternalOptType: "string list",
|
||||||
ValidationRegex: fmt.Sprintf("^(%s|%s|%s)$", ExpertiseLevelNameUser, ExpertiseLevelNameExpert, ExpertiseLevelNameDeveloper),
|
ValidationRegex: fmt.Sprintf("^(%s|%s|%s)$", ExpertiseLevelNameUser, ExpertiseLevelNameExpert, ExpertiseLevelNameDeveloper),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
err := Register(expertiseLevelOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expertiseLevelOptionFlag.Set()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateExpertiseLevel() {
|
func updateExpertiseLevel() {
|
||||||
new := findStringValue(expertiseLevelKey, "")
|
// check if already registered
|
||||||
switch new {
|
if !expertiseLevelOptionFlag.IsSet() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// get value
|
||||||
|
value := expertiseLevelOption.activeFallbackValue
|
||||||
|
if expertiseLevelOption.activeValue != nil {
|
||||||
|
value = expertiseLevelOption.activeValue
|
||||||
|
}
|
||||||
|
if expertiseLevelOption.activeDefaultValue != nil {
|
||||||
|
value = expertiseLevelOption.activeDefaultValue
|
||||||
|
}
|
||||||
|
// set atomic value
|
||||||
|
switch value.stringVal {
|
||||||
case ExpertiseLevelNameUser:
|
case ExpertiseLevelNameUser:
|
||||||
atomic.StoreInt32(expertiseLevel, int32(ExpertiseLevelUser))
|
atomic.StoreInt32(expertiseLevel, int32(ExpertiseLevelUser))
|
||||||
case ExpertiseLevelNameExpert:
|
case ExpertiseLevelNameExpert:
|
||||||
|
|
|
@ -11,15 +11,25 @@ var (
|
||||||
|
|
||||||
// GetAsString returns a function that returns the wanted string with high performance.
|
// GetAsString returns a function that returns the wanted string with high performance.
|
||||||
func (cs *safe) GetAsString(name string, fallback string) StringOption {
|
func (cs *safe) GetAsString(name string, fallback string) StringOption {
|
||||||
valid := getValidityFlag()
|
valid := GetValidityFlag()
|
||||||
value := findStringValue(name, fallback)
|
option, valueCache := getValueCache(name, nil, OptTypeString)
|
||||||
|
value := fallback
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.stringVal
|
||||||
|
}
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
|
|
||||||
return func() string {
|
return func() string {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
if !valid.IsSet() {
|
if !valid.IsSet() {
|
||||||
valid = getValidityFlag()
|
valid = GetValidityFlag()
|
||||||
value = findStringValue(name, fallback)
|
option, valueCache = getValueCache(name, option, OptTypeString)
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.stringVal
|
||||||
|
} else {
|
||||||
|
value = fallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
@ -27,15 +37,25 @@ func (cs *safe) GetAsString(name string, fallback string) StringOption {
|
||||||
|
|
||||||
// GetAsStringArray returns a function that returns the wanted string with high performance.
|
// GetAsStringArray returns a function that returns the wanted string with high performance.
|
||||||
func (cs *safe) GetAsStringArray(name string, fallback []string) StringArrayOption {
|
func (cs *safe) GetAsStringArray(name string, fallback []string) StringArrayOption {
|
||||||
valid := getValidityFlag()
|
valid := GetValidityFlag()
|
||||||
value := findStringArrayValue(name, fallback)
|
option, valueCache := getValueCache(name, nil, OptTypeStringArray)
|
||||||
|
value := fallback
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.stringArrayVal
|
||||||
|
}
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
|
|
||||||
return func() []string {
|
return func() []string {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
if !valid.IsSet() {
|
if !valid.IsSet() {
|
||||||
valid = getValidityFlag()
|
valid = GetValidityFlag()
|
||||||
value = findStringArrayValue(name, fallback)
|
option, valueCache = getValueCache(name, option, OptTypeStringArray)
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.stringArrayVal
|
||||||
|
} else {
|
||||||
|
value = fallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
@ -43,15 +63,25 @@ func (cs *safe) GetAsStringArray(name string, fallback []string) StringArrayOpti
|
||||||
|
|
||||||
// GetAsInt returns a function that returns the wanted int with high performance.
|
// GetAsInt returns a function that returns the wanted int with high performance.
|
||||||
func (cs *safe) GetAsInt(name string, fallback int64) IntOption {
|
func (cs *safe) GetAsInt(name string, fallback int64) IntOption {
|
||||||
valid := getValidityFlag()
|
valid := GetValidityFlag()
|
||||||
value := findIntValue(name, fallback)
|
option, valueCache := getValueCache(name, nil, OptTypeInt)
|
||||||
|
value := fallback
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.intVal
|
||||||
|
}
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
|
|
||||||
return func() int64 {
|
return func() int64 {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
if !valid.IsSet() {
|
if !valid.IsSet() {
|
||||||
valid = getValidityFlag()
|
valid = GetValidityFlag()
|
||||||
value = findIntValue(name, fallback)
|
option, valueCache = getValueCache(name, option, OptTypeInt)
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.intVal
|
||||||
|
} else {
|
||||||
|
value = fallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
@ -59,15 +89,25 @@ func (cs *safe) GetAsInt(name string, fallback int64) IntOption {
|
||||||
|
|
||||||
// GetAsBool returns a function that returns the wanted int with high performance.
|
// GetAsBool returns a function that returns the wanted int with high performance.
|
||||||
func (cs *safe) GetAsBool(name string, fallback bool) BoolOption {
|
func (cs *safe) GetAsBool(name string, fallback bool) BoolOption {
|
||||||
valid := getValidityFlag()
|
valid := GetValidityFlag()
|
||||||
value := findBoolValue(name, fallback)
|
option, valueCache := getValueCache(name, nil, OptTypeBool)
|
||||||
|
value := fallback
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.boolVal
|
||||||
|
}
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
|
|
||||||
return func() bool {
|
return func() bool {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
if !valid.IsSet() {
|
if !valid.IsSet() {
|
||||||
valid = getValidityFlag()
|
valid = GetValidityFlag()
|
||||||
value = findBoolValue(name, fallback)
|
option, valueCache = getValueCache(name, option, OptTypeBool)
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.boolVal
|
||||||
|
} else {
|
||||||
|
value = fallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
204
config/get.go
204
config/get.go
|
@ -15,14 +15,59 @@ type (
|
||||||
BoolOption func() bool
|
BoolOption func() bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getValueCache(name string, option *Option, requestedType uint8) (*Option, *valueCache) {
|
||||||
|
// get option
|
||||||
|
if option == nil {
|
||||||
|
var ok bool
|
||||||
|
optionsLock.RLock()
|
||||||
|
option, ok = options[name]
|
||||||
|
optionsLock.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
log.Errorf("config: request for unregistered option: %s", name)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check type
|
||||||
|
if requestedType != option.OptType {
|
||||||
|
log.Errorf("config: bad type: requested %s as %s, but is %s", name, getTypeName(requestedType), getTypeName(option.OptType))
|
||||||
|
return option, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock option
|
||||||
|
option.Lock()
|
||||||
|
defer option.Unlock()
|
||||||
|
|
||||||
|
// check release level
|
||||||
|
if option.ReleaseLevel <= getReleaseLevel() && option.activeValue != nil {
|
||||||
|
return option, option.activeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.activeDefaultValue != nil {
|
||||||
|
return option, option.activeDefaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return option, option.activeFallbackValue
|
||||||
|
}
|
||||||
|
|
||||||
// GetAsString returns a function that returns the wanted string with high performance.
|
// GetAsString returns a function that returns the wanted string with high performance.
|
||||||
func GetAsString(name string, fallback string) StringOption {
|
func GetAsString(name string, fallback string) StringOption {
|
||||||
valid := getValidityFlag()
|
valid := GetValidityFlag()
|
||||||
value := findStringValue(name, fallback)
|
option, valueCache := getValueCache(name, nil, OptTypeString)
|
||||||
|
value := fallback
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.stringVal
|
||||||
|
}
|
||||||
|
|
||||||
return func() string {
|
return func() string {
|
||||||
if !valid.IsSet() {
|
if !valid.IsSet() {
|
||||||
valid = getValidityFlag()
|
valid = GetValidityFlag()
|
||||||
value = findStringValue(name, fallback)
|
option, valueCache = getValueCache(name, option, OptTypeString)
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.stringVal
|
||||||
|
} else {
|
||||||
|
value = fallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
@ -30,12 +75,22 @@ func GetAsString(name string, fallback string) StringOption {
|
||||||
|
|
||||||
// GetAsStringArray returns a function that returns the wanted string with high performance.
|
// GetAsStringArray returns a function that returns the wanted string with high performance.
|
||||||
func GetAsStringArray(name string, fallback []string) StringArrayOption {
|
func GetAsStringArray(name string, fallback []string) StringArrayOption {
|
||||||
valid := getValidityFlag()
|
valid := GetValidityFlag()
|
||||||
value := findStringArrayValue(name, fallback)
|
option, valueCache := getValueCache(name, nil, OptTypeStringArray)
|
||||||
|
value := fallback
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.stringArrayVal
|
||||||
|
}
|
||||||
|
|
||||||
return func() []string {
|
return func() []string {
|
||||||
if !valid.IsSet() {
|
if !valid.IsSet() {
|
||||||
valid = getValidityFlag()
|
valid = GetValidityFlag()
|
||||||
value = findStringArrayValue(name, fallback)
|
option, valueCache = getValueCache(name, option, OptTypeStringArray)
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.stringArrayVal
|
||||||
|
} else {
|
||||||
|
value = fallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
@ -43,12 +98,22 @@ func GetAsStringArray(name string, fallback []string) StringArrayOption {
|
||||||
|
|
||||||
// GetAsInt returns a function that returns the wanted int with high performance.
|
// GetAsInt returns a function that returns the wanted int with high performance.
|
||||||
func GetAsInt(name string, fallback int64) IntOption {
|
func GetAsInt(name string, fallback int64) IntOption {
|
||||||
valid := getValidityFlag()
|
valid := GetValidityFlag()
|
||||||
value := findIntValue(name, fallback)
|
option, valueCache := getValueCache(name, nil, OptTypeInt)
|
||||||
|
value := fallback
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.intVal
|
||||||
|
}
|
||||||
|
|
||||||
return func() int64 {
|
return func() int64 {
|
||||||
if !valid.IsSet() {
|
if !valid.IsSet() {
|
||||||
valid = getValidityFlag()
|
valid = GetValidityFlag()
|
||||||
value = findIntValue(name, fallback)
|
option, valueCache = getValueCache(name, option, OptTypeInt)
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.intVal
|
||||||
|
} else {
|
||||||
|
value = fallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
@ -56,19 +121,29 @@ func GetAsInt(name string, fallback int64) IntOption {
|
||||||
|
|
||||||
// GetAsBool returns a function that returns the wanted int with high performance.
|
// GetAsBool returns a function that returns the wanted int with high performance.
|
||||||
func GetAsBool(name string, fallback bool) BoolOption {
|
func GetAsBool(name string, fallback bool) BoolOption {
|
||||||
valid := getValidityFlag()
|
valid := GetValidityFlag()
|
||||||
value := findBoolValue(name, fallback)
|
option, valueCache := getValueCache(name, nil, OptTypeBool)
|
||||||
|
value := fallback
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.boolVal
|
||||||
|
}
|
||||||
|
|
||||||
return func() bool {
|
return func() bool {
|
||||||
if !valid.IsSet() {
|
if !valid.IsSet() {
|
||||||
valid = getValidityFlag()
|
valid = GetValidityFlag()
|
||||||
value = findBoolValue(name, fallback)
|
option, valueCache = getValueCache(name, option, OptTypeBool)
|
||||||
|
if valueCache != nil {
|
||||||
|
value = valueCache.boolVal
|
||||||
|
} else {
|
||||||
|
value = fallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// findValue find the correct value in the user or default config.
|
/*
|
||||||
func findValue(key string) interface{} {
|
func getAndFindValue(key string) interface{} {
|
||||||
optionsLock.RLock()
|
optionsLock.RLock()
|
||||||
option, ok := options[key]
|
option, ok := options[key]
|
||||||
optionsLock.RUnlock()
|
optionsLock.RUnlock()
|
||||||
|
@ -77,6 +152,13 @@ func findValue(key string) interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return option.findValue()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
// findValue finds the preferred value in the user or default config.
|
||||||
|
func (option *Option) findValue() interface{} {
|
||||||
// lock option
|
// lock option
|
||||||
option.Lock()
|
option.Lock()
|
||||||
defer option.Unlock()
|
defer option.Unlock()
|
||||||
|
@ -91,88 +173,4 @@ func findValue(key string) interface{} {
|
||||||
|
|
||||||
return option.DefaultValue
|
return option.DefaultValue
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
// findStringValue validates and returns the value with the given key.
|
|
||||||
func findStringValue(key string, fallback string) (value string) {
|
|
||||||
result := findValue(key)
|
|
||||||
if result == nil {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
v, ok := result.(string)
|
|
||||||
if ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
// findStringArrayValue validates and returns the value with the given key.
|
|
||||||
func findStringArrayValue(key string, fallback []string) (value []string) {
|
|
||||||
result := findValue(key)
|
|
||||||
if result == nil {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
v, ok := result.([]interface{})
|
|
||||||
if ok {
|
|
||||||
new := make([]string, len(v))
|
|
||||||
for i, val := range v {
|
|
||||||
s, ok := val.(string)
|
|
||||||
if ok {
|
|
||||||
new[i] = s
|
|
||||||
} else {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new
|
|
||||||
}
|
|
||||||
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
// findIntValue validates and returns the value with the given key.
|
|
||||||
func findIntValue(key string, fallback int64) (value int64) {
|
|
||||||
result := findValue(key)
|
|
||||||
if result == nil {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
switch v := result.(type) {
|
|
||||||
case int:
|
|
||||||
return int64(v)
|
|
||||||
case int8:
|
|
||||||
return int64(v)
|
|
||||||
case int16:
|
|
||||||
return int64(v)
|
|
||||||
case int32:
|
|
||||||
return int64(v)
|
|
||||||
case int64:
|
|
||||||
return v
|
|
||||||
case uint:
|
|
||||||
return int64(v)
|
|
||||||
case uint8:
|
|
||||||
return int64(v)
|
|
||||||
case uint16:
|
|
||||||
return int64(v)
|
|
||||||
case uint32:
|
|
||||||
return int64(v)
|
|
||||||
case uint64:
|
|
||||||
return int64(v)
|
|
||||||
case float32:
|
|
||||||
return int64(v)
|
|
||||||
case float64:
|
|
||||||
return int64(v)
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
// findBoolValue validates and returns the value with the given key.
|
|
||||||
func findBoolValue(key string, fallback bool) (value bool) {
|
|
||||||
result := findValue(key)
|
|
||||||
if result == nil {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
v, ok := result.(bool)
|
|
||||||
if ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/safing/portbase/log"
|
"github.com/safing/portbase/log"
|
||||||
|
@ -39,7 +40,7 @@ func quickRegister(t *testing.T, key string, optType uint8, defaultValue interfa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) { //nolint:gocognit
|
||||||
// reset
|
// reset
|
||||||
options = make(map[string]*Option)
|
options = make(map[string]*Option)
|
||||||
|
|
||||||
|
@ -48,41 +49,41 @@ func TestGet(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
quickRegister(t, "monkey", OptTypeInt, -1)
|
quickRegister(t, "monkey", OptTypeString, "c")
|
||||||
quickRegister(t, "zebras/zebra", OptTypeStringArray, []string{"a", "b"})
|
quickRegister(t, "zebras/zebra", OptTypeStringArray, []string{"a", "b"})
|
||||||
quickRegister(t, "elephant", OptTypeInt, -1)
|
quickRegister(t, "elephant", OptTypeInt, -1)
|
||||||
quickRegister(t, "hot", OptTypeBool, false)
|
quickRegister(t, "hot", OptTypeBool, false)
|
||||||
quickRegister(t, "cold", OptTypeBool, true)
|
quickRegister(t, "cold", OptTypeBool, true)
|
||||||
|
|
||||||
err = parseAndSetConfig(`
|
err = parseAndSetConfig(`
|
||||||
{
|
{
|
||||||
"monkey": "1",
|
"monkey": "a",
|
||||||
"zebras": {
|
"zebras": {
|
||||||
"zebra": ["black", "white"]
|
"zebra": ["black", "white"]
|
||||||
},
|
},
|
||||||
"elephant": 2,
|
"elephant": 2,
|
||||||
"hot": true,
|
"hot": true,
|
||||||
"cold": false
|
"cold": false
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = parseAndSetDefaultConfig(`
|
err = parseAndSetDefaultConfig(`
|
||||||
{
|
{
|
||||||
"monkey": "0",
|
"monkey": "b",
|
||||||
"snake": "0",
|
"snake": "0",
|
||||||
"elephant": 0
|
"elephant": 0
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
monkey := GetAsString("monkey", "none")
|
monkey := GetAsString("monkey", "none")
|
||||||
if monkey() != "1" {
|
if monkey() != "a" {
|
||||||
t.Errorf("monkey should be 1, is %s", monkey())
|
t.Errorf("monkey should be a, is %s", monkey())
|
||||||
}
|
}
|
||||||
|
|
||||||
zebra := GetAsStringArray("zebras/zebra", []string{})
|
zebra := GetAsStringArray("zebras/zebra", []string{})
|
||||||
|
@ -106,10 +107,10 @@ func TestGet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = parseAndSetConfig(`
|
err = parseAndSetConfig(`
|
||||||
{
|
{
|
||||||
"monkey": "3"
|
"monkey": "3"
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -131,6 +132,53 @@ func TestGet(t *testing.T) {
|
||||||
GetAsInt("elephant", -1)()
|
GetAsInt("elephant", -1)()
|
||||||
GetAsBool("hot", false)()
|
GetAsBool("hot", false)()
|
||||||
|
|
||||||
|
// perspective
|
||||||
|
|
||||||
|
// load data
|
||||||
|
pLoaded := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal([]byte(`{
|
||||||
|
"monkey": "a",
|
||||||
|
"zebras": {
|
||||||
|
"zebra": ["black", "white"]
|
||||||
|
},
|
||||||
|
"elephant": 2,
|
||||||
|
"hot": true,
|
||||||
|
"cold": false
|
||||||
|
}`), &pLoaded)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create
|
||||||
|
p, err := NewPerspective(pLoaded)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
monkeyVal, ok := p.GetAsString("monkey")
|
||||||
|
if !ok || monkeyVal != "a" {
|
||||||
|
t.Errorf("[perspective] monkey should be a, is %+v", monkeyVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
zebraVal, ok := p.GetAsStringArray("zebras/zebra")
|
||||||
|
if !ok || len(zebraVal) != 2 || zebraVal[0] != "black" || zebraVal[1] != "white" {
|
||||||
|
t.Errorf("[perspective] zebra should be [\"black\", \"white\"], is %+v", zebraVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
elephantVal, ok := p.GetAsInt("elephant")
|
||||||
|
if !ok || elephantVal != 2 {
|
||||||
|
t.Errorf("[perspective] elephant should be 2, is %+v", elephantVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
hotVal, ok := p.GetAsBool("hot")
|
||||||
|
if !ok || !hotVal {
|
||||||
|
t.Errorf("[perspective] hot should be true, is %+v", hotVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
coldVal, ok := p.GetAsBool("cold")
|
||||||
|
if !ok || coldVal {
|
||||||
|
t.Errorf("[perspective] cold should be false, is %+v", coldVal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReleaseLevel(t *testing.T) {
|
func TestReleaseLevel(t *testing.T) {
|
||||||
|
@ -236,11 +284,9 @@ func BenchmarkGetAsStringCached(b *testing.B) {
|
||||||
options = make(map[string]*Option)
|
options = make(map[string]*Option)
|
||||||
|
|
||||||
// Setup
|
// Setup
|
||||||
err := parseAndSetConfig(`
|
err := parseAndSetConfig(`{
|
||||||
{
|
"monkey": "banana"
|
||||||
"monkey": "banana"
|
}`)
|
||||||
}
|
|
||||||
`)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -257,11 +303,9 @@ func BenchmarkGetAsStringCached(b *testing.B) {
|
||||||
|
|
||||||
func BenchmarkGetAsStringRefetch(b *testing.B) {
|
func BenchmarkGetAsStringRefetch(b *testing.B) {
|
||||||
// Setup
|
// Setup
|
||||||
err := parseAndSetConfig(`
|
err := parseAndSetConfig(`{
|
||||||
{
|
"monkey": "banana"
|
||||||
"monkey": "banana"
|
}`)
|
||||||
}
|
|
||||||
`)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -271,38 +315,34 @@ func BenchmarkGetAsStringRefetch(b *testing.B) {
|
||||||
|
|
||||||
// Start benchmark
|
// Start benchmark
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
findStringValue("monkey", "no banana")
|
getValueCache("monkey", nil, OptTypeString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkGetAsIntCached(b *testing.B) {
|
func BenchmarkGetAsIntCached(b *testing.B) {
|
||||||
// Setup
|
// Setup
|
||||||
err := parseAndSetConfig(`
|
err := parseAndSetConfig(`{
|
||||||
{
|
"elephant": 1
|
||||||
"monkey": 1
|
}`)
|
||||||
}
|
|
||||||
`)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
monkey := GetAsInt("monkey", -1)
|
elephant := GetAsInt("elephant", -1)
|
||||||
|
|
||||||
// Reset timer for precise results
|
// Reset timer for precise results
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
// Start benchmark
|
// Start benchmark
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
monkey()
|
elephant()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkGetAsIntRefetch(b *testing.B) {
|
func BenchmarkGetAsIntRefetch(b *testing.B) {
|
||||||
// Setup
|
// Setup
|
||||||
err := parseAndSetConfig(`
|
err := parseAndSetConfig(`{
|
||||||
{
|
"elephant": 1
|
||||||
"monkey": 1
|
}`)
|
||||||
}
|
|
||||||
`)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -312,6 +352,6 @@ func BenchmarkGetAsIntRefetch(b *testing.B) {
|
||||||
|
|
||||||
// Start benchmark
|
// Start benchmark
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
findIntValue("monkey", 1)
|
getValueCache("elephant", nil, OptTypeInt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ type Option struct {
|
||||||
Name string
|
Name string
|
||||||
Key string // in path format: category/sub/key
|
Key string // in path format: category/sub/key
|
||||||
Description string
|
Description string
|
||||||
|
Help string
|
||||||
|
|
||||||
OptType uint8
|
OptType uint8
|
||||||
ExpertiseLevel uint8
|
ExpertiseLevel uint8
|
||||||
|
@ -52,9 +53,10 @@ type Option struct {
|
||||||
ExternalOptType string
|
ExternalOptType string
|
||||||
ValidationRegex string
|
ValidationRegex string
|
||||||
|
|
||||||
activeValue interface{} // runtime value (loaded from config file or set by user)
|
activeValue *valueCache // runtime value (loaded from config file or set by user)
|
||||||
activeDefaultValue interface{} // runtime default value (may be set internally)
|
activeDefaultValue *valueCache // runtime default value (may be set internally)
|
||||||
compiledRegex *regexp.Regexp
|
activeFallbackValue *valueCache // default value from option registration
|
||||||
|
compiledRegex *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export expors an option to a Record.
|
// Export expors an option to a Record.
|
||||||
|
|
128
config/perspective.go
Normal file
128
config/perspective.go
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Perspective is a view on configuration data without interfering with the configuration system.
|
||||||
|
type Perspective struct {
|
||||||
|
config map[string]*perspectiveOption
|
||||||
|
}
|
||||||
|
|
||||||
|
type perspectiveOption struct {
|
||||||
|
option *Option
|
||||||
|
valueCache *valueCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPerspective parses the given config and returns it as a new perspective.
|
||||||
|
func NewPerspective(config map[string]interface{}) (*Perspective, error) {
|
||||||
|
// flatten config structure
|
||||||
|
flatten(config, config, "")
|
||||||
|
|
||||||
|
perspective := &Perspective{
|
||||||
|
config: make(map[string]*perspectiveOption),
|
||||||
|
}
|
||||||
|
var firstErr error
|
||||||
|
var errCnt int
|
||||||
|
|
||||||
|
optionsLock.Lock()
|
||||||
|
optionsLoop:
|
||||||
|
for key, option := range options {
|
||||||
|
// get option key from config
|
||||||
|
configValue, ok := config[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// validate value
|
||||||
|
valueCache, err := validateValue(option, configValue)
|
||||||
|
if err != nil {
|
||||||
|
errCnt++
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
continue optionsLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to perspective
|
||||||
|
perspective.config[key] = &perspectiveOption{
|
||||||
|
option: option,
|
||||||
|
valueCache: valueCache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
optionsLock.Unlock()
|
||||||
|
|
||||||
|
if firstErr != nil {
|
||||||
|
if errCnt > 0 {
|
||||||
|
return perspective, fmt.Errorf("encountered %d errors, first was: %s", errCnt, firstErr)
|
||||||
|
}
|
||||||
|
return perspective, firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return perspective, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Perspective) getPerspectiveValueCache(name string, requestedType uint8) *valueCache {
|
||||||
|
// get option
|
||||||
|
pOption, ok := p.config[name]
|
||||||
|
if !ok {
|
||||||
|
// check if option exists at all
|
||||||
|
optionsLock.RLock()
|
||||||
|
_, ok = options[name]
|
||||||
|
optionsLock.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
log.Errorf("config: request for unregistered option: %s", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check type
|
||||||
|
if requestedType != pOption.option.OptType {
|
||||||
|
log.Errorf("config: bad type: requested %s as %s, but is %s", name, getTypeName(requestedType), getTypeName(pOption.option.OptType))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check release level
|
||||||
|
if pOption.option.ReleaseLevel > getReleaseLevel() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return pOption.valueCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAsString returns a function that returns the wanted string with high performance.
|
||||||
|
func (p *Perspective) GetAsString(name string) (value string, ok bool) {
|
||||||
|
valueCache := p.getPerspectiveValueCache(name, OptTypeString)
|
||||||
|
if valueCache != nil {
|
||||||
|
return valueCache.stringVal, true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAsStringArray returns a function that returns the wanted string with high performance.
|
||||||
|
func (p *Perspective) GetAsStringArray(name string) (value []string, ok bool) {
|
||||||
|
valueCache := p.getPerspectiveValueCache(name, OptTypeStringArray)
|
||||||
|
if valueCache != nil {
|
||||||
|
return valueCache.stringArrayVal, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAsInt returns a function that returns the wanted int with high performance.
|
||||||
|
func (p *Perspective) GetAsInt(name string) (value int64, ok bool) {
|
||||||
|
valueCache := p.getPerspectiveValueCache(name, OptTypeInt)
|
||||||
|
if valueCache != nil {
|
||||||
|
return valueCache.intVal, true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAsBool returns a function that returns the wanted int with high performance.
|
||||||
|
func (p *Perspective) GetAsBool(name string) (value bool, ok bool) {
|
||||||
|
valueCache := p.getPerspectiveValueCache(name, OptTypeBool)
|
||||||
|
if valueCache != nil {
|
||||||
|
return valueCache.boolVal, true
|
||||||
|
}
|
||||||
|
return false, false
|
||||||
|
}
|
|
@ -26,14 +26,20 @@ func Register(option *Option) error {
|
||||||
return fmt.Errorf("failed to register option: please set option.OptType")
|
return fmt.Errorf("failed to register option: please set option.OptType")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
if option.ValidationRegex != "" {
|
if option.ValidationRegex != "" {
|
||||||
var err error
|
|
||||||
option.compiledRegex, err = regexp.Compile(option.ValidationRegex)
|
option.compiledRegex, err = regexp.Compile(option.ValidationRegex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("config: could not compile option.ValidationRegex: %s", err)
|
return fmt.Errorf("config: could not compile option.ValidationRegex: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
option.activeFallbackValue, err = validateValue(option, option.DefaultValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("config: invalid default value: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
optionsLock.Lock()
|
optionsLock.Lock()
|
||||||
defer optionsLock.Unlock()
|
defer optionsLock.Unlock()
|
||||||
options[option.Key] = option
|
options[option.Key] = option
|
||||||
|
|
|
@ -15,7 +15,7 @@ func TestRegistry(t *testing.T) {
|
||||||
ReleaseLevel: ReleaseLevelStable,
|
ReleaseLevel: ReleaseLevelStable,
|
||||||
ExpertiseLevel: ExpertiseLevelUser,
|
ExpertiseLevel: ExpertiseLevelUser,
|
||||||
OptType: OptTypeString,
|
OptType: OptTypeString,
|
||||||
DefaultValue: "default",
|
DefaultValue: "water",
|
||||||
ValidationRegex: "^(banana|water)$",
|
ValidationRegex: "^(banana|water)$",
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
|
|
@ -5,6 +5,8 @@ package config
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/tevino/abool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Release Level constants
|
// Release Level constants
|
||||||
|
@ -21,7 +23,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
releaseLevel *int32
|
releaseLevel *int32
|
||||||
|
releaseLevelOption *Option
|
||||||
|
releaseLevelOptionFlag = abool.New()
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -32,7 +36,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerReleaseLevelOption() {
|
func registerReleaseLevelOption() {
|
||||||
err := Register(&Option{
|
releaseLevelOption = &Option{
|
||||||
Name: "Release Level",
|
Name: "Release Level",
|
||||||
Key: releaseLevelKey,
|
Key: releaseLevelKey,
|
||||||
Description: "The Release Level changes which features are available to you. Some beta or experimental features are also available in the stable release channel. Unavailable settings are set to the default value.",
|
Description: "The Release Level changes which features are available to you. Some beta or experimental features are also available in the stable release channel. Unavailable settings are set to the default value.",
|
||||||
|
@ -46,15 +50,31 @@ func registerReleaseLevelOption() {
|
||||||
|
|
||||||
ExternalOptType: "string list",
|
ExternalOptType: "string list",
|
||||||
ValidationRegex: fmt.Sprintf("^(%s|%s|%s)$", ReleaseLevelNameStable, ReleaseLevelNameBeta, ReleaseLevelNameExperimental),
|
ValidationRegex: fmt.Sprintf("^(%s|%s|%s)$", ReleaseLevelNameStable, ReleaseLevelNameBeta, ReleaseLevelNameExperimental),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
err := Register(releaseLevelOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
releaseLevelOptionFlag.Set()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateReleaseLevel() {
|
func updateReleaseLevel() {
|
||||||
new := findStringValue(releaseLevelKey, "")
|
// check if already registered
|
||||||
switch new {
|
if !releaseLevelOptionFlag.IsSet() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// get value
|
||||||
|
value := releaseLevelOption.activeFallbackValue
|
||||||
|
if releaseLevelOption.activeValue != nil {
|
||||||
|
value = releaseLevelOption.activeValue
|
||||||
|
}
|
||||||
|
if releaseLevelOption.activeDefaultValue != nil {
|
||||||
|
value = releaseLevelOption.activeDefaultValue
|
||||||
|
}
|
||||||
|
// set atomic value
|
||||||
|
switch value.stringVal {
|
||||||
case ReleaseLevelNameStable:
|
case ReleaseLevelNameStable:
|
||||||
atomic.StoreInt32(releaseLevel, int32(ReleaseLevelStable))
|
atomic.StoreInt32(releaseLevel, int32(ReleaseLevelStable))
|
||||||
case ReleaseLevelNameBeta:
|
case ReleaseLevelNameBeta:
|
||||||
|
|
101
config/set.go
101
config/set.go
|
@ -19,7 +19,8 @@ var (
|
||||||
validityFlagLock sync.RWMutex
|
validityFlagLock sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
func getValidityFlag() *abool.AtomicBool {
|
// GetValidityFlag returns a flag that signifies if the configuration has been changed. This flag must not be changed, only read.
|
||||||
|
func GetValidityFlag() *abool.AtomicBool {
|
||||||
validityFlagLock.RLock()
|
validityFlagLock.RLock()
|
||||||
defer validityFlagLock.RUnlock()
|
defer validityFlagLock.RUnlock()
|
||||||
return validityFlag
|
return validityFlag
|
||||||
|
@ -41,14 +42,24 @@ func signalChanges() {
|
||||||
|
|
||||||
// setConfig sets the (prioritized) user defined config.
|
// setConfig sets the (prioritized) user defined config.
|
||||||
func setConfig(newValues map[string]interface{}) error {
|
func setConfig(newValues map[string]interface{}) error {
|
||||||
|
var firstErr error
|
||||||
|
var errCnt int
|
||||||
|
|
||||||
optionsLock.Lock()
|
optionsLock.Lock()
|
||||||
for key, option := range options {
|
for key, option := range options {
|
||||||
newValue, ok := newValues[key]
|
newValue, ok := newValues[key]
|
||||||
option.Lock()
|
option.Lock()
|
||||||
|
option.activeValue = nil
|
||||||
if ok {
|
if ok {
|
||||||
option.activeValue = newValue
|
valueCache, err := validateValue(option, newValue)
|
||||||
} else {
|
if err == nil {
|
||||||
option.activeValue = nil
|
option.activeValue = valueCache
|
||||||
|
} else {
|
||||||
|
errCnt++
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
option.Unlock()
|
option.Unlock()
|
||||||
}
|
}
|
||||||
|
@ -56,19 +67,37 @@ func setConfig(newValues map[string]interface{}) error {
|
||||||
|
|
||||||
signalChanges()
|
signalChanges()
|
||||||
go pushFullUpdate()
|
go pushFullUpdate()
|
||||||
|
|
||||||
|
if firstErr != nil {
|
||||||
|
if errCnt > 0 {
|
||||||
|
return fmt.Errorf("encountered %d errors, first was: %s", errCnt, firstErr)
|
||||||
|
}
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultConfig sets the (fallback) default config.
|
// SetDefaultConfig sets the (fallback) default config.
|
||||||
func SetDefaultConfig(newValues map[string]interface{}) error {
|
func SetDefaultConfig(newValues map[string]interface{}) error {
|
||||||
|
var firstErr error
|
||||||
|
var errCnt int
|
||||||
|
|
||||||
optionsLock.Lock()
|
optionsLock.Lock()
|
||||||
for key, option := range options {
|
for key, option := range options {
|
||||||
newValue, ok := newValues[key]
|
newValue, ok := newValues[key]
|
||||||
option.Lock()
|
option.Lock()
|
||||||
|
option.activeDefaultValue = nil
|
||||||
if ok {
|
if ok {
|
||||||
option.activeDefaultValue = newValue
|
valueCache, err := validateValue(option, newValue)
|
||||||
} else {
|
if err == nil {
|
||||||
option.activeDefaultValue = nil
|
option.activeDefaultValue = valueCache
|
||||||
|
} else {
|
||||||
|
errCnt++
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
option.Unlock()
|
option.Unlock()
|
||||||
}
|
}
|
||||||
|
@ -76,51 +105,15 @@ func SetDefaultConfig(newValues map[string]interface{}) error {
|
||||||
|
|
||||||
signalChanges()
|
signalChanges()
|
||||||
go pushFullUpdate()
|
go pushFullUpdate()
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateValue(option *Option, value interface{}) error {
|
if firstErr != nil {
|
||||||
switch v := value.(type) {
|
if errCnt > 0 {
|
||||||
case string:
|
return fmt.Errorf("encountered %d errors, first was: %s", errCnt, firstErr)
|
||||||
if option.OptType != OptTypeString {
|
|
||||||
return fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), option.Key, v)
|
|
||||||
}
|
}
|
||||||
if option.compiledRegex != nil {
|
return firstErr
|
||||||
if !option.compiledRegex.MatchString(v) {
|
|
||||||
return fmt.Errorf("validation failed: string \"%s\" did not match regex for option %s", v, option.Key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case []string:
|
|
||||||
if option.OptType != OptTypeStringArray {
|
|
||||||
return fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), option.Key, 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, option.Key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
|
||||||
if option.OptType != OptTypeInt {
|
|
||||||
return fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), option.Key, v)
|
|
||||||
}
|
|
||||||
if option.compiledRegex != nil {
|
|
||||||
if !option.compiledRegex.MatchString(fmt.Sprintf("%d", v)) {
|
|
||||||
return fmt.Errorf("validation failed: number \"%d\" did not match regex for option %s", v, option.Key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case bool:
|
|
||||||
if option.OptType != OptTypeBool {
|
|
||||||
return fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), option.Key, v)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid option value type: %T", value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetConfigOption sets a single value in the (prioritized) user defined config.
|
// SetConfigOption sets a single value in the (prioritized) user defined config.
|
||||||
|
@ -140,9 +133,10 @@ func setConfigOption(key string, value interface{}, push bool) (err error) {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
option.activeValue = nil
|
option.activeValue = nil
|
||||||
} else {
|
} else {
|
||||||
err = validateValue(option, value)
|
var valueCache *valueCache
|
||||||
|
valueCache, err = validateValue(option, value)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
option.activeValue = value
|
option.activeValue = valueCache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
option.Unlock()
|
option.Unlock()
|
||||||
|
@ -175,9 +169,10 @@ func setDefaultConfigOption(key string, value interface{}, push bool) (err error
|
||||||
if value == nil {
|
if value == nil {
|
||||||
option.activeDefaultValue = nil
|
option.activeDefaultValue = nil
|
||||||
} else {
|
} else {
|
||||||
err = validateValue(option, value)
|
var valueCache *valueCache
|
||||||
|
valueCache, err = validateValue(option, value)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
option.activeDefaultValue = value
|
option.activeDefaultValue = valueCache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
option.Unlock()
|
option.Unlock()
|
||||||
|
|
104
config/validate.go
Normal file
104
config/validate.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type valueCache struct {
|
||||||
|
stringVal string
|
||||||
|
stringArrayVal []string
|
||||||
|
intVal int64
|
||||||
|
boolVal bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateValue(option *Option, value interface{}) (*valueCache, error) { //nolint:gocyclo
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
if option.OptType != OptTypeString {
|
||||||
|
return nil, fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), option.Key, v)
|
||||||
|
}
|
||||||
|
if option.compiledRegex != nil {
|
||||||
|
if !option.compiledRegex.MatchString(v) {
|
||||||
|
return nil, fmt.Errorf("validation of option %s failed: string \"%s\" did not match validation regex for option", option.Key, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &valueCache{stringVal: v}, nil
|
||||||
|
case []interface{}:
|
||||||
|
vConverted := make([]string, len(v))
|
||||||
|
for pos, entry := range v {
|
||||||
|
s, ok := entry.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("validation of option %s failed: element %+v at index %d is not a string", option.Key, entry, pos)
|
||||||
|
|
||||||
|
}
|
||||||
|
vConverted[pos] = s
|
||||||
|
}
|
||||||
|
// continue to next case
|
||||||
|
return validateValue(option, vConverted)
|
||||||
|
case []string:
|
||||||
|
if option.OptType != OptTypeStringArray {
|
||||||
|
return nil, fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), option.Key, v)
|
||||||
|
}
|
||||||
|
if option.compiledRegex != nil {
|
||||||
|
for pos, entry := range v {
|
||||||
|
if !option.compiledRegex.MatchString(entry) {
|
||||||
|
return nil, fmt.Errorf("validation of option %s failed: string \"%s\" at index %d did not match validation regex", option.Key, entry, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &valueCache{stringArrayVal: v}, nil
|
||||||
|
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, float32, float64:
|
||||||
|
// uint64 is omitted, as it does not fit in a int64
|
||||||
|
if option.OptType != OptTypeInt {
|
||||||
|
return nil, fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), option.Key, v)
|
||||||
|
}
|
||||||
|
if option.compiledRegex != nil {
|
||||||
|
if !option.compiledRegex.MatchString(fmt.Sprintf("%d", v)) {
|
||||||
|
return nil, fmt.Errorf("validation of option %s failed: number \"%d\" did not match validation regex", option.Key, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch v := value.(type) {
|
||||||
|
case int:
|
||||||
|
return &valueCache{intVal: int64(v)}, nil
|
||||||
|
case int8:
|
||||||
|
return &valueCache{intVal: int64(v)}, nil
|
||||||
|
case int16:
|
||||||
|
return &valueCache{intVal: int64(v)}, nil
|
||||||
|
case int32:
|
||||||
|
return &valueCache{intVal: int64(v)}, nil
|
||||||
|
case int64:
|
||||||
|
return &valueCache{intVal: v}, nil
|
||||||
|
case uint:
|
||||||
|
return &valueCache{intVal: int64(v)}, nil
|
||||||
|
case uint8:
|
||||||
|
return &valueCache{intVal: int64(v)}, nil
|
||||||
|
case uint16:
|
||||||
|
return &valueCache{intVal: int64(v)}, nil
|
||||||
|
case uint32:
|
||||||
|
return &valueCache{intVal: int64(v)}, nil
|
||||||
|
case float32:
|
||||||
|
// convert if float has no decimals
|
||||||
|
if math.Remainder(float64(v), 1) == 0 {
|
||||||
|
return &valueCache{intVal: int64(v)}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to convert float32 to int64 for option %s, got value %+v", option.Key, v)
|
||||||
|
case float64:
|
||||||
|
// convert if float has no decimals
|
||||||
|
if math.Remainder(v, 1) == 0 {
|
||||||
|
return &valueCache{intVal: int64(v)}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to convert float64 to int64 for option %s, got value %+v", option.Key, v)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("internal error")
|
||||||
|
}
|
||||||
|
case bool:
|
||||||
|
if option.OptType != OptTypeBool {
|
||||||
|
return nil, fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), option.Key, v)
|
||||||
|
}
|
||||||
|
return &valueCache{boolVal: v}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid option value type for option %s: %T", option.Key, value)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue