1
16
17 package typeurl
18
19 import (
20 "encoding/json"
21 "errors"
22 "fmt"
23 "path"
24 "reflect"
25 "sync"
26
27 gogoproto "github.com/gogo/protobuf/proto"
28 "google.golang.org/protobuf/proto"
29 "google.golang.org/protobuf/reflect/protoregistry"
30 )
31
32 var (
33 mu sync.RWMutex
34 registry = make(map[reflect.Type]string)
35 )
36
37
38
39
40
41
42
43
44
45 var (
46 ErrNotFound = errors.New("not found")
47 )
48
49
50
51
52
53
54
55
56 type Any interface {
57
58
59 GetTypeUrl() string
60
61
62
63 GetValue() []byte
64 }
65
66 type anyType struct {
67 typeURL string
68 value []byte
69 }
70
71 func (a *anyType) GetTypeUrl() string {
72 if a == nil {
73 return ""
74 }
75 return a.typeURL
76 }
77
78 func (a *anyType) GetValue() []byte {
79 if a == nil {
80 return nil
81 }
82 return a.value
83 }
84
85
86
87
88
89 func Register(v interface{}, args ...string) {
90 var (
91 t = tryDereference(v)
92 p = path.Join(args...)
93 )
94 mu.Lock()
95 defer mu.Unlock()
96 if et, ok := registry[t]; ok {
97 if et != p {
98 panic(fmt.Errorf("type registered with alternate path %q != %q", et, p))
99 }
100 return
101 }
102 registry[t] = p
103 }
104
105
106 func TypeURL(v interface{}) (string, error) {
107 mu.RLock()
108 u, ok := registry[tryDereference(v)]
109 mu.RUnlock()
110 if !ok {
111 switch t := v.(type) {
112 case proto.Message:
113 return string(t.ProtoReflect().Descriptor().FullName()), nil
114 case gogoproto.Message:
115 return gogoproto.MessageName(t), nil
116 default:
117 return "", fmt.Errorf("type %s: %w", reflect.TypeOf(v), ErrNotFound)
118 }
119 }
120 return u, nil
121 }
122
123
124 func Is(any Any, v interface{}) bool {
125
126 tryDereference(v)
127 url, err := TypeURL(v)
128 if err != nil {
129 return false
130 }
131 return any.GetTypeUrl() == url
132 }
133
134
135
136
137
138 func MarshalAny(v interface{}) (Any, error) {
139 var marshal func(v interface{}) ([]byte, error)
140 switch t := v.(type) {
141 case Any:
142
143 return t, nil
144 case proto.Message:
145 marshal = func(v interface{}) ([]byte, error) {
146 return proto.Marshal(t)
147 }
148 case gogoproto.Message:
149 marshal = func(v interface{}) ([]byte, error) {
150 return gogoproto.Marshal(t)
151 }
152 default:
153 marshal = json.Marshal
154 }
155
156 url, err := TypeURL(v)
157 if err != nil {
158 return nil, err
159 }
160
161 data, err := marshal(v)
162 if err != nil {
163 return nil, err
164 }
165 return &anyType{
166 typeURL: url,
167 value: data,
168 }, nil
169 }
170
171
172 func UnmarshalAny(any Any) (interface{}, error) {
173 return UnmarshalByTypeURL(any.GetTypeUrl(), any.GetValue())
174 }
175
176
177 func UnmarshalByTypeURL(typeURL string, value []byte) (interface{}, error) {
178 return unmarshal(typeURL, value, nil)
179 }
180
181
182
183
184 func UnmarshalTo(any Any, out interface{}) error {
185 return UnmarshalToByTypeURL(any.GetTypeUrl(), any.GetValue(), out)
186 }
187
188
189
190
191 func UnmarshalToByTypeURL(typeURL string, value []byte, out interface{}) error {
192 _, err := unmarshal(typeURL, value, out)
193 return err
194 }
195
196 func unmarshal(typeURL string, value []byte, v interface{}) (interface{}, error) {
197 t, err := getTypeByUrl(typeURL)
198 if err != nil {
199 return nil, err
200 }
201
202 if v == nil {
203 v = reflect.New(t.t).Interface()
204 } else {
205
206 vURL, err := TypeURL(v)
207 if err != nil {
208 return nil, err
209 }
210 if typeURL != vURL {
211 return nil, fmt.Errorf("can't unmarshal type %q to output %q", typeURL, vURL)
212 }
213 }
214
215 if t.isProto {
216 switch t := v.(type) {
217 case proto.Message:
218 err = proto.Unmarshal(value, t)
219 case gogoproto.Message:
220 err = gogoproto.Unmarshal(value, t)
221 }
222 } else {
223 err = json.Unmarshal(value, v)
224 }
225
226 return v, err
227 }
228
229 type urlType struct {
230 t reflect.Type
231 isProto bool
232 }
233
234 func getTypeByUrl(url string) (urlType, error) {
235 mu.RLock()
236 for t, u := range registry {
237 if u == url {
238 mu.RUnlock()
239 return urlType{
240 t: t,
241 }, nil
242 }
243 }
244 mu.RUnlock()
245
246 t := gogoproto.MessageType(url)
247 if t != nil {
248 return urlType{
249
250 t: t.Elem(),
251 isProto: true,
252 }, nil
253 }
254 mt, err := protoregistry.GlobalTypes.FindMessageByURL(url)
255 if err != nil {
256 return urlType{}, fmt.Errorf("type with url %s: %w", url, ErrNotFound)
257 }
258 empty := mt.New().Interface()
259 return urlType{t: reflect.TypeOf(empty).Elem(), isProto: true}, nil
260 }
261
262 func tryDereference(v interface{}) reflect.Type {
263 t := reflect.TypeOf(v)
264 if t.Kind() == reflect.Ptr {
265
266 return t.Elem()
267 }
268 panic("v is not a pointer to a type")
269 }
270
View as plain text