github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/config/env.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/configtypes"
    11  )
    12  
    13  // loadFromEnv loads configuration from environment variables
    14  func loadFromEnv(prefix string, destination *configtypes.Config) (err error) {
    15  	// First we get all env variables then filter by prefix and finally parse the values
    16  	envs := os.Environ()
    17  	for i := 0; i < len(envs); i++ {
    18  		// Turn VAR=value into []string{"VAR", "value"}
    19  		parsed := strings.Split(envs[i], "=")
    20  		if !strings.HasPrefix(parsed[0], prefix) {
    21  			continue
    22  		}
    23  
    24  		// Turn PARENT_CHILD into []string{"PARENT", "CHILD"}
    25  		path := strings.Split(parsed[0][len(prefix):], "_")
    26  		if err := setByPath(destination, path, parsed[1]); err != nil {
    27  			return err
    28  		}
    29  	}
    30  	return nil
    31  }
    32  
    33  // setByPath matches struct field by a string path and sets its value
    34  func setByPath(structure any, path []string, value string) error {
    35  	v := reflect.ValueOf(structure)
    36  	return deepSetByPath(&v, path, value)
    37  }
    38  
    39  // deepSetByPath follows path recursively until it reaches a single struct field.
    40  // Then it parses the value and sets the field.
    41  func deepSetByPath(structure *reflect.Value, path []string, value string) error {
    42  	structValue := *structure
    43  	// Follow pointers
    44  	if structValue.Kind() == reflect.Pointer {
    45  		structValue = structValue.Elem()
    46  	}
    47  
    48  	structType := structValue.Type()
    49  	fieldCount := structType.NumField()
    50  	makeParseError := func(err error) error {
    51  		return fmt.Errorf("parsing %v value: %w", path, err)
    52  	}
    53  	for i := 0; i < fieldCount; i++ {
    54  		field := structType.Field(i)
    55  		if !strings.EqualFold(field.Name, path[0]) {
    56  			continue
    57  		}
    58  
    59  		fieldValue := structValue.Field(i)
    60  		if !fieldValue.CanSet() {
    61  			return fmt.Errorf("cannot set %s", field.Name)
    62  		}
    63  		switch fieldValue.Kind() {
    64  		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    65  			i, err := strconv.ParseInt(value, 0, 64)
    66  			if err != nil {
    67  				return makeParseError(err)
    68  			}
    69  			fieldValue.SetInt(i)
    70  		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
    71  			u, err := strconv.ParseUint(value, 0, 64)
    72  			if err != nil {
    73  				return makeParseError(err)
    74  			}
    75  			fieldValue.SetUint(u)
    76  		case reflect.Bool:
    77  			b, err := strconv.ParseBool(value)
    78  			if err != nil {
    79  				return makeParseError(err)
    80  			}
    81  			fieldValue.SetBool(b)
    82  		case reflect.String:
    83  			fieldValue.SetString(value)
    84  		case reflect.Map:
    85  			// When dealing with maps, we first have to obtain values for map key and map value
    86  			mapKey := reflect.ValueOf(strings.ToLower(path[1]))
    87  			mapValue := reflect.New(field.Type.Elem())
    88  
    89  			// If map value is nil, it's simple: we will use mapValue that we've created above
    90  			// and assign values to its fields (we assume that map items are always structs, because
    91  			// nothing else makes sense for configuration).
    92  			// However, if the map value exists, we have to clone the existing struct (the value) into
    93  			// mapValue and reassign mapValue to mapKey (modifying map value will cause panic)
    94  			if !fieldValue.IsNil() {
    95  				existing := fieldValue.MapIndex(mapKey)
    96  				if err := cloneStruct(&mapValue, &existing); err != nil {
    97  					return err
    98  				}
    99  			}
   100  
   101  			// Since all maps in Config have structs as values, we use recursion to set the value
   102  			// of a correct field of the struct
   103  			if err := deepSetByPath(&mapValue, path[2:], value); err != nil {
   104  				return err
   105  			}
   106  			// SetMapIndex below needs a concrete type, not a pointer
   107  			if mapValue.Kind() == reflect.Pointer {
   108  				mapValue = mapValue.Elem()
   109  			}
   110  			// If the map is nil, we have to initialize it (just like `make(map[t1]t2)` does)
   111  			if fieldValue.IsNil() {
   112  				// MakeMap initializes a map. reflect.MapOf gives us the map type.
   113  				newMap := reflect.MakeMap(reflect.MapOf(mapKey.Type(), mapValue.Type()))
   114  				fieldValue.Set(newMap)
   115  			}
   116  			// We set the key to the correct value
   117  			fieldValue.SetMapIndex(mapKey, mapValue)
   118  		case reflect.Struct, reflect.Interface:
   119  			if len(path) > 1 {
   120  				return deepSetByPath(&fieldValue, path[1:], value)
   121  			}
   122  			return fmt.Errorf("expected value, but got struct for %v", path)
   123  		default:
   124  			return fmt.Errorf("unsupported type for %v", path)
   125  		}
   126  	}
   127  	return nil
   128  }
   129  
   130  // cloneStruct sets fields of destination to the same values as found in source.
   131  // The two structs have to be of the same type.
   132  func cloneStruct(destination *reflect.Value, source *reflect.Value) error {
   133  	v := *source
   134  	// Follow pointers
   135  	if v.Kind() == reflect.Pointer {
   136  		v = v.Elem()
   137  	}
   138  
   139  	fieldCount := v.Type().NumField()
   140  	for i := 0; i < fieldCount; i++ {
   141  		fieldType := v.Type().Field(i)
   142  		fieldValue := v.Field(i)
   143  		if fieldValue.IsZero() {
   144  			continue
   145  		}
   146  
   147  		if kind := destination.Kind(); kind != reflect.Pointer && kind != reflect.Interface {
   148  			return fmt.Errorf("expected pointer to %s", fieldType.Name)
   149  		}
   150  		destValue := destination.Elem()
   151  		if destValue.Kind() != reflect.Struct {
   152  			return fmt.Errorf("expected %s to be a struct", fieldType.Name)
   153  		}
   154  		destField := destValue.FieldByName(fieldType.Name)
   155  		if !destField.CanSet() {
   156  			return fmt.Errorf("cannot set %s", fieldType.Name)
   157  		}
   158  
   159  		destField.Set(fieldValue)
   160  	}
   161  	return nil
   162  }