Improve config import and export utils

This commit is contained in:
Daniel 2023-09-22 15:17:16 +02:00
parent 01b03aa936
commit 4451b6985c
7 changed files with 109 additions and 45 deletions

View file

@ -482,7 +482,6 @@ func (e *Endpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Check for handler error. // Check for handler error.
if err != nil { if err != nil {
// if statusProvider, ok := err.(HTTPStatusProvider); ok {
var statusProvider HTTPStatusProvider var statusProvider HTTPStatusProvider
if errors.As(err, &statusProvider) { if errors.As(err, &statusProvider) {
http.Error(w, err.Error(), statusProvider.HTTPStatus()) http.Error(w, err.Error(), statusProvider.HTTPStatus())

View file

@ -14,7 +14,7 @@ func parseAndReplaceConfig(jsonData string) error {
return err return err
} }
validationErrors := replaceConfig(m) validationErrors, _ := ReplaceConfig(m)
if len(validationErrors) > 0 { if len(validationErrors) > 0 {
return fmt.Errorf("%d errors, first: %w", len(validationErrors), validationErrors[0]) return fmt.Errorf("%d errors, first: %w", len(validationErrors), validationErrors[0])
} }
@ -27,7 +27,7 @@ func parseAndReplaceDefaultConfig(jsonData string) error {
return err return err
} }
validationErrors := replaceDefaultConfig(m) validationErrors, _ := ReplaceDefaultConfig(m)
if len(validationErrors) > 0 { if len(validationErrors) > 0 {
return fmt.Errorf("%d errors, first: %w", len(validationErrors), validationErrors[0]) return fmt.Errorf("%d errors, first: %w", len(validationErrors), validationErrors[0])
} }

View file

@ -69,7 +69,7 @@ func start() error {
err = loadConfig(false) err = loadConfig(false)
if err != nil && !errors.Is(err, fs.ErrNotExist) { if err != nil && !errors.Is(err, fs.ErrNotExist) {
return err return fmt.Errorf("failed to load config file: %w", err)
} }
return nil return nil
} }

View file

@ -325,6 +325,29 @@ func (option *Option) IsSetByUser() bool {
return option.activeValue != nil return option.activeValue != nil
} }
// UserValue returns the value set by the user or nil if the value has not
// been changed from the default.
func (option *Option) UserValue() any {
option.Lock()
defer option.Unlock()
if option.activeValue == nil {
return nil
}
return option.activeValue.getData(option)
}
// ValidateValue checks if the given value is valid for the option.
func (option *Option) ValidateValue(value any) error {
option.Lock()
defer option.Unlock()
if _, err := validateValue(option, value); err != nil {
return err
}
return nil
}
// Export expors an option to a Record. // Export expors an option to a Record.
func (option *Option) Export() (record.Record, error) { func (option *Option) Export() (record.Record, error) {
option.Lock() option.Lock()

View file

@ -45,7 +45,7 @@ func loadConfig(requireValidConfig bool) error {
return err return err
} }
validationErrors := replaceConfig(newValues) validationErrors, _ := ReplaceConfig(newValues)
if requireValidConfig && len(validationErrors) > 0 { if requireValidConfig && len(validationErrors) > 0 {
return fmt.Errorf("encountered %d validation errors during config loading", len(validationErrors)) return fmt.Errorf("encountered %d validation errors during config loading", len(validationErrors))
} }

View file

@ -37,70 +37,112 @@ func signalChanges() {
module.TriggerEvent(ChangeEvent, nil) module.TriggerEvent(ChangeEvent, nil)
} }
// replaceConfig sets the (prioritized) user defined config. // ValidateConfig validates the given configuration and returns all validation
func replaceConfig(newValues map[string]interface{}) []*ValidationError { // errors as well as whether the given configuration contains unknown keys.
var validationErrors []*ValidationError func ValidateConfig(newValues map[string]interface{}) (validationErrors []*ValidationError, requiresRestart bool, containsUnknown bool) {
// RLock the options because we are not adding or removing // RLock the options because we are not adding or removing
// options from the registration but rather only update the // options from the registration but rather only checking the
// options value which is guarded by the option's lock itself // options value which is guarded by the option's lock itself.
optionsLock.RLock() optionsLock.RLock()
defer optionsLock.RUnlock() defer optionsLock.RUnlock()
var checked int
for key, option := range options { for key, option := range options {
newValue, ok := newValues[key] newValue, ok := newValues[key]
option.Lock()
option.activeValue = nil
if ok { if ok {
valueCache, err := validateValue(option, newValue) checked++
if err == nil {
option.activeValue = valueCache
} else {
validationErrors = append(validationErrors, err)
}
}
handleOptionUpdate(option, true) func() {
option.Unlock() option.Lock()
defer option.Unlock()
_, err := validateValue(option, newValue)
if err != nil {
validationErrors = append(validationErrors, err)
}
if option.RequiresRestart {
requiresRestart = true
}
}()
}
} }
signalChanges() return validationErrors, requiresRestart, checked < len(newValues)
return validationErrors
} }
// replaceDefaultConfig sets the (fallback) default config. // ReplaceConfig sets the (prioritized) user defined config.
func replaceDefaultConfig(newValues map[string]interface{}) []*ValidationError { func ReplaceConfig(newValues map[string]interface{}) (validationErrors []*ValidationError, requiresRestart bool) {
var validationErrors []*ValidationError
// RLock the options because we are not adding or removing // RLock the options because we are not adding or removing
// options from the registration but rather only update the // options from the registration but rather only update the
// options value which is guarded by the option's lock itself // options value which is guarded by the option's lock itself.
optionsLock.RLock() optionsLock.RLock()
defer optionsLock.RUnlock() defer optionsLock.RUnlock()
for key, option := range options { for key, option := range options {
newValue, ok := newValues[key] newValue, ok := newValues[key]
option.Lock() func() {
option.activeDefaultValue = nil option.Lock()
if ok { defer option.Unlock()
valueCache, err := validateValue(option, newValue)
if err == nil { option.activeValue = nil
option.activeDefaultValue = valueCache if ok {
} else { valueCache, err := validateValue(option, newValue)
validationErrors = append(validationErrors, err) if err == nil {
option.activeValue = valueCache
} else {
validationErrors = append(validationErrors, err)
}
} }
} handleOptionUpdate(option, true)
handleOptionUpdate(option, true)
option.Unlock() if option.RequiresRestart {
requiresRestart = true
}
}()
} }
signalChanges() signalChanges()
return validationErrors return validationErrors, requiresRestart
}
// ReplaceDefaultConfig sets the (fallback) default config.
func ReplaceDefaultConfig(newValues map[string]interface{}) (validationErrors []*ValidationError, requiresRestart bool) {
// RLock the options because we are not adding or removing
// options from the registration but rather only update the
// options value which is guarded by the option's lock itself.
optionsLock.RLock()
defer optionsLock.RUnlock()
for key, option := range options {
newValue, ok := newValues[key]
func() {
option.Lock()
defer option.Unlock()
option.activeDefaultValue = nil
if ok {
valueCache, err := validateValue(option, newValue)
if err == nil {
option.activeDefaultValue = valueCache
} else {
validationErrors = append(validationErrors, err)
}
}
handleOptionUpdate(option, true)
if option.RequiresRestart {
requiresRestart = true
}
}()
}
signalChanges()
return validationErrors, requiresRestart
} }
// SetConfigOption sets a single value in the (prioritized) user defined config. // SetConfigOption sets a single value in the (prioritized) user defined config.

View file

@ -24,7 +24,7 @@ func TestLayersGetters(t *testing.T) { //nolint:paralleltest
t.Fatal(err) t.Fatal(err)
} }
validationErrors := replaceConfig(mapData) validationErrors, _ := ReplaceConfig(mapData)
if len(validationErrors) > 0 { if len(validationErrors) > 0 {
t.Fatalf("%d errors, first: %s", len(validationErrors), validationErrors[0].Error()) t.Fatalf("%d errors, first: %s", len(validationErrors), validationErrors[0].Error())
} }