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 }