-
Notifications
You must be signed in to change notification settings - Fork 1
/
resolver.go
152 lines (129 loc) · 3.37 KB
/
resolver.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
148
149
150
151
152
package apitools
import (
"errors"
"fmt"
"reflect"
"sync"
"github.com/blang/semver/v4"
)
var errAPINotFound = errors.New("api not found")
var (
typeMap = map[string]map[string]typeRef{}
typeMapMu = &sync.RWMutex{}
)
type typeRef struct {
Type reflect.Type
Hooks []func(interface{})
}
func (ref typeRef) new() interface{} {
typ := reflect.New(ref.Type).Interface()
for _, hook := range ref.Hooks {
hook(typ)
}
return typ
}
// ResolveOption a customization on how types are resolved
type ResolveOption interface {
apply(ref *typeRef)
}
// WithAlias Option allows a type to be resolved by names other than its type
// name
func WithAlias(alias ...string) ResolveOption {
return aliasOpt{
Aliases: alias,
}
}
type aliasOpt struct {
Aliases []string
}
// Noop to fit Option interface
func (aliasOpt) apply(*typeRef) {}
// WithResolveHook allows modules to preform initalization on resolved types
func WithResolveHook(fn func(interface{})) ResolveOption {
return hookOpt(fn)
}
type hookOpt func(interface{})
func (fn hookOpt) apply(ref *typeRef) {
ref.Hooks = append(ref.Hooks, fn)
}
// RegisterType allows modules to register API types to be resolved.
func RegisterType(apiGroup string, t interface{}, opts ...ResolveOption) {
var typeAliases []string
rrt := reflect.ValueOf(t)
rType := reflect.Indirect(rrt).Type()
ref := typeRef{Type: rType}
for _, opt := range opts {
if alias, ok := opt.(aliasOpt); ok {
typeAliases = append(typeAliases, alias.Aliases...)
}
opt.apply(&ref)
}
typeMapMu.Lock()
defer typeMapMu.Unlock()
if _, ok := typeMap[apiGroup]; !ok {
typeMap[apiGroup] = map[string]typeRef{}
}
typeMap[apiGroup][rType.Name()] = ref
for _, alias := range typeAliases {
typeMap[apiGroup][alias] = ref
}
}
// Resolve resolves the raw type for the requested api version and type.
func Resolve(apiVersion string, typename string) (interface{}, error) {
// Guard read access to packageMap
typeMapMu.RLock()
defer typeMapMu.RUnlock()
apiGroup, reqVer := parseAPIVersion(apiVersion)
group, ok := typeMap[apiGroup]
if !ok {
return nil, fmt.Errorf("api group %s has not been registered", apiGroup)
}
ref, ok := group[typename]
if !ok {
return nil, errAPINotFound
}
if foundVer, err := versionOf(ref.Type); err == nil {
if semverGreater(reqVer, foundVer) {
return nil, fmt.Errorf("requested version of %s was %s, but only %s is available", typename, reqVer, foundVer)
}
}
return ref.new(), nil
}
func semverGreater(s1, s2 string) bool {
s1Ver, err := semver.ParseTolerant(s1)
if err != nil {
// semver should be validated before being passed here
return false
}
s2Ver, err := semver.ParseTolerant(s2)
if err != nil {
// semver should be validated before being passed here
return false
}
return s1Ver.GT(s2Ver)
}
func copyTypeMap() map[string]map[string]typeRef {
typeMapMu.Lock()
defer typeMapMu.Unlock()
mapCopy := make(map[string]map[string]typeRef, len(typeMap))
for k, v := range typeMap {
inner := map[string]typeRef{}
for kk, vv := range v {
inner[kk] = vv
}
mapCopy[k] = inner
}
return mapCopy
}
// IterTypes iterates a copy of the typemap, calling fn for each type, with the
// API group, type name, and type.
func IterTypes(fn func(apiGroup, typename string, t any) (cont bool)) {
mapCopy := copyTypeMap()
for k, v := range mapCopy {
for kk, vv := range v {
if !fn(k, kk, vv.new()) {
return
}
}
}
}