blob: 1e7ff64205775dd576528bcddb4140f648627635 [file] [log] [blame]
Andrea Campanella3614a922021-02-25 12:40:42 +01001// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package proto
6
7import (
8 "bytes"
9 "compress/gzip"
10 "fmt"
11 "io/ioutil"
12 "reflect"
13 "strings"
14 "sync"
15
16 "google.golang.org/protobuf/reflect/protoreflect"
17 "google.golang.org/protobuf/reflect/protoregistry"
18 "google.golang.org/protobuf/runtime/protoimpl"
19)
20
21// filePath is the path to the proto source file.
22type filePath = string // e.g., "google/protobuf/descriptor.proto"
23
24// fileDescGZIP is the compressed contents of the encoded FileDescriptorProto.
25type fileDescGZIP = []byte
26
27var fileCache sync.Map // map[filePath]fileDescGZIP
28
29// RegisterFile is called from generated code to register the compressed
30// FileDescriptorProto with the file path for a proto source file.
31//
32// Deprecated: Use protoregistry.GlobalFiles.RegisterFile instead.
33func RegisterFile(s filePath, d fileDescGZIP) {
34 // Decompress the descriptor.
35 zr, err := gzip.NewReader(bytes.NewReader(d))
36 if err != nil {
37 panic(fmt.Sprintf("proto: invalid compressed file descriptor: %v", err))
38 }
39 b, err := ioutil.ReadAll(zr)
40 if err != nil {
41 panic(fmt.Sprintf("proto: invalid compressed file descriptor: %v", err))
42 }
43
44 // Construct a protoreflect.FileDescriptor from the raw descriptor.
45 // Note that DescBuilder.Build automatically registers the constructed
46 // file descriptor with the v2 registry.
47 protoimpl.DescBuilder{RawDescriptor: b}.Build()
48
49 // Locally cache the raw descriptor form for the file.
50 fileCache.Store(s, d)
51}
52
53// FileDescriptor returns the compressed FileDescriptorProto given the file path
54// for a proto source file. It returns nil if not found.
55//
56// Deprecated: Use protoregistry.GlobalFiles.FindFileByPath instead.
57func FileDescriptor(s filePath) fileDescGZIP {
58 if v, ok := fileCache.Load(s); ok {
59 return v.(fileDescGZIP)
60 }
61
62 // Find the descriptor in the v2 registry.
63 var b []byte
64 if fd, _ := protoregistry.GlobalFiles.FindFileByPath(s); fd != nil {
65 if fd, ok := fd.(interface{ ProtoLegacyRawDesc() []byte }); ok {
66 b = fd.ProtoLegacyRawDesc()
67 } else {
68 // TODO: Use protodesc.ToFileDescriptorProto to construct
69 // a descriptorpb.FileDescriptorProto and marshal it.
70 // However, doing so causes the proto package to have a dependency
71 // on descriptorpb, leading to cyclic dependency issues.
72 }
73 }
74
75 // Locally cache the raw descriptor form for the file.
76 if len(b) > 0 {
77 v, _ := fileCache.LoadOrStore(s, protoimpl.X.CompressGZIP(b))
78 return v.(fileDescGZIP)
79 }
80 return nil
81}
82
83// enumName is the name of an enum. For historical reasons, the enum name is
84// neither the full Go name nor the full protobuf name of the enum.
85// The name is the dot-separated combination of just the proto package that the
86// enum is declared within followed by the Go type name of the generated enum.
87type enumName = string // e.g., "my.proto.package.GoMessage_GoEnum"
88
89// enumsByName maps enum values by name to their numeric counterpart.
90type enumsByName = map[string]int32
91
92// enumsByNumber maps enum values by number to their name counterpart.
93type enumsByNumber = map[int32]string
94
95var enumCache sync.Map // map[enumName]enumsByName
96var numFilesCache sync.Map // map[protoreflect.FullName]int
97
98// RegisterEnum is called from the generated code to register the mapping of
99// enum value names to enum numbers for the enum identified by s.
100//
101// Deprecated: Use protoregistry.GlobalTypes.RegisterEnum instead.
102func RegisterEnum(s enumName, _ enumsByNumber, m enumsByName) {
103 if _, ok := enumCache.Load(s); ok {
104 panic("proto: duplicate enum registered: " + s)
105 }
106 enumCache.Store(s, m)
107
108 // This does not forward registration to the v2 registry since this API
109 // lacks sufficient information to construct a complete v2 enum descriptor.
110}
111
112// EnumValueMap returns the mapping from enum value names to enum numbers for
113// the enum of the given name. It returns nil if not found.
114//
115// Deprecated: Use protoregistry.GlobalTypes.FindEnumByName instead.
116func EnumValueMap(s enumName) enumsByName {
117 if v, ok := enumCache.Load(s); ok {
118 return v.(enumsByName)
119 }
120
121 // Check whether the cache is stale. If the number of files in the current
122 // package differs, then it means that some enums may have been recently
123 // registered upstream that we do not know about.
124 var protoPkg protoreflect.FullName
125 if i := strings.LastIndexByte(s, '.'); i >= 0 {
126 protoPkg = protoreflect.FullName(s[:i])
127 }
128 v, _ := numFilesCache.Load(protoPkg)
129 numFiles, _ := v.(int)
130 if protoregistry.GlobalFiles.NumFilesByPackage(protoPkg) == numFiles {
131 return nil // cache is up-to-date; was not found earlier
132 }
133
134 // Update the enum cache for all enums declared in the given proto package.
135 numFiles = 0
136 protoregistry.GlobalFiles.RangeFilesByPackage(protoPkg, func(fd protoreflect.FileDescriptor) bool {
137 walkEnums(fd, func(ed protoreflect.EnumDescriptor) {
138 name := protoimpl.X.LegacyEnumName(ed)
139 if _, ok := enumCache.Load(name); !ok {
140 m := make(enumsByName)
141 evs := ed.Values()
142 for i := evs.Len() - 1; i >= 0; i-- {
143 ev := evs.Get(i)
144 m[string(ev.Name())] = int32(ev.Number())
145 }
146 enumCache.LoadOrStore(name, m)
147 }
148 })
149 numFiles++
150 return true
151 })
152 numFilesCache.Store(protoPkg, numFiles)
153
154 // Check cache again for enum map.
155 if v, ok := enumCache.Load(s); ok {
156 return v.(enumsByName)
157 }
158 return nil
159}
160
161// walkEnums recursively walks all enums declared in d.
162func walkEnums(d interface {
163 Enums() protoreflect.EnumDescriptors
164 Messages() protoreflect.MessageDescriptors
165}, f func(protoreflect.EnumDescriptor)) {
166 eds := d.Enums()
167 for i := eds.Len() - 1; i >= 0; i-- {
168 f(eds.Get(i))
169 }
170 mds := d.Messages()
171 for i := mds.Len() - 1; i >= 0; i-- {
172 walkEnums(mds.Get(i), f)
173 }
174}
175
176// messageName is the full name of protobuf message.
177type messageName = string
178
179var messageTypeCache sync.Map // map[messageName]reflect.Type
180
181// RegisterType is called from generated code to register the message Go type
182// for a message of the given name.
183//
184// Deprecated: Use protoregistry.GlobalTypes.RegisterMessage instead.
185func RegisterType(m Message, s messageName) {
186 mt := protoimpl.X.LegacyMessageTypeOf(m, protoreflect.FullName(s))
187 if err := protoregistry.GlobalTypes.RegisterMessage(mt); err != nil {
188 panic(err)
189 }
190 messageTypeCache.Store(s, reflect.TypeOf(m))
191}
192
193// RegisterMapType is called from generated code to register the Go map type
194// for a protobuf message representing a map entry.
195//
196// Deprecated: Do not use.
197func RegisterMapType(m interface{}, s messageName) {
198 t := reflect.TypeOf(m)
199 if t.Kind() != reflect.Map {
200 panic(fmt.Sprintf("invalid map kind: %v", t))
201 }
202 if _, ok := messageTypeCache.Load(s); ok {
203 panic(fmt.Errorf("proto: duplicate proto message registered: %s", s))
204 }
205 messageTypeCache.Store(s, t)
206}
207
208// MessageType returns the message type for a named message.
209// It returns nil if not found.
210//
211// Deprecated: Use protoregistry.GlobalTypes.FindMessageByName instead.
212func MessageType(s messageName) reflect.Type {
213 if v, ok := messageTypeCache.Load(s); ok {
214 return v.(reflect.Type)
215 }
216
217 // Derive the message type from the v2 registry.
218 var t reflect.Type
219 if mt, _ := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(s)); mt != nil {
220 t = messageGoType(mt)
221 }
222
223 // If we could not get a concrete type, it is possible that it is a
224 // pseudo-message for a map entry.
225 if t == nil {
226 d, _ := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(s))
227 if md, _ := d.(protoreflect.MessageDescriptor); md != nil && md.IsMapEntry() {
228 kt := goTypeForField(md.Fields().ByNumber(1))
229 vt := goTypeForField(md.Fields().ByNumber(2))
230 t = reflect.MapOf(kt, vt)
231 }
232 }
233
234 // Locally cache the message type for the given name.
235 if t != nil {
236 v, _ := messageTypeCache.LoadOrStore(s, t)
237 return v.(reflect.Type)
238 }
239 return nil
240}
241
242func goTypeForField(fd protoreflect.FieldDescriptor) reflect.Type {
243 switch k := fd.Kind(); k {
244 case protoreflect.EnumKind:
245 if et, _ := protoregistry.GlobalTypes.FindEnumByName(fd.Enum().FullName()); et != nil {
246 return enumGoType(et)
247 }
248 return reflect.TypeOf(protoreflect.EnumNumber(0))
249 case protoreflect.MessageKind, protoreflect.GroupKind:
250 if mt, _ := protoregistry.GlobalTypes.FindMessageByName(fd.Message().FullName()); mt != nil {
251 return messageGoType(mt)
252 }
253 return reflect.TypeOf((*protoreflect.Message)(nil)).Elem()
254 default:
255 return reflect.TypeOf(fd.Default().Interface())
256 }
257}
258
259func enumGoType(et protoreflect.EnumType) reflect.Type {
260 return reflect.TypeOf(et.New(0))
261}
262
263func messageGoType(mt protoreflect.MessageType) reflect.Type {
264 return reflect.TypeOf(MessageV1(mt.Zero().Interface()))
265}
266
267// MessageName returns the full protobuf name for the given message type.
268//
269// Deprecated: Use protoreflect.MessageDescriptor.FullName instead.
270func MessageName(m Message) messageName {
271 if m == nil {
272 return ""
273 }
274 if m, ok := m.(interface{ XXX_MessageName() messageName }); ok {
275 return m.XXX_MessageName()
276 }
277 return messageName(protoimpl.X.MessageDescriptorOf(m).FullName())
278}
279
280// RegisterExtension is called from the generated code to register
281// the extension descriptor.
282//
283// Deprecated: Use protoregistry.GlobalTypes.RegisterExtension instead.
284func RegisterExtension(d *ExtensionDesc) {
285 if err := protoregistry.GlobalTypes.RegisterExtension(d); err != nil {
286 panic(err)
287 }
288}
289
290type extensionsByNumber = map[int32]*ExtensionDesc
291
292var extensionCache sync.Map // map[messageName]extensionsByNumber
293
294// RegisteredExtensions returns a map of the registered extensions for the
295// provided protobuf message, indexed by the extension field number.
296//
297// Deprecated: Use protoregistry.GlobalTypes.RangeExtensionsByMessage instead.
298func RegisteredExtensions(m Message) extensionsByNumber {
299 // Check whether the cache is stale. If the number of extensions for
300 // the given message differs, then it means that some extensions were
301 // recently registered upstream that we do not know about.
302 s := MessageName(m)
303 v, _ := extensionCache.Load(s)
304 xs, _ := v.(extensionsByNumber)
305 if protoregistry.GlobalTypes.NumExtensionsByMessage(protoreflect.FullName(s)) == len(xs) {
306 return xs // cache is up-to-date
307 }
308
309 // Cache is stale, re-compute the extensions map.
310 xs = make(extensionsByNumber)
311 protoregistry.GlobalTypes.RangeExtensionsByMessage(protoreflect.FullName(s), func(xt protoreflect.ExtensionType) bool {
312 if xd, ok := xt.(*ExtensionDesc); ok {
313 xs[int32(xt.TypeDescriptor().Number())] = xd
314 } else {
315 // TODO: This implies that the protoreflect.ExtensionType is a
316 // custom type not generated by protoc-gen-go. We could try and
317 // convert the type to an ExtensionDesc.
318 }
319 return true
320 })
321 extensionCache.Store(s, xs)
322 return xs
323}