mirror of
				https://github.com/aler9/rtsp-simple-server
				synced 2025-10-31 02:56:29 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			259 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			259 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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, ok2 := env[prefix]; ok2 {
 | |
| 			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(uint(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() == reflect.TypeOf(float64(0)):
 | |
| 			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))
 | |
| 					}
 | |
| 
 | |
| 					raw := strings.Split(ev, ",")
 | |
| 					vals := make([]float64, len(raw))
 | |
| 
 | |
| 					for i, v := range raw {
 | |
| 						tmp, err := strconv.ParseFloat(v, 64)
 | |
| 						if err != nil {
 | |
| 							return err
 | |
| 						}
 | |
| 						vals[i] = tmp
 | |
| 					}
 | |
| 
 | |
| 					prv.Elem().Set(reflect.ValueOf(vals))
 | |
| 				}
 | |
| 			}
 | |
| 			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)
 | |
| }
 | 
