github.com/AbsaOSS/env-binder@v1.0.1/env/bind.go (about) 1 /* 2 Copyright 2021 The k8gb Contributors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 16 Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic 17 */ 18 19 package env 20 21 import ( 22 "fmt" 23 "os" 24 "reflect" 25 "regexp" 26 "strconv" 27 "strings" 28 "unsafe" 29 ) 30 31 type field struct { 32 env env 33 fieldName string 34 fieldType *reflect.Type 35 fieldValue *reflect.Value 36 public bool 37 } 38 39 // contains raw info about string tag field. e.g: default=hello, 40 type strTag struct { 41 value string 42 exists bool 43 } 44 45 type env struct { 46 value string 47 name string 48 tagName string 49 def strTag 50 req strTag 51 protected strTag 52 present bool 53 } 54 55 type meta map[string]field 56 57 // Bind binds environment variables into structure 58 func Bind(s interface{}) (err error) { 59 var meta meta 60 if s == nil { 61 return fmt.Errorf("invalid argument value (nil)") 62 } 63 v := reflect.ValueOf(s) 64 t := reflect.TypeOf(s).Kind() 65 if t != reflect.Ptr { 66 return fmt.Errorf("argument must be pointer to structure") 67 } 68 if v.Elem().Kind() != reflect.Struct { 69 return fmt.Errorf("argument must be pointer to structure") 70 } 71 meta, err = roll(v.Elem(), v.Elem().Type().Name(), "") 72 if err != nil { 73 return 74 } 75 err = bind(meta) 76 return 77 } 78 79 // binds meta to structure pointer 80 func bind(m meta) (err error) { 81 for k, v := range m { 82 f := reflect.NewAt(v.fieldValue.Type(), unsafe.Pointer(v.fieldValue.UnsafeAddr())).Elem() 83 switch f.Interface().(type) { 84 case bool: 85 var b bool 86 if v.env.protected.isTrue() { 87 continue 88 } 89 b, err = boolean(v.env) 90 if err != nil { 91 return 92 } 93 f.SetBool(b) 94 continue 95 96 case int, int8, int16, int32, int64: 97 if v.env.protected.isTrue() && v.fieldValue.Int() != 0 { 98 continue 99 } 100 err = setNumeric(f, v) 101 if err != nil { 102 return 103 } 104 continue 105 106 case float32, float64: 107 if v.env.protected.isTrue() && v.fieldValue.Float() != 0 { 108 continue 109 } 110 err = setNumeric(f, v) 111 if err != nil { 112 return 113 } 114 continue 115 116 case uint, uint8, uint16, uint32, uint64: 117 if v.env.protected.isTrue() && v.fieldValue.Uint() != 0 { 118 continue 119 } 120 err = setNumeric(f, v) 121 if err != nil { 122 return 123 } 124 continue 125 126 case string: 127 var s string 128 if v.env.protected.isTrue() && v.fieldValue.String() != "" { 129 continue 130 } 131 s = GetEnvAsStringOrFallback(v.env.name, v.env.def.value) 132 f.SetString(s) 133 continue 134 135 case []string: 136 if v.env.protected.isTrue() && !v.fieldValue.IsNil() { 137 continue 138 } 139 var ss []string 140 ss = GetEnvAsArrayOfStringsOrFallback(v.env.name, v.env.def.asStringSlice()) 141 f.Set(reflect.ValueOf(ss)) 142 continue 143 144 case []int, []int8, []int16, []int32, []int64, []float32, []float64, []uint, []uint8, []uint16, []uint32, []uint64: 145 if v.env.protected.isTrue() && !v.fieldValue.IsNil() { 146 continue 147 } 148 var floats []float64 149 floats, err = floatSlice(v.env) 150 if err != nil { 151 return 152 } 153 setNumericSlice(f, floats) 154 continue 155 156 case []bool: 157 if v.env.protected.isTrue() && !v.fieldValue.IsNil() { 158 continue 159 } 160 var bs []bool 161 bs, err = boolSlice(v.env) 162 if err != nil { 163 return 164 } 165 f.Set(reflect.ValueOf(bs)) 166 continue 167 168 default: 169 err = fmt.Errorf("unsupported type %s: %s", k, v.fieldValue.Type().Name()) 170 } 171 } 172 return err 173 } 174 175 func setNumericSlice(f reflect.Value, floats []float64) { 176 switch f.Interface().(type) { 177 case []uint: 178 f.Set(reflect.ValueOf(convertToUInt(floats))) 179 return 180 case []uint8: 181 f.Set(reflect.ValueOf(convertToUInt8(floats))) 182 return 183 case []uint16: 184 f.Set(reflect.ValueOf(convertToUInt16(floats))) 185 return 186 case []uint32: 187 f.Set(reflect.ValueOf(convertToUInt32(floats))) 188 return 189 case []uint64: 190 f.Set(reflect.ValueOf(convertToUInt64(floats))) 191 return 192 case []int: 193 f.Set(reflect.ValueOf(convertToInt(floats))) 194 return 195 case []int8: 196 f.Set(reflect.ValueOf(convertToInt8(floats))) 197 return 198 case []int16: 199 f.Set(reflect.ValueOf(convertToInt16(floats))) 200 return 201 case []int32: 202 f.Set(reflect.ValueOf(convertToInt32(floats))) 203 return 204 case []int64: 205 f.Set(reflect.ValueOf(convertToInt64(floats))) 206 return 207 case []float32: 208 f.Set(reflect.ValueOf(convertToFloat32(floats))) 209 return 210 case []float64: 211 f.Set(reflect.ValueOf(floats)) 212 return 213 } 214 } 215 216 func setNumeric(f reflect.Value, v field) (err error) { 217 var fl float64 218 fl, err = float(v.env) 219 if err != nil { 220 return 221 } 222 switch f.Interface().(type) { 223 case int, int8, int16, int32, int64: 224 f.SetInt(int64(fl)) 225 return 226 case float32, float64: 227 f.SetFloat(fl) 228 return 229 case uint, uint8, uint16, uint32, uint64: 230 f.SetUint(uint64(fl)) 231 return 232 } 233 return 234 } 235 236 // recoursive function builds meta structure 237 func roll(value reflect.Value, n, prefix string) (m meta, err error) { 238 const tagEnv = "env" 239 240 m = meta{} 241 for i := 0; i < value.NumField(); i++ { 242 var e env 243 vf := value.Field(i) 244 tf := value.Type().Field(i) 245 key := fmt.Sprintf("%s.%s", n, tf.Name) 246 tag := tf.Tag.Get(tagEnv) 247 if vf.Kind() == reflect.Struct { 248 var sm meta 249 prefix := strings.TrimPrefix(fmt.Sprintf("%s_%s", prefix, getTagName(tag)), "_") 250 sm, err = roll(vf, key, prefix) 251 if err != nil { 252 return 253 } 254 for k, v := range sm { 255 m[k] = v 256 } 257 continue 258 } 259 if tag == "" { 260 continue 261 } 262 if e, err = parseTag(tag, prefix); err != nil { 263 return 264 } 265 if !e.present && e.req.value == "true" { 266 err = fmt.Errorf("%s is required", e.name) 267 return 268 } 269 m[key] = field{ 270 env: e, 271 fieldName: tf.Name, 272 fieldType: &tf.Type, 273 fieldValue: &vf, 274 public: tf.PkgPath == "", 275 } 276 } 277 return m, err 278 } 279 280 // parseTag, retrieves env info and metadata 281 func parseTag(tag, prefix string) (e env, err error) { 282 var def, req, protected strTag 283 var tagName = getTagName(tag) 284 req, err = getTagProperty(tag, "require") 285 if err != nil { 286 return 287 } 288 def, err = getTagProperty(tag, "default") 289 if err != nil { 290 return 291 } 292 protected, err = getTagProperty(tag, "protected") 293 if err != nil { 294 return 295 } 296 envName := getEnvName(tagName, prefix) 297 value, exists := os.LookupEnv(envName) 298 e = env{ 299 name: envName, 300 tagName: tagName, 301 value: value, 302 req: req, 303 def: def, 304 protected: protected, 305 present: exists, 306 } 307 return 308 } 309 310 func getEnvName(envName, prefix string) string { 311 if prefix != "" { 312 return fmt.Sprintf("%s_%s", prefix, envName) 313 } 314 return envName 315 } 316 317 func getTagName(tag string) string { 318 return regexp.MustCompile("[a-zA-Z_]+[a-zA-Z0-9_]*").FindString(tag) 319 } 320 321 // parses value from env tag and returns <tag value, tag value exists, error> 322 func getTagProperty(tag, t string) (r strTag, err error) { 323 const arr = `\[\w*\s*\!*\@*\#*\$*\%*\^*\&*\**\(*\)*\_*\-*\+*\<*\>*\?*\~*\=*\,*\.*\/*\{*\}*\|*\;*\:*\/*\'*\"*\/*\\*` 324 const scalar = `\[*\]*\w*\s*\!*\@*\#*\$*\%*\^*\&*\**\(*\)*\_*\-*\+*\<*\>*\?*\~*\=*\.*\/*\{*\}*\|*\;*\:*\/*\'*\"*\/*\\*` 325 r = strTag{} 326 var findRegex, removeRegex *regexp.Regexp 327 // findRegex, err = regexp.Compile(",\\s*" + t + "\\s*=((\\s*([\\[\\w*\\,*\\.*\\s*\\-*])*\\])|(\\s*\\w*\\.*\\-*)*)") 328 findRegex, err = regexp.Compile(",\\s*" + t + "\\s*=((\\s*\\[[" + arr + "]*\\])|(" + scalar + ")*)") 329 if err != nil { 330 err = fmt.Errorf("ivalid %s", t) 331 return 332 } 333 removeRegex, err = regexp.Compile(",\\s*" + t + "\\s*=\\s*") 334 if err != nil { 335 err = fmt.Errorf("ivalid %s", t) 336 return 337 } 338 match := findRegex.FindString(tag) 339 if match == "" { 340 return 341 } 342 remove := removeRegex.FindString(strings.ToLower(tag)) 343 r.value = strings.ReplaceAll(match, remove, "") 344 r.exists = true 345 return 346 } 347 348 func (t strTag) asStringSlice() (s []string) { 349 if !t.exists { 350 return 351 } 352 envdef := strings.TrimSuffix(strings.TrimPrefix(t.value, " "), " ") 353 envdef = strings.TrimSuffix(strings.TrimPrefix(envdef, "["), "]") 354 s = strings.Split(envdef, ",") 355 if s[0] == "" { 356 s = []string{} 357 } 358 return 359 } 360 361 func (t strTag) isTrue() bool { 362 if !t.exists { 363 return false 364 } 365 b, _ := strconv.ParseBool(t.value) 366 return b 367 }