Improve config and integrate with database

This commit is contained in:
Daniel 2018-09-27 15:58:53 +02:00
parent 31c09512a0
commit 53fde29e1a
12 changed files with 672 additions and 157 deletions

155
config/database.go Normal file
View file

@ -0,0 +1,155 @@
package config
import (
"errors"
"sort"
"strings"
"github.com/Safing/portbase/log"
"github.com/Safing/portbase/database"
"github.com/Safing/portbase/database/storage"
"github.com/Safing/portbase/database/record"
"github.com/Safing/portbase/database/query"
"github.com/Safing/portbase/database/iterator"
)
var (
dbController *database.Controller
)
// ConfigStorageInterface provices a storage.Interface to the configuration manager.
type ConfigStorageInterface struct {
storage.InjectBase
}
// Get returns a database record.
func (s *ConfigStorageInterface) Get(key string) (record.Record, error) {
optionsLock.Lock()
defer optionsLock.Unlock()
opt, ok := options[key]
if !ok {
return nil, storage.ErrNotFound
}
return opt.Export()
}
// Put stores a record in the database.
func (s *ConfigStorageInterface) Put(r record.Record) error {
acc := r.GetAccessor(r)
if acc == nil {
return errors.New("invalid data")
}
optionsLock.RLock()
option, ok := options[r.DatabaseKey()]
optionsLock.RUnlock()
if !ok {
return errors.New("config option does not exist")
}
var value interface{}
switch option.OptType {
case OptTypeString :
value, ok = acc.GetString("Value")
case OptTypeStringArray :
value, ok = acc.GetStringArray("Value")
case OptTypeInt :
value, ok = acc.GetInt("Value")
case OptTypeBool :
value, ok = acc.GetBool("Value")
}
if !ok {
return errors.New("expected new value in \"Value\"")
}
err := setConfigOption(r.DatabaseKey(), value, false)
if err != nil {
return err
}
return nil
}
// Delete deletes a record from the database.
func (s *ConfigStorageInterface) Delete(key string) error {
return setConfigOption(key, nil, false)
}
// Query returns a an iterator for the supplied query.
func (s *ConfigStorageInterface) Query(q *query.Query, local, internal bool) (*iterator.Iterator, error) {
optionsLock.Lock()
defer optionsLock.Unlock()
it := iterator.New()
var opts []*Option
for _, opt := range options {
if strings.HasPrefix(opt.Key, q.DatabaseKeyPrefix()) {
opts = append(opts, opt)
}
}
go s.processQuery(q, it, opts)
return it, nil
}
func (s *ConfigStorageInterface) processQuery(q *query.Query, it *iterator.Iterator, opts []*Option) {
sort.Sort(sortableOptions(opts))
for _, opt := range options {
r, err := opt.Export()
if err != nil {
it.Finish(err)
return
}
it.Next <- r
}
it.Finish(nil)
}
// ReadOnly returns whether the database is read only.
func (s *ConfigStorageInterface) ReadOnly() bool {
return false
}
func registerAsDatabase() error {
_, err := database.Register(&database.Database{
Name: "config",
Description: "Configuration Manager",
StorageType: "injected",
PrimaryAPI: "",
})
if err != nil {
return nil
}
controller, err := database.InjectDatabase("config", &ConfigStorageInterface{})
if err != nil {
return nil
}
dbController = controller
return nil
}
func pushFullUpdate() {
optionsLock.RLock()
defer optionsLock.RUnlock()
for _, option := range options {
pushUpdate(option)
}
}
func pushUpdate(option *Option) {
r, err := option.Export()
if err != nil {
log.Errorf("failed to export option to push update: %s", err)
} else {
dbController.PushUpdate(r)
}
}

View file

