mirror of
https://github.com/safing/portbase
synced 2025-09-01 18:19:57 +00:00
Add option registry to config package
This commit is contained in:
parent
ffc13d6e16
commit
773889f66a
6 changed files with 462 additions and 17 deletions
|
@ -43,6 +43,19 @@ func GetAsString(name string, fallback string) func() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAsStringArray returns a function that returns the wanted string with high performance.
|
||||||
|
func GetAsStringArray(name string, fallback []string) func() []string {
|
||||||
|
valid := getValidityFlag()
|
||||||
|
value := findStringArrayValue(name, fallback)
|
||||||
|
return func() []string {
|
||||||
|
if !valid.IsSet() {
|
||||||
|
valid = getValidityFlag()
|
||||||
|
value = findStringArrayValue(name, fallback)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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) func() int64 {
|
func GetAsInt(name string, fallback int64) func() int64 {
|
||||||
valid := getValidityFlag()
|
valid := getValidityFlag()
|
||||||
|
@ -55,3 +68,16 @@ func GetAsInt(name string, fallback int64) func() int64 {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAsBool returns a function that returns the wanted int with high performance.
|
||||||
|
func GetAsBool(name string, fallback bool) func() bool {
|
||||||
|
valid := getValidityFlag()
|
||||||
|
value := findBoolValue(name, fallback)
|
||||||
|
return func() bool {
|
||||||
|
if !valid.IsSet() {
|
||||||
|
valid = getValidityFlag()
|
||||||
|
value = findBoolValue(name, fallback)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,10 @@ func TestGet(t *testing.T) {
|
||||||
err := SetConfig(`
|
err := SetConfig(`
|
||||||
{
|
{
|
||||||
"monkey": "1",
|
"monkey": "1",
|
||||||
"elephant": 2
|
"zebra": ["black", "white"],
|
||||||
|
"elephant": 2,
|
||||||
|
"hot": true,
|
||||||
|
"cold": false
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -28,14 +31,30 @@ func TestGet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
monkey := GetAsString("monkey", "none")
|
monkey := GetAsString("monkey", "none")
|
||||||
elephant := GetAsInt("elephant", -1)
|
|
||||||
if monkey() != "1" {
|
if monkey() != "1" {
|
||||||
t.Fatalf("monkey should be 1, is %s", monkey())
|
t.Fatalf("monkey should be 1, is %s", monkey())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zebra := GetAsStringArray("zebra", []string{})
|
||||||
|
if len(zebra()) != 2 || zebra()[0] != "black" || zebra()[1] != "white" {
|
||||||
|
t.Fatalf("zebra should be [\"black\", \"white\"], is %v", zebra())
|
||||||
|
}
|
||||||
|
|
||||||
|
elephant := GetAsInt("elephant", -1)
|
||||||
if elephant() != 2 {
|
if elephant() != 2 {
|
||||||
t.Fatalf("elephant should be 2, is %d", elephant())
|
t.Fatalf("elephant should be 2, is %d", elephant())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hot := GetAsBool("hot", false)
|
||||||
|
if !hot() {
|
||||||
|
t.Fatalf("hot should be true, is %v", hot())
|
||||||
|
}
|
||||||
|
|
||||||
|
cold := GetAsBool("cold", true)
|
||||||
|
if cold() {
|
||||||
|
t.Fatalf("cold should be false, is %v", cold())
|
||||||
|
}
|
||||||
|
|
||||||
err = SetConfig(`
|
err = SetConfig(`
|
||||||
{
|
{
|
||||||
"monkey": "3"
|
"monkey": "3"
|
||||||
|
@ -48,10 +67,14 @@ func TestGet(t *testing.T) {
|
||||||
if monkey() != "3" {
|
if monkey() != "3" {
|
||||||
t.Fatalf("monkey should be 0, is %s", monkey())
|
t.Fatalf("monkey should be 0, is %s", monkey())
|
||||||
}
|
}
|
||||||
|
|
||||||
if elephant() != 0 {
|
if elephant() != 0 {
|
||||||
t.Fatalf("elephant should be 0, is %d", elephant())
|
t.Fatalf("elephant should be 0, is %d", elephant())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zebra()
|
||||||
|
hot()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkGetAsStringCached(b *testing.B) {
|
func BenchmarkGetAsStringCached(b *testing.B) {
|
||||||
|
|
154
config/layers.go
154
config/layers.go
|
@ -3,8 +3,10 @@ package config
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -15,6 +17,9 @@ var (
|
||||||
|
|
||||||
// ErrInvalidJSON is returned by SetConfig and SetDefaultConfig if they receive invalid json.
|
// ErrInvalidJSON is returned by SetConfig and SetDefaultConfig if they receive invalid json.
|
||||||
ErrInvalidJSON = errors.New("json string invalid")
|
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.
|
// SetConfig sets the (prioritized) user defined config.
|
||||||
|
@ -45,7 +50,115 @@ func SetDefaultConfig(json string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// findValue find the correct value in the user or default config
|
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) {
|
func findValue(name string) (result gjson.Result) {
|
||||||
configLock.RLock()
|
configLock.RLock()
|
||||||
defer configLock.RUnlock()
|
defer configLock.RUnlock()
|
||||||
|
@ -57,7 +170,7 @@ func findValue(name string) (result gjson.Result) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// findStringValue validates and return the value with the given name
|
// findStringValue validates and returns the value with the given name.
|
||||||
func findStringValue(name string, fallback string) (value string) {
|
func findStringValue(name string, fallback string) (value string) {
|
||||||
result := findValue(name)
|
result := findValue(name)
|
||||||
if !result.Exists() {
|
if !result.Exists() {
|
||||||
|
@ -69,7 +182,26 @@ func findStringValue(name string, fallback string) (value string) {
|
||||||
return result.String()
|
return result.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// findIntValue validates and return the value with the given name
|
// 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) {
|
func findIntValue(name string, fallback int64) (value int64) {
|
||||||
result := findValue(name)
|
result := findValue(name)
|
||||||
if !result.Exists() {
|
if !result.Exists() {
|
||||||
|
@ -80,3 +212,19 @@ func findIntValue(name string, fallback int64) (value int64) {
|
||||||
}
|
}
|
||||||
return result.Int()
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,50 +2,186 @@ package config
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestLayers(t *testing.T) {
|
func TestLayersGetters(t *testing.T) {
|
||||||
|
|
||||||
err := SetConfig("{invalid json")
|
err := SetConfig("{invalid json")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Error("expected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = SetDefaultConfig("{invalid json")
|
err = SetDefaultConfig("{invalid json")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Error("expected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = SetConfig(`
|
err = SetConfig(`
|
||||||
{
|
{
|
||||||
"monkey": "banana",
|
"monkey": "1",
|
||||||
"elephant": 3
|
"zebra": ["black", "white"],
|
||||||
}
|
"weird_zebra": ["black", -1],
|
||||||
|
"elephant": 2,
|
||||||
|
"hot": true
|
||||||
|
}
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test missing values
|
// Test missing values
|
||||||
|
|
||||||
missingString := GetAsString("missing", "fallback")
|
missingString := GetAsString("missing", "fallback")
|
||||||
if missingString() != "fallback" {
|
if missingString() != "fallback" {
|
||||||
t.Fatal("expected fallback value: fallback")
|
t.Error("expected fallback value: fallback")
|
||||||
|
}
|
||||||
|
|
||||||
|
missingStringArray := GetAsStringArray("missing", []string{"fallback"})
|
||||||
|
if len(missingStringArray()) != 1 || missingStringArray()[0] != "fallback" {
|
||||||
|
t.Error("expected fallback value: [fallback]")
|
||||||
}
|
}
|
||||||
|
|
||||||
missingInt := GetAsInt("missing", -1)
|
missingInt := GetAsInt("missing", -1)
|
||||||
if missingInt() != -1 {
|
if missingInt() != -1 {
|
||||||
t.Fatal("expected fallback value: -1")
|
t.Error("expected fallback value: -1")
|
||||||
|
}
|
||||||
|
|
||||||
|
missingBool := GetAsBool("missing", false)
|
||||||
|
if missingBool() {
|
||||||
|
t.Error("expected fallback value: false")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test value mismatch
|
// Test value mismatch
|
||||||
|
|
||||||
notString := GetAsString("elephant", "fallback")
|
notString := GetAsString("elephant", "fallback")
|
||||||
if notString() != "fallback" {
|
if notString() != "fallback" {
|
||||||
t.Fatal("expected fallback value: fallback")
|
t.Error("expected fallback value: fallback")
|
||||||
|
}
|
||||||
|
|
||||||
|
notStringArray := GetAsStringArray("elephant", []string{"fallback"})
|
||||||
|
if len(notStringArray()) != 1 || notStringArray()[0] != "fallback" {
|
||||||
|
t.Error("expected fallback value: [fallback]")
|
||||||
|
}
|
||||||
|
|
||||||
|
mixedStringArray := GetAsStringArray("weird_zebra", []string{"fallback"})
|
||||||
|
if len(mixedStringArray()) != 1 || mixedStringArray()[0] != "fallback" {
|
||||||
|
t.Error("expected fallback value: [fallback]")
|
||||||
}
|
}
|
||||||
|
|
||||||
notInt := GetAsInt("monkey", -1)
|
notInt := GetAsInt("monkey", -1)
|
||||||
if notInt() != -1 {
|
if notInt() != -1 {
|
||||||
t.Fatal("expected fallback value: -1")
|
t.Error("expected fallback value: -1")
|
||||||
|
}
|
||||||
|
|
||||||
|
notBool := GetAsBool("monkey", false)
|
||||||
|
if notBool() {
|
||||||
|
t.Error("expected fallback value: false")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLayersSetters(t *testing.T) {
|
||||||
|
|
||||||
|
Register(&Option{
|
||||||
|
Name: "name",
|
||||||
|
Key: "monkey",
|
||||||
|
Description: "description",
|
||||||
|
ExpertiseLevel: 1,
|
||||||
|
OptType: OptTypeString,
|
||||||
|
DefaultValue: "banana",
|
||||||
|
ValidationRegex: "^(banana|water)$",
|
||||||
|
})
|
||||||
|
Register(&Option{
|
||||||
|
Name: "name",
|
||||||
|
Key: "zebra",
|
||||||
|
Description: "description",
|
||||||
|
ExpertiseLevel: 1,
|
||||||
|
OptType: OptTypeStringArray,
|
||||||
|
DefaultValue: []string{"black", "white"},
|
||||||
|
ValidationRegex: "^[a-z]+$",
|
||||||
|
})
|
||||||
|
Register(&Option{
|
||||||
|
Name: "name",
|
||||||
|
Key: "elephant",
|
||||||
|
Description: "description",
|
||||||
|
ExpertiseLevel: 1,
|
||||||
|
OptType: OptTypeInt,
|
||||||
|
DefaultValue: 2,
|
||||||
|
ValidationRegex: "",
|
||||||
|
})
|
||||||
|
Register(&Option{
|
||||||
|
Name: "name",
|
||||||
|
Key: "hot",
|
||||||
|
Description: "description",
|
||||||
|
ExpertiseLevel: 1,
|
||||||
|
OptType: OptTypeBool,
|
||||||
|
DefaultValue: true,
|
||||||
|
ValidationRegex: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
// correct types
|
||||||
|
if err := SetConfigOption("monkey", "banana"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := SetConfigOption("zebra", []string{"black", "white"}); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := SetDefaultConfigOption("elephant", 2); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := SetDefaultConfigOption("hot", true); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// incorrect types
|
||||||
|
if err := SetConfigOption("monkey", []string{"black", "white"}); err == nil {
|
||||||
|
t.Error("should fail")
|
||||||
|
}
|
||||||
|
if err := SetConfigOption("zebra", 2); err == nil {
|
||||||
|
t.Error("should fail")
|
||||||
|
}
|
||||||
|
if err := SetDefaultConfigOption("elephant", true); err == nil {
|
||||||
|
t.Error("should fail")
|
||||||
|
}
|
||||||
|
if err := SetDefaultConfigOption("hot", "banana"); err == nil {
|
||||||
|
t.Error("should fail")
|
||||||
|
}
|
||||||
|
if err := SetDefaultConfigOption("hot", []byte{0}); err == nil {
|
||||||
|
t.Error("should fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
// validation fail
|
||||||
|
if err := SetConfigOption("monkey", "dirt"); err == nil {
|
||||||
|
t.Error("should fail")
|
||||||
|
}
|
||||||
|
if err := SetConfigOption("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", []string{"black", "white"}); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := SetConfigOption("invalid", 2); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := SetConfigOption("invalid", true); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := SetConfigOption("invalid", []byte{0}); err != ErrInvalidOptionType {
|
||||||
|
t.Error("should fail with ErrInvalidOptionType")
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete
|
||||||
|
if err := SetConfigOption("monkey", nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := SetDefaultConfigOption("elephant", nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := SetDefaultConfigOption("invalid_delete", nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
67
config/registry.go
Normal file
67
config/registry.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Variable Type IDs for frontend Identification. Values over 100 are free for custom use.
|
||||||
|
const (
|
||||||
|
OptTypeString uint8 = 1
|
||||||
|
OptTypeStringArray uint8 = 2
|
||||||
|
OptTypeInt uint8 = 3
|
||||||
|
OptTypeBool uint8 = 4
|
||||||
|
|
||||||
|
ExpertiseLevelUser int8 = 1
|
||||||
|
ExpertiseLevelExpert int8 = 2
|
||||||
|
ExpertiseLevelDeveloper int8 = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
optionsLock sync.RWMutex
|
||||||
|
options = make(map[string]*Option)
|
||||||
|
|
||||||
|
// ErrIncompleteCall is return when RegisterOption is called with empty mandatory values.
|
||||||
|
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 {
|
||||||
|
|
||||||
|
if option.Name == "" ||
|
||||||
|
option.Key == "" ||
|
||||||
|
option.Description == "" ||
|
||||||
|
option.ExpertiseLevel == 0 ||
|
||||||
|
option.OptType == 0 {
|
||||||
|
return ErrIncompleteCall
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.ValidationRegex != "" {
|
||||||
|
var err error
|
||||||
|
option.compiledRegex, err = regexp.Compile(option.ValidationRegex)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("config: could not compile option.ValidationRegex: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optionsLock.Lock()
|
||||||
|
defer optionsLock.Unlock()
|
||||||
|
|
||||||
|
options[option.Key] = option
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
45
config/registry_test.go
Normal file
45
config/registry_test.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegistry(t *testing.T) {
|
||||||
|
|
||||||
|
if err := Register(&Option{
|
||||||
|
Name: "name",
|
||||||
|
Key: "key",
|
||||||
|
Description: "description",
|
||||||
|
ExpertiseLevel: 1,
|
||||||
|
OptType: OptTypeString,
|
||||||
|
DefaultValue: "default",
|
||||||
|
ValidationRegex: "^(banana|water)$",
|
||||||
|
}); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Register(&Option{
|
||||||
|
Name: "name",
|
||||||
|
Key: "key",
|
||||||
|
Description: "description",
|
||||||
|
ExpertiseLevel: 1,
|
||||||
|
OptType: 0,
|
||||||
|
DefaultValue: "default",
|
||||||
|
ValidationRegex: "^[A-Z][a-z]+$",
|
||||||
|
}); err == nil {
|
||||||
|
t.Error("should fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Register(&Option{
|
||||||
|
Name: "name",
|
||||||
|
Key: "key",
|
||||||
|
Description: "description",
|
||||||
|
ExpertiseLevel: 1,
|
||||||
|
OptType: OptTypeString,
|
||||||
|
DefaultValue: "default",
|
||||||
|
ValidationRegex: "[",
|
||||||
|
}); err == nil {
|
||||||
|
t.Error("should fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue