-
Notifications
You must be signed in to change notification settings - Fork 0
/
flags.go
147 lines (119 loc) · 3.06 KB
/
flags.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package clite
import (
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
)
const (
envTag = "env"
flagTag = "flag"
)
// Field represents a struct field
type Field struct {
path []string
name string
field reflect.StructField
value reflect.Value
}
// flattenStructFields returns a flat slice of Field from recursively traversing the struct fields of v.
// - unexported fields are omitted
// - fields marked with an env, flag or secret tag are included, but their children are not
func paramFields(ptr any) ([]Field, error) {
v := reflect.ValueOf(ptr)
if v.Kind() != reflect.Ptr {
return nil, fmt.Errorf("ptr should be a pointer")
}
v = v.Elem()
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("ptr should be a pointer to struct")
}
return flattenFields(v, nil), nil
}
func flattenFields(v reflect.Value, path []string) []Field {
t := v.Type()
var fields []Field
for i := 0; i < t.NumField(); i++ {
// skip unexported fields
if !t.Field(i).IsExported() {
continue
}
// get field comment
f := Field{
path: path,
name: t.Field(i).Name,
field: t.Field(i),
value: v.Field(i),
}
fields = append(fields, f)
// do not recurse into fields that have the env, flag or secret tags
_, env := f.envVar()
_, flag := f.flagName()
if env || flag {
continue
}
if f.field.Type.Kind() == reflect.Struct {
subFields := flattenFields(f.value, append(path, f.name))
fields = append(fields, subFields...)
}
}
return fields
}
// envVar returns the `env` tag value and a bool indicating if the field has the `env` tag.
func (f *Field) envVar() (string, bool) {
envVar := f.field.Tag.Get(envTag)
if envVar != "" {
return envVar, true
}
return "", false
}
// flagName returns the `flag` tag value and a bool indicating if the field has the `flag` tag.
func (f *Field) flagName() (string, bool) {
flagName := f.field.Tag.Get(flagTag)
if flagName != "" {
return flagName, true
}
return "", false
}
// setString sets the underlying field value from a string.
// - []byte fields are assumed to be base64 encoded
// - string fields are not pre-processed
// - all other types are assumed to be JSON encoded
func (f *Field) setString(rawVal string, found bool) error {
if f.value.Kind() == reflect.Slice && f.value.Type().Elem().Kind() == reflect.Uint8 {
if !found {
return nil
}
dst := make([]byte, base64.StdEncoding.DecodedLen(len(rawVal)))
n, err := base64.StdEncoding.Decode(dst, []byte(rawVal))
if err != nil {
return fmt.Errorf("decoding base64: %w", err)
}
if n > 0 {
f.value.SetBytes(dst[:n])
}
return nil
}
switch f.value.Kind() {
case reflect.Bool:
if found && rawVal == "" {
f.value.SetBool(true)
} else if found && rawVal != "" {
f.value.SetBool(rawVal == "true")
}
case reflect.String:
if !found {
return nil
}
f.value.Set(reflect.ValueOf(rawVal))
default:
if !found {
return nil
}
val := f.value.Addr().Interface()
if err := json.Unmarshal([]byte(rawVal), val); err != nil {
return fmt.Errorf("%w, raw value: %q", err, rawVal)
}
}
return nil
}