@ -2,14 +2,41 @@ package config
import (
"testing"
"github.com/Safing/portbase/log"
)
func parseAndSetConfig(jsonData string) error {
m, err := JSONToMap([]byte(jsonData))
if err != nil {
return err
}
return setConfig(m)
}
func parseAndSetDefaultConfig(jsonData string) error {
m, err := JSONToMap([]byte(jsonData))
if err != nil {
return err
}
return SetDefaultConfig(m)
}
func TestGet(t *testing.T) {
err := SetConfig(`
err := log.Start()
if err != nil {
t.Fatal(err)
}
err = parseAndSetConfig(`
{
"monkey": "1",
"zebra": ["black", "white"],
"zebras": {
"zebra": ["black", "white"]
},
"elephant": 2,
"hot": true,
"cold": false
@ -19,7 +46,7 @@ func TestGet(t *testing.T) {
t.Fatal(err)
}
err = SetDefaultConfig(`
err = parseAndSetDefaultConfig(`
{
"monkey": "0",
"snake": "0",
@ -35,7 +62,7 @@ func TestGet(t *testing.T) {
t.Fatalf("monkey should be 1, is %s", monkey())
}
zebra := GetAsStringArray("zebra", []string{})
zebra := GetAsStringArray("zebras/zebra", []string{})
if len(zebra()) != 2 || zebra()[0] != "black" || zebra()[1] != "white" {
t.Fatalf("zebra should be [\"black\", \"white\"], is %v", zebra())
}
@ -55,7 +82,7 @@ func TestGet(t *testing.T) {
t.Fatalf("cold should be false, is %v", cold())
}
err = SetConfig(`
err = parseAndSetConfig(`
{
"monkey": "3"
}
@ -79,7 +106,7 @@ func TestGet(t *testing.T) {
func BenchmarkGetAsStringCached(b *testing.B) {
// Setup
err := SetConfig(`
err := parseAndSetConfig(`
{
"monkey": "banana"
}
@ -100,7 +127,7 @@ func BenchmarkGetAsStringCached(b *testing.B) {
func BenchmarkGetAsStringRefetch(b *testing.B) {
// Setup
err := SetConfig(`
err := parseAndSetConfig(`
{
"monkey": "banana"
}
@ -120,7 +147,7 @@ func BenchmarkGetAsStringRefetch(b *testing.B) {
func BenchmarkGetAsIntCached(b *testing.B) {
// Setup
err := SetConfig(`
err := parseAndSetConfig(`
{
"monkey": 1
}
@ -141,7 +168,7 @@ func BenchmarkGetAsIntCached(b *testing.B) {
func BenchmarkGetAsIntRefetch(b *testing.B) {
// Setup
err := SetConfig(`
err := parseAndSetConfig(`
{
"monkey": 1
}

View file

@ -1,3 +0,0 @@
package integration
// API

View file

@ -1,3 +0,0 @@
package integration
// register as module

View file

@ -1,4 +0,0 @@
package integration
// persist config file
// create callback function in config to get updates

View file

@ -5,15 +5,14 @@ import (
"sync"
"fmt"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"github.com/Safing/portbase/log"
)
var (
configLock sync.RWMutex
userConfig = ""
defaultConfig = ""
userConfig = make(map[string]interface{})
defaultConfig = make(map[string]interface{})
// ErrInvalidJSON is returned by SetConfig and SetDefaultConfig if they receive invalid json.
ErrInvalidJSON = errors.New("json string invalid")
@ -22,112 +21,109 @@ var (
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
}
// setConfig sets the (prioritized) user defined config.
func setConfig(m map[string]interface{}) error {
configLock.Lock()
defer configLock.Unlock()
userConfig = json
userConfig = m
resetValidityFlag()
go pushFullUpdate()
return nil
}
// SetDefaultConfig sets the (fallback) default config.
func SetDefaultConfig(json string) error {
if !gjson.Valid(json) {
return ErrInvalidJSON
}
func SetDefaultConfig(m map[string]interface{}) error {
configLock.Lock()
defer configLock.Unlock()
defaultConfig = json
defaultConfig = m
resetValidityFlag()
go pushFullUpdate()
return nil
}
func validateValue(name string, value interface{}) error {
func validateValue(name string, value interface{}) (*Option, 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
}
return nil, errors.New("config option does not exist")
}
switch v := value.(type) {
case string:
if option.OptType != OptTypeString {
return fmt.Errorf("expected type string for option %s, got type %T", name, v)
return nil, fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), 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, fmt.Errorf("validation failed: string \"%s\" did not match regex for option %s", v, name)
}
}
return nil
return option, nil
case []string:
if option.OptType != OptTypeStringArray {
return fmt.Errorf("expected type string for option %s, got type %T", name, v)
return nil, fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), 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, fmt.Errorf("validation failed: string \"%s\" at index %d did not match regex for option %s", entry, pos, name)
}
}
}
return nil
case int:
return option, nil
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
if option.OptType != OptTypeInt {
return fmt.Errorf("expected type int for option %s, got type %T", name, v)
return nil, fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), name, v)
}
return nil
if option.compiledRegex != nil {
if !option.compiledRegex.MatchString(fmt.Sprintf("%d", v)) {
return nil, fmt.Errorf("validation failed: number \"%d\" did not match regex for option %s", v, name)
}
}
return option, nil
case bool:
if option.OptType != OptTypeBool {
return fmt.Errorf("expected type bool for option %s, got type %T", name, v)
return nil, fmt.Errorf("expected type %s for option %s, got type %T", getTypeName(option.OptType), name, v)
}
return nil
return option, nil
default:
return ErrInvalidOptionType
return nil, fmt.Errorf("invalid option value type: %T", value)
}
}
// SetConfigOption sets a single value in the (prioritized) user defined config.
func SetConfigOption(name string, value interface{}) error {
return setConfigOption(name, value, true)
}
func setConfigOption(name string, value interface{}, push bool) error {
configLock.Lock()
defer configLock.Unlock()
var err error
var newConfig string
if value == nil {
newConfig, err = sjson.Delete(userConfig, name)
delete(userConfig, name)
} else {
err = validateValue(name, value)
var option *Option
option, err = validateValue(name, value)
if err == nil {
newConfig, err = sjson.Set(userConfig, name, value)
userConfig[name] = value
if push {
go pushUpdate(option)
}
}
}
if err == nil {
userConfig = newConfig
resetValidityFlag()
go saveConfig()
}
return err
@ -135,23 +131,29 @@ func SetConfigOption(name string, value interface{}) error {
// SetDefaultConfigOption sets a single value in the (fallback) default config.
func SetDefaultConfigOption(name string, value interface{}) error {
return setDefaultConfigOption(name, value, true)
}
func setDefaultConfigOption(name string, value interface{}, push bool) error {
configLock.Lock()
defer configLock.Unlock()
var err error
var newConfig string
if value == nil {
newConfig, err = sjson.Delete(defaultConfig, name)
delete(defaultConfig, name)
} else {
err = validateValue(name, value)
var option *Option
option, err = validateValue(name, value)
if err == nil {
newConfig, err = sjson.Set(defaultConfig, name, value)
defaultConfig[name] = value
if push {
go pushUpdate(option)
}
}
}
if err == nil {
defaultConfig = newConfig
resetValidityFlag()
}
@ -159,72 +161,113 @@ func SetDefaultConfigOption(name string, value interface{}) error {
}
// findValue find the correct value in the user or default config.
func findValue(name string) (result gjson.Result) {
func findValue(name string) (result interface{}) {
configLock.RLock()
defer configLock.RUnlock()
result = gjson.Get(userConfig, name)
if !result.Exists() {
result = gjson.Get(defaultConfig, name)
result, ok := userConfig[name]
if ok {
return
}
return result
result, ok = defaultConfig[name]
if ok {
return
}
optionsLock.RLock()
defer optionsLock.RUnlock()
option, ok := options[name]
if ok {
return option.DefaultValue
}
log.Errorf("config: request for unregistered option: %s", name)
return nil
}
// findStringValue validates and returns the value with the given name.
func findStringValue(name string, fallback string) (value string) {
result := findValue(name)
if !result.Exists() {
if result == nil {
return fallback
}
if result.Type != gjson.String {
return fallback
v, ok := result.(string)
if ok {
return v
}
return result.String()
return fallback
}
// findStringArrayValue validates and returns the value with the given name.
func findStringArrayValue(name string, fallback []string) (value []string) {
result := findValue(name)
if !result.Exists() {
if result == nil {
return fallback
}
if !result.IsArray() {
return fallback
}
results := result.Array()
for _, r := range results {
if r.Type != gjson.String {
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
}
}
value = append(value, r.String())
return new
}
return value
return fallback
}
// findIntValue validates and returns the value with the given name.
func findIntValue(name string, fallback int64) (value int64) {
result := findValue(name)
if !result.Exists() {
if result == nil {
return fallback
}
if result.Type != gjson.Number {
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 result.Int()
return fallback
}
// findBoolValue validates and returns the value with the given name.
func findBoolValue(name string, fallback bool) (value bool) {
result := findValue(name)
if !result.Exists() {
if result == nil {
return fallback
}
switch result.Type {
case gjson.True:
return true
case gjson.False:
return false
default:
return fallback
v, ok := result.(bool)
if ok {
return v
}
return fallback
}

View file

@ -4,27 +4,26 @@ import "testing"
func TestLayersGetters(t *testing.T) {
err := SetConfig("{invalid json")
if err == nil {
t.Error("expected error")
}
err = SetDefaultConfig("{invalid json")
if err == nil {
t.Error("expected error")
}
err = SetConfig(`
mapData, err := JSONToMap([]byte(`
{
"monkey": "1",
"zebra": ["black", "white"],
"weird_zebra": ["black", -1],
"elephant": 2,
"hot": true
"zebras": {
"zebra": ["black", "white"],
"weird_zebra": ["black", -1]
},
"env": {
"hot": true
}
}
`)
`))
if err != nil {
t.Error(err)
t.Fatal(err)
}
err = setConfig(mapData)
if err != nil {
t.Fatal(err)
}
// Test missing values
@ -61,7 +60,7 @@ func TestLayersGetters(t *testing.T) {
t.Error("expected fallback value: [fallback]")
}
mixedStringArray := GetAsStringArray("weird_zebra", []string{"fallback"})
mixedStringArray := GetAsStringArray("zebras/weird_zebra", []string{"fallback"})
if len(mixedStringArray()) != 1 || mixedStringArray()[0] != "fallback" {
t.Error("expected fallback value: [fallback]")
}
@ -91,7 +90,7 @@ func TestLayersSetters(t *testing.T) {
})
Register(&Option{
Name: "name",
Key: "zebra",
Key: "zebras/zebra",
Description: "description",
ExpertiseLevel: 1,
OptType: OptTypeStringArray,
@ -121,7 +120,7 @@ func TestLayersSetters(t *testing.T) {
if err := SetConfigOption("monkey", "banana"); err != nil {
t.Error(err)
}
if err := SetConfigOption("zebra", []string{"black", "white"}); err != nil {
if err := SetConfigOption("zebras/zebra", []string{"black", "white"}); err != nil {
t.Error(err)
}
if err := SetDefaultConfigOption("elephant", 2); err != nil {
@ -135,7 +134,7 @@ func TestLayersSetters(t *testing.T) {
if err := SetConfigOption("monkey", []string{"black", "white"}); err == nil {
t.Error("should fail")
}
if err := SetConfigOption("zebra", 2); err == nil {
if err := SetConfigOption("zebras/zebra", 2); err == nil {
t.Error("should fail")
}
if err := SetDefaultConfigOption("elephant", true); err == nil {
@ -152,25 +151,25 @@ func TestLayersSetters(t *testing.T) {
if err := SetConfigOption("monkey", "dirt"); err == nil {
t.Error("should fail")
}
if err := SetConfigOption("zebra", []string{"Element649"}); err == nil {
if err := SetConfigOption("zebras/zebra", []string{"Element649"}); err == nil {
t.Error("should fail")
}
// unregistered checking
if err := SetConfigOption("invalid", "banana"); err != nil {
t.Error(err)
if err := SetConfigOption("invalid", "banana"); err == nil {
t.Error("should fail")
}
if err := SetConfigOption("invalid", []string{"black", "white"}); err != nil {
t.Error(err)
if err := SetConfigOption("invalid", []string{"black", "white"}); err == nil {
t.Error("should fail")
}
if err := SetConfigOption("invalid", 2); err != nil {
t.Error(err)
if err := SetConfigOption("invalid", 2); err == nil {
t.Error("should fail")
}
if err := SetConfigOption("invalid", true); err != nil {
t.Error(err)
if err := SetConfigOption("invalid", true); err == nil {
t.Error("should fail")
}
if err := SetConfigOption("invalid", []byte{0}); err != ErrInvalidOptionType {
t.Error("should fail with ErrInvalidOptionType")
if err := SetConfigOption("invalid", []byte{0}); err == nil {
t.Error("should fail")
}
// delete

32
config/main.go Normal file
View file

@ -0,0 +1,32 @@
package config
import (
"os"
"path"
"github.com/Safing/portbase/database"
"github.com/Safing/portbase/modules"
)
func init() {
modules.Register("config", prep, start, stop, "database")
}
func prep() error {
return nil
}
func start() error {
configFilePath = path.Join(database.GetDatabaseRoot(), "config.json")
err := loadConfig()
if err != nil && !os.IsNotExist(err) {
return err
}
return registerAsDatabase()
}
func stop() error {
return nil
}

104
config/option.go Normal file
View file

@ -0,0 +1,104 @@
package config
import (
"fmt"
"regexp"
"encoding/json"
"github.com/tidwall/sjson"
"github.com/Safing/portbase/database/record"
)
// Variable Type IDs for frontend Identification. Use ExternalOptType for extended types in the frontend.
const (
OptTypeString uint8 = 1
OptTypeStringArray uint8 = 2
OptTypeInt uint8 = 3
OptTypeBool uint8 = 4
ExpertiseLevelUser uint8 = 1
ExpertiseLevelExpert uint8 = 2
ExpertiseLevelDeveloper uint8 = 3
)
func getTypeName(t uint8) string {
switch t {
case OptTypeString:
return "string"
case OptTypeStringArray:
return "[]string"
case OptTypeInt:
return "int"
case OptTypeBool:
return "bool"
default:
return "unknown"
}
}
// Option describes a configuration option.
type Option struct {
Name string
Key string // category/sub/key
Description string
ExpertiseLevel uint8
OptType uint8
DefaultValue interface{}
ExternalOptType string
ValidationRegex string
compiledRegex *regexp.Regexp
}
// Export expors an option to a Record.
func (opt *Option) Export() (record.Record, error) {
data, err := json.Marshal(opt)
if err != nil {
return nil, err
}
configLock.RLock()
defer configLock.RUnlock()
userValue, ok := userConfig[opt.Key]
if ok {
data, err = sjson.SetBytes(data, "Value", userValue)
if err != nil {
return nil, err
}
}
defaultValue, ok := defaultConfig[opt.Key]
if ok {
data, err = sjson.SetBytes(data, "DefaultValue", defaultValue)
if err != nil {
return nil, err
}
}
r, err := record.NewWrapper(fmt.Sprintf("config:%s", opt.Key), nil, record.JSON, data)
if err != nil {
return nil, err
}
r.SetMeta(&record.Meta{})
return r, nil
}
type sortableOptions []*Option
// Len is the number of elements in the collection.
func (opts sortableOptions) Len() int {
return len(opts)
}
// Less reports whether the element with
// index i should sort before the element with index j.
func (opts sortableOptions) Less(i, j int) bool {
return opts[i].Key < opts[j].Key
}
// Swap swaps the elements with indexes i and j.
func (opts sortableOptions) Swap(i, j int) {
opts[i], opts[j] = opts[j], opts[i]
}

125
config/persistence.go Normal file
View file

@ -0,0 +1,125 @@
package config
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"github.com/Safing/portbase/log"
)
var (
configFilePath string
)
func loadConfig() error {
data, err := ioutil.ReadFile(configFilePath)
if err != nil {
return err
}
m, err := JSONToMap(data)
if err != nil {
return err
}
return setConfig(m)
}
func saveConfig() (err error) {
data, err := MapToJSON(userConfig)
if err == nil {
err = ioutil.WriteFile(configFilePath, data, 0600)
}
if err != nil {
log.Errorf("config: failed to save config: %s", err)
}
return err
}
// JSONToMap parses and flattens a hierarchical json object.
func JSONToMap(jsonData []byte) (map[string]interface{}, error) {
loaded := make(map[string]interface{})
err := json.Unmarshal(jsonData, &loaded)
if err != nil {
return nil, err
}
flatten(loaded, loaded, "")
return loaded, nil
}
func flatten(rootMap, subMap map[string]interface{}, subKey string) {
for key, entry := range subMap {
// get next level key
subbedKey := key
if subKey != "" {
subbedKey = fmt.Sprintf("%s/%s", subKey, key)
}
// check for next subMap
nextSub, ok := entry.(map[string]interface{})
if ok {
flatten(rootMap, nextSub, subbedKey)
delete(rootMap, key)
} else if subKey != "" {
// only set if not on root level
rootMap[subbedKey] = entry
}
}
}
// MapToJSON expands a flattened map and returns it as json.
func MapToJSON(mapData map[string]interface{}) ([]byte, error) {
configLock.RLock()
defer configLock.RUnlock()
new := make(map[string]interface{})
for key, value := range mapData {
new[key] = value
}
expand(new)
return json.MarshalIndent(new, "", " ")
}
// expand expands a flattened map.
func expand(mapData map[string]interface{}) {
var newMaps []map[string]interface{}
for key, entry := range mapData {
if strings.Contains(key, "/") {
parts := strings.SplitN(key, "/", 2)
if len(parts) == 2 {
// get subMap
var subMap map[string]interface{}
v, ok := mapData[parts[0]]
if ok {
subMap, ok = v.(map[string]interface{})
if !ok {
subMap = make(map[string]interface{})
newMaps = append(newMaps, subMap)
mapData[parts[0]] = subMap
}
} else {
subMap = make(map[string]interface{})
newMaps = append(newMaps, subMap)
mapData[parts[0]] = subMap
}
// set entry
subMap[parts[1]] = entry
// delete entry from
delete(mapData, key)
}
}
}
for _, entry := range newMaps {
expand(entry)
}
}

View file

@ -0,0 +1,64 @@
package config
import (
"bytes"
"testing"
)
func TestJSONMapConversion(t *testing.T) {
jsonData := `{
"a": "b",
"c": {
"d": "e",
"f": "g",
"h": {
"i": "j",
"k": "l",
"m": {
"n": "o"
}
}
},
"p": "q"
}`
jsonBytes := []byte(jsonData)
mapData := map[string]interface{}{
"a": "b",
"p": "q",
"c/d": "e",
"c/f": "g",
"c/h/i": "j",
"c/h/k": "l",
"c/h/m/n": "o",
}
m, err := JSONToMap(jsonBytes)
if err != nil {
t.Fatal(err)
}
j, err := MapToJSON(mapData)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(jsonBytes, j) {
t.Errorf("json does not match, got %s", j)
}
j2, err := MapToJSON(m)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(jsonBytes, j2) {
t.Errorf("json does not match, got %s", j)
}
// fails for some reason
// if !reflect.DeepEqual(mapData, m) {
// t.Errorf("maps do not match, got %s", m)
// }
}

View file

@ -7,18 +7,6 @@ import (
"sync"
)
// Variable Type IDs for frontend Identification. Values from 100 are free for custom use.
const (
OptTypeString uint8 = 1
OptTypeStringArray uint8 = 2
OptTypeInt uint8 = 3
OptTypeBool uint8 = 4
ExpertiseLevelUser uint8 = 1
ExpertiseLevelExpert uint8 = 2
ExpertiseLevelDeveloper uint8 = 3
)
var (
optionsLock sync.RWMutex
options = make(map[string]*Option)
@ -27,18 +15,6 @@ var (
ErrIncompleteCall = errors.New("could not register config option: all fields, except for the validationRegex are mandatory")
)
// Option describes a configuration option.
type Option struct {
Name string
Key string
Description string
ExpertiseLevel uint8
OptType uint8
DefaultValue interface{}
ValidationRegex string
compiledRegex *regexp.Regexp
}
// Register registers a new configuration option.
func Register(option *Option) error {