-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.go
128 lines (111 loc) · 3.42 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package main
import (
"errors"
"fmt"
"log"
"os"
"reflect"
"strconv"
"github.com/hashicorp/go-multierror"
)
func FillEnvTags(confStructPtr interface{}) error {
value := reflect.ValueOf(confStructPtr)
if value.Kind() != reflect.Ptr {
return errors.New("must pass a pointer, not a value")
}
if value.IsNil() {
return errors.New("must pass a non nil pointer")
}
if value.Type().Elem().Kind() != reflect.Struct {
return errors.New("must pass a pointer to a struct")
}
// use error count and wrapped errors instead of failing early
// so we get a full list of problems/missing env var in output
errorCount := 0
var errorChain error
numberKindBitSize := map[reflect.Kind]int {
reflect.Int: 0, // auto
reflect.Uint: 0, // auto
reflect.Int8: 8,
reflect.Uint8: 8,
reflect.Int16: 16,
reflect.Uint16: 16,
reflect.Int32: 32,
reflect.Uint32: 32,
reflect.Int64: 64,
reflect.Uint64: 64,
}
isTruthy := func(str string) bool {
return str != "" && str != "0" && str != "false" && str != "FALSE"
}
direct := value.Elem() // already checked its of kind ptr
directType := direct.Type()
for i := 0; i < direct.NumField(); i++ {
tag := directType.Field(i).Tag
tagEnvName := tag.Get("env")
if tagEnvName == "" {
continue
}
f := direct.Field(i)
if !f.CanSet() {
errorCount++
errorChain = multierror.Append(errorChain, fmt.Errorf("unable to set %s of type %s", directType.Field(i).Name, f.Kind(),))
continue
}
envRequiredVal := tag.Get("required")
envVar, envVarSet := os.LookupEnv(tagEnvName)
if !envVarSet && isTruthy(envRequiredVal) {
errorCount++
errorChain = multierror.Append(errorChain, fmt.Errorf("unable to set %s of type %s, required environment variable '%s' missing", directType.Field(i).Name, f.Kind(), tagEnvName))
continue
}
switch f.Kind() {
case reflect.String:
f.SetString(envVar)
case reflect.Bool:
f.SetBool(isTruthy(envVar))
case reflect.Int8: fallthrough
case reflect.Int16: fallthrough
case reflect.Int32: fallthrough
case reflect.Int64: fallthrough
case reflect.Int:
asInt, asIntErr := strconv.ParseInt(envVar, 10, numberKindBitSize[f.Kind()])
if asIntErr != nil {
errorCount++
errorChain = multierror.Append(errorChain, fmt.Errorf("unable to set %s of type %s from %s: %w", directType.Field(i).Name, f.Kind(), tagEnvName, asIntErr))
continue
}
f.SetInt(asInt)
case reflect.Uint8: fallthrough
case reflect.Uint16: fallthrough
case reflect.Uint32: fallthrough
case reflect.Uint64: fallthrough
case reflect.Uint:
asUint, asUintErr := strconv.ParseUint(envVar, 10, numberKindBitSize[f.Kind()])
if asUintErr != nil {
errorCount++
errorChain = multierror.Append(errorChain, fmt.Errorf("unable to set %s of type %s from %s: %w", directType.Field(i).Name, f.Kind(), tagEnvName, asUintErr))
continue
}
f.SetUint(asUint)
default:
errorCount++
errorChain = multierror.Append(errorChain, fmt.Errorf("unable to set %s of type %s (supported)", directType.Field(i).Name, f.Kind()))
continue
}
}
return errorChain
}
type Config struct {
Debug bool `env:"DEBUG"`
Environment string `env:"APP_ENV" required:"true"`
DbConnectionDSN string `env:"DATABASE_DSN" required:"true"`
}
func ConfigExample() {
config := Config{}
confErr := FillEnvTags(&config)
if confErr != nil {
log.Fatalf("unable to make config: %v", confErr)
}
fmt.Printf("successfuly made config: %+v", config)
}