config updated to improve setting values, writing changes and test coverage

This commit is contained in:
bwilliams committed 2026-10-13 13:19:01 +01:00
1 parent 8f0e72821f
commit 169c6c6b64
8 files changed
+453 -59

No files matched your search

+72 -40
View File
@@ -10,74 +10,106 @@ import (
)
// StoreValue parses value as the type of key, checks conf accepts it, sets it
// in conf and Viper and writes the config file Viper is using. It returns the
// parsed value and its kind.
// in conf and Viper and writes it to the config file Viper is using. Only the
// file's own settings are written back, not defaults or environment
// variables. It returns the stored value (as ValueOf gives it) and its kind.
func StoreValue(
conf *JobSearchConfig, key string, value string,
) (any, reflect.Kind, error) {
var confValue any = nil
var confType reflect.Kind
var err error
return storeValue(conf, key, value)
}
confType, err = conf.TypeOf(key)
// storeValue is StoreValue for any section, so key types that
// JobSearchConfig doesn't have yet can be tested.
func storeValue(s Section, key string, value string) (any, reflect.Kind, error) {
confType, err := s.TypeOf(key)
if err != nil {
return nil, confType, err
}
switch confType {
case reflect.Bool:
confValue, err = strconv.ParseBool(value)
case reflect.String:
confValue = value
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
confValue, err = strconv.ParseInt(value, 10, 64)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
confValue, err = strconv.ParseUint(value, 10, 64)
case reflect.Float32, reflect.Float64:
confValue, err = strconv.ParseFloat(value, 64)
default:
err = errors.ErrUnsupported
}
if err != nil {
// Check the value is of the right kind first, for a clearer error than
// SetValue gives.
if _, err := parseValue(confType, value); err != nil {
return nil, confType, fmt.Errorf(
"config value type not accepted, expected <%s>", confType,
)
}
// Reject values the configuration itself would not accept, e.g. an
// unknown log level or an unparseable duration.
if err := conf.SetValue(key, confValue); err != nil {
// SetValue parses the string into the field's own type (e.g. int8,
// float32 or time.Duration) and rejects values the configuration itself
// would not accept, e.g. an unknown log level or an unparseable duration.
if err := s.SetValue(key, value); err != nil {
return nil, confType, err
}
confValue, err := s.ValueOf(key)
if err != nil {
return nil, confType, err
}
viper.Set(key, confValue)
if err := viper.WriteConfig(); err != nil {
file, err := readUsedConfigFile()
if err != nil {
return nil, confType, err
}
file.Set(key, confValue)
if err := file.WriteConfig(); err != nil {
return nil, confType, err
}
return confValue, confType, nil
}
// PurgeValue rewrites the config file Viper is using without key.
// parseValue parses value as a value of kind, returning
// errors.ErrUnsupported for a kind it can't parse.
func parseValue(kind reflect.Kind, value string) (any, error) {
switch kind {
case reflect.Bool:
return strconv.ParseBool(value)
case reflect.String:
return value, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.ParseInt(value, 10, 64)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.ParseUint(value, 10, 64)
case reflect.Float32, reflect.Float64:
return strconv.ParseFloat(value, 64)
}
return nil, errors.ErrUnsupported
}
// PurgeValue rewrites the config file Viper is using without key. The
// file's other settings are kept as they are; defaults and environment
// variables are not written to it.
func PurgeValue(conf *JobSearchConfig, key string) error {
if _, err := conf.TypeOf(key); err != nil {
return err
}
v := viper.New()
v.SetConfigFile(viper.ConfigFileUsed())
keys := viper.AllKeys()
for i := range keys {
k := keys[i]
if k != key {
v.Set(k, viper.Get(k))
}
}
if err := v.WriteConfig(); err != nil {
file, err := readUsedConfigFile()
if err != nil {
return err
}
return nil
out := viper.New()
out.SetConfigFile(file.ConfigFileUsed())
for _, k := range file.AllKeys() {
if k != key {
out.Set(k, file.Get(k))
}
}
return out.WriteConfig()
}
// readUsedConfigFile reads the config file Viper is using into a new Viper, so
// it holds only the file's settings.
func readUsedConfigFile() (*viper.Viper, error) {
path := viper.ConfigFileUsed()
if path == "" {
return nil, errors.New("no configuration file in use")
}
file := viper.New()
file.SetConfigFile(path)
if err := file.ReadInConfig(); err != nil {
return nil, err
}
return file, nil
}