-
Notifications
You must be signed in to change notification settings - Fork 1
/
oap.go
132 lines (102 loc) · 3.22 KB
/
oap.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
// Package oap is a library for loading Apollo configuration into a struct.
// It supports JSON/YAML/TOML format.
// You can also use custom unmarshal for struct type filed.
package oap
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"github.com/pelletier/go-toml/v2"
"github.com/philchia/agollo/v4"
"gopkg.in/yaml.v3"
)
const (
apolloTag = "apollo"
apolloNamespaceTag = "apollo_namespace"
)
type UnmarshalFunc func([]byte, interface{}) error
var registryForUnmarshal = map[string]UnmarshalFunc{
"json": json.Unmarshal,
"yaml": yaml.Unmarshal,
"toml": toml.Unmarshal,
}
// You can use custom unmarshal for struct type filed.
// Package oap provides built-in support for JSON/YAML/TOML.
func SetUnmarshalFunc(name string, f UnmarshalFunc) {
registryForUnmarshal[name] = f
}
func Decode(ptr interface{}, client agollo.Client, keyOpts map[string][]agollo.OpOption) error {
v := reflect.ValueOf(ptr).Elem()
if v.Kind() != reflect.Struct {
return nil
}
return decodeStruct(ptr, client, nil, keyOpts)
}
func decodeStruct(ptr interface{}, client agollo.Client, opts []agollo.OpOption, keyOpts map[string][]agollo.OpOption) error {
v := reflect.ValueOf(ptr).Elem()
if v.Kind() != reflect.Struct {
return nil
}
for i := 0; i < v.NumField(); i++ {
structField := v.Type().Field(i)
field := v.FieldByName(structField.Name)
if err := decode(ptr, structField, field, client, opts, keyOpts); err != nil {
return err
}
}
return nil
}
func decode(ptr interface{}, structField reflect.StructField, field reflect.Value, client agollo.Client, opts []agollo.OpOption, keyOpts map[string][]agollo.OpOption) error {
if !field.CanSet() {
return nil
}
tag := structField.Tag
apolloRawKey := tag.Get(apolloTag)
apolloKeyParts := strings.Split(apolloRawKey, ",")
apolloKey := apolloKeyParts[0]
// OpOptions
kopts := keyOpts[apolloKey]
newOpts := make([]agollo.OpOption, len(opts)+len(kopts))
copy(newOpts, opts)
copy(newOpts[len(opts):], kopts)
// using namespace
if ns := tag.Get(apolloNamespaceTag); ns != "" {
//nolint:makezero // we have already copy kopts and opts to newOpts
newOpts = append(newOpts, agollo.WithNamespace(ns))
}
val := reflect.New(structField.Type)
// nested struct fields
if apolloKey == "" {
if err := decodeStruct(val.Interface(), client, newOpts, keyOpts); err != nil {
return fmt.Errorf("Decode %s error: %w", structField.Name, err)
}
field.Set(val.Elem())
return nil
}
// get config content
apolloVal := client.GetString(apolloKey, newOpts...)
// set raw string, first
if field.Kind() == reflect.String {
field.Set(reflect.ValueOf(apolloVal).Convert(val.Elem().Type()))
return nil
}
// use unmarshaller function
if len(apolloKeyParts) > 1 {
if unmarshallerFunc, ok := registryForUnmarshal[apolloKeyParts[1]]; ok {
if err := unmarshallerFunc([]byte(apolloVal), val.Interface()); err != nil {
return fmt.Errorf("%s unmarshal %s error: %w", apolloKeyParts[1], apolloKey, err)
}
if field.CanSet() {
field.Set(val.Elem())
}
return nil
}
}
// parse value via yaml
if err := yaml.Unmarshal([]byte(apolloVal), val.Interface()); err != nil {
return fmt.Errorf("unmarshal %s error: %w", apolloVal, err)
}
field.Set(val.Elem())
return nil
}