golanggohlsrtmpwebrtcmedia-serverobs-studiortcprtmp-proxyrtmp-serverrtprtsprtsp-proxyrtsp-relayrtsp-serversrtstreamingwebrtc-proxy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
233 lines
5.0 KiB
233 lines
5.0 KiB
// Package env contains a function to load configuration from environment. |
|
package env |
|
|
|
import ( |
|
"fmt" |
|
"os" |
|
"reflect" |
|
"strconv" |
|
"strings" |
|
) |
|
|
|
// Unmarshaler can be implemented to override the unmarshaling process. |
|
type Unmarshaler interface { |
|
UnmarshalEnv(prefix string, v string) error |
|
} |
|
|
|
func envHasAtLeastAKeyWithPrefix(env map[string]string, prefix string) bool { |
|
for key := range env { |
|
if strings.HasPrefix(key, prefix) { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) error { |
|
if prv.Kind() != reflect.Pointer { |
|
return loadEnvInternal(env, prefix, prv.Addr()) |
|
} |
|
|
|
rt := prv.Type().Elem() |
|
|
|
if i, ok := prv.Interface().(Unmarshaler); ok { |
|
if ev, ok := env[prefix]; ok { |
|
if prv.IsNil() { |
|
prv.Set(reflect.New(rt)) |
|
i = prv.Interface().(Unmarshaler) |
|
} |
|
err := i.UnmarshalEnv(prefix, ev) |
|
if err != nil { |
|
return fmt.Errorf("%s: %w", prefix, err) |
|
} |
|
} else if envHasAtLeastAKeyWithPrefix(env, prefix) { |
|
err := i.UnmarshalEnv(prefix, "") |
|
if err != nil { |
|
return fmt.Errorf("%s: %w", prefix, err) |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
switch rt { |
|
case reflect.TypeOf(""): |
|
if ev, ok := env[prefix]; ok { |
|
if prv.IsNil() { |
|
prv.Set(reflect.New(rt)) |
|
} |
|
prv.Elem().SetString(ev) |
|
} |
|
return nil |
|
|
|
case reflect.TypeOf(int(0)): |
|
if ev, ok := env[prefix]; ok { |
|
if prv.IsNil() { |
|
prv.Set(reflect.New(rt)) |
|
} |
|
iv, err := strconv.ParseInt(ev, 10, 32) |
|
if err != nil { |
|
return fmt.Errorf("%s: %w", prefix, err) |
|
} |
|
prv.Elem().SetInt(iv) |
|
} |
|
return nil |
|
|
|
case reflect.TypeOf(uint64(0)): |
|
if ev, ok := env[prefix]; ok { |
|
if prv.IsNil() { |
|
prv.Set(reflect.New(rt)) |
|
} |
|
iv, err := strconv.ParseUint(ev, 10, 32) |
|
if err != nil { |
|
return fmt.Errorf("%s: %w", prefix, err) |
|
} |
|
prv.Elem().SetUint(iv) |
|
} |
|
return nil |
|
|
|
case reflect.TypeOf(float64(0)): |
|
if ev, ok := env[prefix]; ok { |
|
if prv.IsNil() { |
|
prv.Set(reflect.New(rt)) |
|
} |
|
iv, err := strconv.ParseFloat(ev, 64) |
|
if err != nil { |
|
return fmt.Errorf("%s: %w", prefix, err) |
|
} |
|
prv.Elem().SetFloat(iv) |
|
} |
|
return nil |
|
|
|
case reflect.TypeOf(bool(false)): |
|
if ev, ok := env[prefix]; ok { |
|
if prv.IsNil() { |
|
prv.Set(reflect.New(rt)) |
|
} |
|
switch strings.ToLower(ev) { |
|
case "yes", "true": |
|
prv.Elem().SetBool(true) |
|
|
|
case "no", "false": |
|
prv.Elem().SetBool(false) |
|
|
|
default: |
|
return fmt.Errorf("%s: invalid value '%s'", prefix, ev) |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
switch rt.Kind() { |
|
case reflect.Map: |
|
for k := range env { |
|
if !strings.HasPrefix(k, prefix+"_") { |
|
continue |
|
} |
|
|
|
mapKey := strings.Split(k[len(prefix+"_"):], "_")[0] |
|
if len(mapKey) == 0 { |
|
continue |
|
} |
|
|
|
// allow only keys in uppercase |
|
if mapKey != strings.ToUpper(mapKey) { |
|
continue |
|
} |
|
|
|
// initialize only if there's at least one key |
|
if prv.Elem().IsNil() { |
|
prv.Elem().Set(reflect.MakeMap(rt)) |
|
} |
|
|
|
mapKeyLower := strings.ToLower(mapKey) |
|
nv := prv.Elem().MapIndex(reflect.ValueOf(mapKeyLower)) |
|
zero := reflect.Value{} |
|
if nv == zero { |
|
nv = reflect.New(rt.Elem().Elem()) |
|
prv.Elem().SetMapIndex(reflect.ValueOf(mapKeyLower), nv) |
|
} |
|
|
|
err := loadEnvInternal(env, prefix+"_"+mapKey, nv.Elem()) |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
|
|
case reflect.Struct: |
|
flen := rt.NumField() |
|
for i := 0; i < flen; i++ { |
|
f := rt.Field(i) |
|
jsonTag := f.Tag.Get("json") |
|
|
|
// load only public fields |
|
if jsonTag == "-" { |
|
continue |
|
} |
|
|
|
err := loadEnvInternal(env, prefix+"_"+ |
|
strings.ToUpper(strings.TrimSuffix(jsonTag, ",omitempty")), prv.Elem().Field(i)) |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
|
|
case reflect.Slice: |
|
switch { |
|
case rt.Elem() == reflect.TypeOf(""): |
|
if ev, ok := env[prefix]; ok { |
|
if ev == "" { |
|
prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0)) |
|
} else { |
|
if prv.IsNil() { |
|
prv.Set(reflect.New(rt)) |
|
} |
|
prv.Elem().Set(reflect.ValueOf(strings.Split(ev, ","))) |
|
} |
|
} |
|
return nil |
|
|
|
case rt.Elem().Kind() == reflect.Struct: |
|
if ev, ok := env[prefix]; ok && ev == "" { // special case: empty list |
|
prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0)) |
|
} else { |
|
for i := 0; ; i++ { |
|
itemPrefix := prefix + "_" + strconv.FormatInt(int64(i), 10) |
|
if !envHasAtLeastAKeyWithPrefix(env, itemPrefix) { |
|
break |
|
} |
|
|
|
elem := reflect.New(rt.Elem()) |
|
err := loadEnvInternal(env, itemPrefix, elem.Elem()) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
prv.Elem().Set(reflect.Append(prv.Elem(), elem.Elem())) |
|
} |
|
} |
|
return nil |
|
} |
|
} |
|
|
|
return fmt.Errorf("unsupported type: %v", rt) |
|
} |
|
|
|
func loadWithEnv(env map[string]string, prefix string, v interface{}) error { |
|
return loadEnvInternal(env, prefix, reflect.ValueOf(v).Elem()) |
|
} |
|
|
|
func envToMap() map[string]string { |
|
env := make(map[string]string) |
|
for _, kv := range os.Environ() { |
|
tmp := strings.SplitN(kv, "=", 2) |
|
env[tmp[0]] = tmp[1] |
|
} |
|
return env |
|
} |
|
|
|
// Load loads the configuration from the environment. |
|
func Load(prefix string, v interface{}) error { |
|
return loadWithEnv(envToMap(), prefix, v) |
|
}
|
|
|