sshpoke/internal/config/viper.go
2023-11-16 20:09:40 +03:00

67 lines
1.8 KiB
Go

package config
import (
"strings"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"
"github.com/spf13/viper"
)
// BindStructEnv binds struct using environment variables.
// Note: borrowed, see https://github.com/spf13/viper/pull/1429
func BindStructEnv(input interface{}) error {
envKeysMap := map[string]interface{}{}
if err := mapstructure.Decode(input, &envKeysMap); err != nil {
return err
}
structKeys := flattenAndMergeMap(map[string]bool{}, envKeysMap, "")
for key := range structKeys {
if err := viper.BindEnv(key); err != nil {
return err
}
}
return nil
}
// FlattenAndMergeMap recursively flattens the given map into a map[string]bool
// of key paths (used as a set, easier to manipulate than a []string):
// - each path is merged into a single key string, delimited with v.keyDelim
// - if a path is shadowed by an earlier value in the initial shadow map,
// it is skipped.
//
// The resulting set of paths is merged to the given shadow set at the same time.
// Note: borrowed, see https://github.com/spf13/viper/pull/1429
func flattenAndMergeMap(shadow map[string]bool, m map[string]any, prefix string) map[string]bool {
if shadow != nil && prefix != "" && shadow[prefix] {
// prefix is shadowed => nothing more to flatten
return shadow
}
if shadow == nil {
shadow = make(map[string]bool)
}
var m2 map[string]any
if prefix != "" {
prefix += "." // assuming that delimiter is a dot.
}
for k, val := range m {
fullKey := prefix + k
switch val := val.(type) {
case map[string]any:
m2 = val
case map[any]any:
m2 = cast.ToStringMap(val)
default:
// immediate value
shadow[strings.ToLower(fullKey)] = true
continue
}
// recursively merge to shadow map
shadow = flattenAndMergeMap(shadow, m2, fullKey)
}
return shadow
}