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  }