-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathconfigurator.go
126 lines (103 loc) · 3 KB
/
configurator.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
// Package configuration provides ability to initialize your custom configuration struct from: flags, environment variables, `default` tag, files (json, yaml)
package configuration
import (
"fmt"
"reflect"
)
// New creates a new instance of the Configurator.
func New[T any](
providers ...Provider, // providers will be executed in order of their declaration
) (*T, error) {
cfg := &Configurator[T]{
configPtr: new(T),
providers: providers,
registeredProviders: map[string]struct{}{},
registeredTags: map[string]struct{}{},
}
return cfg.initValues()
}
type Configurator[T any] struct {
configPtr *T
providers []Provider
registeredTags map[string]struct{}
registeredProviders map[string]struct{}
}
// InitValues sets values into struct field using given set of providers
// respecting their order: first defined -> first executed
func (c *Configurator[T]) initValues() (*T, error) {
if reflect.TypeOf(c.configPtr).Elem().Kind() != reflect.Struct {
return nil, ErrNotAStruct
}
if len(c.providers) == 0 {
return nil, ErrNoProviders
}
for _, p := range c.providers {
if _, ok := c.registeredProviders[p.Name()]; ok {
return nil, ErrProviderNameCollision
}
c.registeredProviders[p.Name()] = struct{}{}
if _, ok := c.registeredTags[p.Tag()]; ok {
return nil, ErrProviderTagCollision
}
c.registeredTags[p.Tag()] = struct{}{}
if err := p.Init(c.configPtr); err != nil {
return nil, fmt.Errorf("cannot init [%s] provider: %w", p.Name(), err)
}
}
if err := c.fillUp(c.configPtr); err != nil {
return nil, err
}
return c.configPtr, nil
}
func (c *Configurator[T]) fillUp(i any) error {
var (
t = reflect.TypeOf(i)
v = reflect.ValueOf(i)
)
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
for i := range t.NumField() {
var (
tField = t.Field(i)
vField = v.Field(i)
)
if tField.Type.Kind() == reflect.Struct {
if err := c.fillUp(vField.Addr().Interface()); err != nil {
return err
}
continue
}
if tField.Type.Kind() == reflect.Ptr && tField.Type.Elem().Kind() == reflect.Struct {
vField.Set(reflect.New(tField.Type.Elem()))
if err := c.fillUp(vField.Interface()); err != nil {
return err
}
continue
}
if err := c.applyProviders(tField, vField); err != nil {
return err
}
}
return nil
}
func (c *Configurator[T]) applyProviders(field reflect.StructField, v reflect.Value) error {
if !field.IsExported() {
return nil
}
for _, provider := range c.providers {
if _, found := fetchTagKey(field.Tag, c.registeredTags)[provider.Tag()]; !found {
// skip provider if it's not specified in tags
continue
}
if err := provider.Provide(field, v); err == nil {
return nil
}
}
return fmt.Errorf("field [%s] with tags [%s] hasn't been set", field.Name, field.Tag)
}
// FromEnvAndDefault is a shortcut for `New(cfg, NewEnvProvider(), NewDefaultProvider()).InitValues()`.
func FromEnvAndDefault[T any]() (*T, error) {
return New[T](NewEnvProvider(), NewDefaultProvider())
}