blob: 4135adee391882a8aedd5d4d603d7eb7c93fe0b4 [file] [log] [blame]
Joe Tsai23ddbd12018-08-26 22:48:17 -07001// Copyright 2018 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
Joe Tsaie1f8d502018-11-26 18:55:29 -08005// Package protodesc provides for converting descriptorpb.FileDescriptorProto
6// to/from the reflective protoreflect.FileDescriptor.
7package protodesc
Joe Tsai23ddbd12018-08-26 22:48:17 -07008
9import (
Joe Tsai23ddbd12018-08-26 22:48:17 -070010 "strings"
11
Damien Neile89e6242019-05-13 23:55:40 -070012 "google.golang.org/protobuf/internal/encoding/defval"
13 "google.golang.org/protobuf/internal/errors"
14 "google.golang.org/protobuf/internal/prototype"
15 "google.golang.org/protobuf/reflect/protoreflect"
16 "google.golang.org/protobuf/reflect/protoregistry"
Joe Tsaie1f8d502018-11-26 18:55:29 -080017
Joe Tsaia95b29f2019-05-16 12:47:20 -070018 "google.golang.org/protobuf/types/descriptorpb"
Joe Tsai23ddbd12018-08-26 22:48:17 -070019)
20
21// TODO: Should we be responsible for validating other parts of the descriptor
22// that we don't directly use?
23//
24// For example:
Joe Tsai23ddbd12018-08-26 22:48:17 -070025// * That "json_name" is not set for an extension field. Maybe, maybe not.
Joe Tsai23ddbd12018-08-26 22:48:17 -070026// * That "weak" is not set for an extension field (double check this).
27
28// TODO: Store the input file descriptor to implement:
29// * protoreflect.Descriptor.DescriptorProto
30// * protoreflect.Descriptor.DescriptorOptions
31
32// TODO: Should we return a File instead of protoreflect.FileDescriptor?
33// This would allow users to mutate the File before converting it.
34// However, this will complicate future work for validation since File may now
35// diverge from the stored descriptor proto (see above TODO).
36
Joe Tsaie1f8d502018-11-26 18:55:29 -080037// NewFile creates a new protoreflect.FileDescriptor from the provided
38// file descriptor message. The file must represent a valid proto file according
39// to protobuf semantics.
Joe Tsai23ddbd12018-08-26 22:48:17 -070040//
41// Any import files, enum types, or message types referenced in the file are
42// resolved using the provided registry. When looking up an import file path,
43// the path must be unique. The newly created file descriptor is not registered
44// back into the provided file registry.
45//
46// The caller must relinquish full ownership of the input fd and must not
47// access or mutate any fields.
Joe Tsaie1f8d502018-11-26 18:55:29 -080048func NewFile(fd *descriptorpb.FileDescriptorProto, r *protoregistry.Files) (protoreflect.FileDescriptor, error) {
49 var f prototype.File
Joe Tsai23ddbd12018-08-26 22:48:17 -070050 switch fd.GetSyntax() {
Joe Tsaibce82b82018-12-06 09:39:03 -080051 case "proto2", "":
Joe Tsai23ddbd12018-08-26 22:48:17 -070052 f.Syntax = protoreflect.Proto2
53 case "proto3":
54 f.Syntax = protoreflect.Proto3
55 default:
56 return nil, errors.New("invalid syntax: %v", fd.GetSyntax())
57 }
58 f.Path = fd.GetName()
59 f.Package = protoreflect.FullName(fd.GetPackage())
Damien Neil204f1c02018-10-23 15:03:38 -070060 f.Options = fd.GetOptions()
Joe Tsai23ddbd12018-08-26 22:48:17 -070061
62 f.Imports = make([]protoreflect.FileImport, len(fd.GetDependency()))
63 for _, i := range fd.GetPublicDependency() {
64 if int(i) >= len(f.Imports) || f.Imports[i].IsPublic {
65 return nil, errors.New("invalid or duplicate public import index: %d", i)
66 }
67 f.Imports[i].IsPublic = true
68 }
69 for _, i := range fd.GetWeakDependency() {
70 if int(i) >= len(f.Imports) || f.Imports[i].IsWeak {
71 return nil, errors.New("invalid or duplicate weak import index: %d", i)
72 }
73 f.Imports[i].IsWeak = true
74 }
75 for i, path := range fd.GetDependency() {
Joe Tsai23ddbd12018-08-26 22:48:17 -070076 imp := &f.Imports[i]
Joe Tsaibd7b7a92019-06-15 05:39:04 -070077 fd, err := r.FindFileByPath(path)
78 if err != nil {
79 fd = prototype.PlaceholderFile(path, "")
Joe Tsai23ddbd12018-08-26 22:48:17 -070080 }
Joe Tsaibd7b7a92019-06-15 05:39:04 -070081 imp.FileDescriptor = fd
Joe Tsai23ddbd12018-08-26 22:48:17 -070082 }
83
John Wright9a824c92019-05-03 14:05:20 -060084 imps := importedFiles(f.Imports)
85
Joe Tsai23ddbd12018-08-26 22:48:17 -070086 var err error
John Wright9a824c92019-05-03 14:05:20 -060087 f.Messages, err = messagesFromDescriptorProto(fd.GetMessageType(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -070088 if err != nil {
89 return nil, err
90 }
91 f.Enums, err = enumsFromDescriptorProto(fd.GetEnumType(), r)
92 if err != nil {
93 return nil, err
94 }
John Wright9a824c92019-05-03 14:05:20 -060095 f.Extensions, err = extensionsFromDescriptorProto(fd.GetExtension(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -070096 if err != nil {
97 return nil, err
98 }
John Wright9a824c92019-05-03 14:05:20 -060099 f.Services, err = servicesFromDescriptorProto(fd.GetService(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700100 if err != nil {
101 return nil, err
102 }
103
Joe Tsaie1f8d502018-11-26 18:55:29 -0800104 return prototype.NewFile(&f)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700105}
106
John Wrightbac4cd42019-05-07 19:10:41 -0600107type importSet map[string]bool
John Wright9a824c92019-05-03 14:05:20 -0600108
109func importedFiles(imps []protoreflect.FileImport) importSet {
110 ret := make(importSet)
111 for _, imp := range imps {
John Wrightbac4cd42019-05-07 19:10:41 -0600112 ret[imp.Path()] = true
113 addPublicImports(imp, ret)
John Wright9a824c92019-05-03 14:05:20 -0600114 }
115 return ret
116}
117
118func addPublicImports(fd protoreflect.FileDescriptor, out importSet) {
119 imps := fd.Imports()
120 for i := 0; i < imps.Len(); i++ {
121 imp := imps.Get(i)
122 if imp.IsPublic {
John Wrightbac4cd42019-05-07 19:10:41 -0600123 out[imp.Path()] = true
124 addPublicImports(imp, out)
John Wright9a824c92019-05-03 14:05:20 -0600125 }
126 }
127}
128
129func messagesFromDescriptorProto(mds []*descriptorpb.DescriptorProto, imps importSet, r *protoregistry.Files) (ms []prototype.Message, err error) {
Joe Tsai23ddbd12018-08-26 22:48:17 -0700130 for _, md := range mds {
Joe Tsaie1f8d502018-11-26 18:55:29 -0800131 var m prototype.Message
Joe Tsai23ddbd12018-08-26 22:48:17 -0700132 m.Name = protoreflect.Name(md.GetName())
Damien Neil204f1c02018-10-23 15:03:38 -0700133 m.Options = md.GetOptions()
Damien Neil232ea152018-12-10 15:14:36 -0800134 m.IsMapEntry = md.GetOptions().GetMapEntry()
John Wright9a824c92019-05-03 14:05:20 -0600135
136 for _, s := range md.GetReservedName() {
137 m.ReservedNames = append(m.ReservedNames, protoreflect.Name(s))
138 }
139 for _, rr := range md.GetReservedRange() {
140 m.ReservedRanges = append(m.ReservedRanges, [2]protoreflect.FieldNumber{
141 protoreflect.FieldNumber(rr.GetStart()),
142 protoreflect.FieldNumber(rr.GetEnd()),
143 })
144 }
145 for _, xr := range md.GetExtensionRange() {
146 m.ExtensionRanges = append(m.ExtensionRanges, [2]protoreflect.FieldNumber{
147 protoreflect.FieldNumber(xr.GetStart()),
148 protoreflect.FieldNumber(xr.GetEnd()),
149 })
150 m.ExtensionRangeOptions = append(m.ExtensionRangeOptions, xr.GetOptions())
151 }
152 resNames := prototype.Names(m.ReservedNames)
153 resRanges := prototype.FieldRanges(m.ReservedRanges)
154 extRanges := prototype.FieldRanges(m.ExtensionRanges)
155
Joe Tsai23ddbd12018-08-26 22:48:17 -0700156 for _, fd := range md.GetField() {
John Wright9a824c92019-05-03 14:05:20 -0600157 if fd.GetExtendee() != "" {
158 return nil, errors.New("message field may not have extendee")
159 }
Joe Tsaie1f8d502018-11-26 18:55:29 -0800160 var f prototype.Field
Joe Tsai23ddbd12018-08-26 22:48:17 -0700161 f.Name = protoreflect.Name(fd.GetName())
John Wright9a824c92019-05-03 14:05:20 -0600162 if resNames.Has(f.Name) {
163 return nil, errors.New("%v contains field with reserved name %q", m.Name, f.Name)
164 }
Joe Tsai23ddbd12018-08-26 22:48:17 -0700165 f.Number = protoreflect.FieldNumber(fd.GetNumber())
John Wright9a824c92019-05-03 14:05:20 -0600166 if resRanges.Has(f.Number) {
167 return nil, errors.New("%v contains field with reserved number %d", m.Name, f.Number)
168 }
169 if extRanges.Has(f.Number) {
170 return nil, errors.New("%v contains field with number %d in extension range", m.Name, f.Number)
171 }
Joe Tsai23ddbd12018-08-26 22:48:17 -0700172 f.Cardinality = protoreflect.Cardinality(fd.GetLabel())
173 f.Kind = protoreflect.Kind(fd.GetType())
Damien Neil232ea152018-12-10 15:14:36 -0800174 opts := fd.GetOptions()
175 f.Options = opts
176 if opts != nil && opts.Packed != nil {
177 if *opts.Packed {
178 f.IsPacked = prototype.True
179 } else {
180 f.IsPacked = prototype.False
181 }
182 }
183 f.IsWeak = opts.GetWeak()
Joe Tsai23ddbd12018-08-26 22:48:17 -0700184 f.JSONName = fd.GetJsonName()
Joe Tsai23ddbd12018-08-26 22:48:17 -0700185 if fd.DefaultValue != nil {
Joe Tsaic9899da2018-12-06 18:34:53 -0800186 f.Default, err = defval.Unmarshal(fd.GetDefaultValue(), f.Kind, defval.Descriptor)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700187 if err != nil {
188 return nil, err
189 }
190 }
191 if fd.OneofIndex != nil {
192 i := int(fd.GetOneofIndex())
193 if i >= len(md.GetOneofDecl()) {
194 return nil, errors.New("invalid oneof index: %d", i)
195 }
196 f.OneofName = protoreflect.Name(md.GetOneofDecl()[i].GetName())
197 }
198 switch f.Kind {
199 case protoreflect.EnumKind:
John Wright9a824c92019-05-03 14:05:20 -0600200 f.EnumType, err = findEnumDescriptor(fd.GetTypeName(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700201 if err != nil {
202 return nil, err
203 }
Joe Tsaie1f8d502018-11-26 18:55:29 -0800204 if opts.GetWeak() && !f.EnumType.IsPlaceholder() {
205 f.EnumType = prototype.PlaceholderEnum(f.EnumType.FullName())
Joe Tsai23ddbd12018-08-26 22:48:17 -0700206 }
207 case protoreflect.MessageKind, protoreflect.GroupKind:
John Wright9a824c92019-05-03 14:05:20 -0600208 f.MessageType, err = findMessageDescriptor(fd.GetTypeName(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700209 if err != nil {
210 return nil, err
211 }
Joe Tsaie1f8d502018-11-26 18:55:29 -0800212 if opts.GetWeak() && !f.MessageType.IsPlaceholder() {
213 f.MessageType = prototype.PlaceholderMessage(f.MessageType.FullName())
Joe Tsai23ddbd12018-08-26 22:48:17 -0700214 }
John Wright9a824c92019-05-03 14:05:20 -0600215 default:
216 if fd.GetTypeName() != "" {
217 return nil, errors.New("field of kind %v has type_name set", f.Kind)
218 }
Joe Tsai23ddbd12018-08-26 22:48:17 -0700219 }
220 m.Fields = append(m.Fields, f)
221 }
222 for _, od := range md.GetOneofDecl() {
Joe Tsaie1f8d502018-11-26 18:55:29 -0800223 m.Oneofs = append(m.Oneofs, prototype.Oneof{
Damien Neil204f1c02018-10-23 15:03:38 -0700224 Name: protoreflect.Name(od.GetName()),
225 Options: od.Options,
226 })
Joe Tsai23ddbd12018-08-26 22:48:17 -0700227 }
Joe Tsai23ddbd12018-08-26 22:48:17 -0700228
John Wright9a824c92019-05-03 14:05:20 -0600229 m.Messages, err = messagesFromDescriptorProto(md.GetNestedType(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700230 if err != nil {
231 return nil, err
232 }
233 m.Enums, err = enumsFromDescriptorProto(md.GetEnumType(), r)
234 if err != nil {
235 return nil, err
236 }
John Wright9a824c92019-05-03 14:05:20 -0600237 m.Extensions, err = extensionsFromDescriptorProto(md.GetExtension(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700238 if err != nil {
239 return nil, err
240 }
241
242 ms = append(ms, m)
243 }
244 return ms, nil
245}
246
Joe Tsaie1f8d502018-11-26 18:55:29 -0800247func enumsFromDescriptorProto(eds []*descriptorpb.EnumDescriptorProto, r *protoregistry.Files) (es []prototype.Enum, err error) {
Joe Tsai23ddbd12018-08-26 22:48:17 -0700248 for _, ed := range eds {
Joe Tsaie1f8d502018-11-26 18:55:29 -0800249 var e prototype.Enum
Joe Tsai23ddbd12018-08-26 22:48:17 -0700250 e.Name = protoreflect.Name(ed.GetName())
Damien Neil204f1c02018-10-23 15:03:38 -0700251 e.Options = ed.GetOptions()
Joe Tsaibce82b82018-12-06 09:39:03 -0800252 for _, s := range ed.GetReservedName() {
253 e.ReservedNames = append(e.ReservedNames, protoreflect.Name(s))
254 }
255 for _, rr := range ed.GetReservedRange() {
256 e.ReservedRanges = append(e.ReservedRanges, [2]protoreflect.EnumNumber{
257 protoreflect.EnumNumber(rr.GetStart()),
258 protoreflect.EnumNumber(rr.GetEnd()),
259 })
260 }
John Wright9a824c92019-05-03 14:05:20 -0600261 resNames := prototype.Names(e.ReservedNames)
262 resRanges := prototype.EnumRanges(e.ReservedRanges)
263
264 for _, vd := range ed.GetValue() {
265 v := prototype.EnumValue{
266 Name: protoreflect.Name(vd.GetName()),
267 Number: protoreflect.EnumNumber(vd.GetNumber()),
268 Options: vd.Options,
269 }
270 if resNames.Has(v.Name) {
271 return nil, errors.New("enum %v contains value with reserved name %q", e.Name, v.Name)
272 }
273 if resRanges.Has(v.Number) {
274 return nil, errors.New("enum %v contains value with reserved number %d", e.Name, v.Number)
275 }
276 e.Values = append(e.Values, v)
277 }
Joe Tsai23ddbd12018-08-26 22:48:17 -0700278 es = append(es, e)
279 }
280 return es, nil
281}
282
John Wright9a824c92019-05-03 14:05:20 -0600283func extensionsFromDescriptorProto(xds []*descriptorpb.FieldDescriptorProto, imps importSet, r *protoregistry.Files) (xs []prototype.Extension, err error) {
Joe Tsai23ddbd12018-08-26 22:48:17 -0700284 for _, xd := range xds {
John Wright9a824c92019-05-03 14:05:20 -0600285 if xd.OneofIndex != nil {
286 return nil, errors.New("extension may not have oneof_index")
287 }
Joe Tsaie1f8d502018-11-26 18:55:29 -0800288 var x prototype.Extension
Joe Tsai23ddbd12018-08-26 22:48:17 -0700289 x.Name = protoreflect.Name(xd.GetName())
290 x.Number = protoreflect.FieldNumber(xd.GetNumber())
291 x.Cardinality = protoreflect.Cardinality(xd.GetLabel())
292 x.Kind = protoreflect.Kind(xd.GetType())
Damien Neil204f1c02018-10-23 15:03:38 -0700293 x.Options = xd.GetOptions()
Joe Tsai23ddbd12018-08-26 22:48:17 -0700294 if xd.DefaultValue != nil {
Joe Tsaic9899da2018-12-06 18:34:53 -0800295 x.Default, err = defval.Unmarshal(xd.GetDefaultValue(), x.Kind, defval.Descriptor)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700296 if err != nil {
297 return nil, err
298 }
299 }
300 switch x.Kind {
301 case protoreflect.EnumKind:
John Wright9a824c92019-05-03 14:05:20 -0600302 x.EnumType, err = findEnumDescriptor(xd.GetTypeName(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700303 if err != nil {
304 return nil, err
305 }
306 case protoreflect.MessageKind, protoreflect.GroupKind:
John Wright9a824c92019-05-03 14:05:20 -0600307 x.MessageType, err = findMessageDescriptor(xd.GetTypeName(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700308 if err != nil {
309 return nil, err
310 }
John Wright9a824c92019-05-03 14:05:20 -0600311 default:
312 if xd.GetTypeName() != "" {
313 return nil, errors.New("extension of kind %v has type_name set", x.Kind)
314 }
Joe Tsai23ddbd12018-08-26 22:48:17 -0700315 }
John Wright9a824c92019-05-03 14:05:20 -0600316 x.ExtendedType, err = findMessageDescriptor(xd.GetExtendee(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700317 if err != nil {
318 return nil, err
319 }
320 xs = append(xs, x)
321 }
322 return xs, nil
323}
324
John Wright9a824c92019-05-03 14:05:20 -0600325func servicesFromDescriptorProto(sds []*descriptorpb.ServiceDescriptorProto, imps importSet, r *protoregistry.Files) (ss []prototype.Service, err error) {
Joe Tsai23ddbd12018-08-26 22:48:17 -0700326 for _, sd := range sds {
Joe Tsaie1f8d502018-11-26 18:55:29 -0800327 var s prototype.Service
Joe Tsai23ddbd12018-08-26 22:48:17 -0700328 s.Name = protoreflect.Name(sd.GetName())
Damien Neil204f1c02018-10-23 15:03:38 -0700329 s.Options = sd.GetOptions()
Joe Tsai23ddbd12018-08-26 22:48:17 -0700330 for _, md := range sd.GetMethod() {
Joe Tsaie1f8d502018-11-26 18:55:29 -0800331 var m prototype.Method
Joe Tsai23ddbd12018-08-26 22:48:17 -0700332 m.Name = protoreflect.Name(md.GetName())
Damien Neil204f1c02018-10-23 15:03:38 -0700333 m.Options = md.GetOptions()
John Wright9a824c92019-05-03 14:05:20 -0600334 m.InputType, err = findMessageDescriptor(md.GetInputType(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700335 if err != nil {
336 return nil, err
337 }
John Wright9a824c92019-05-03 14:05:20 -0600338 m.OutputType, err = findMessageDescriptor(md.GetOutputType(), imps, r)
Joe Tsai23ddbd12018-08-26 22:48:17 -0700339 if err != nil {
340 return nil, err
341 }
342 m.IsStreamingClient = md.GetClientStreaming()
343 m.IsStreamingServer = md.GetServerStreaming()
344 s.Methods = append(s.Methods, m)
345 }
346 ss = append(ss, s)
347 }
348 return ss, nil
349}
350
351// TODO: Should we allow relative names? The protoc compiler has emitted
352// absolute names for some time now. Requiring absolute names as an input
353// simplifies our implementation as we won't need to implement C++'s namespace
354// scoping rules.
355
John Wright9a824c92019-05-03 14:05:20 -0600356func findMessageDescriptor(s string, imps importSet, r *protoregistry.Files) (protoreflect.MessageDescriptor, error) {
Joe Tsai23ddbd12018-08-26 22:48:17 -0700357 if !strings.HasPrefix(s, ".") {
358 return nil, errors.New("identifier name must be fully qualified with a leading dot: %v", s)
359 }
360 name := protoreflect.FullName(strings.TrimPrefix(s, "."))
Damien Neil2300c182019-04-15 13:05:13 -0700361 md, err := r.FindMessageByName(name)
362 if err != nil {
Joe Tsaie1f8d502018-11-26 18:55:29 -0800363 return prototype.PlaceholderMessage(name), nil
Joe Tsai23ddbd12018-08-26 22:48:17 -0700364 }
John Wright9a824c92019-05-03 14:05:20 -0600365 if err := validateFileInImports(md, imps); err != nil {
366 return nil, err
367 }
Damien Neil2300c182019-04-15 13:05:13 -0700368 return md, nil
Joe Tsai23ddbd12018-08-26 22:48:17 -0700369}
370
John Wright9a824c92019-05-03 14:05:20 -0600371func findEnumDescriptor(s string, imps importSet, r *protoregistry.Files) (protoreflect.EnumDescriptor, error) {
Joe Tsai23ddbd12018-08-26 22:48:17 -0700372 if !strings.HasPrefix(s, ".") {
373 return nil, errors.New("identifier name must be fully qualified with a leading dot: %v", s)
374 }
375 name := protoreflect.FullName(strings.TrimPrefix(s, "."))
Damien Neil2300c182019-04-15 13:05:13 -0700376 ed, err := r.FindEnumByName(name)
377 if err != nil {
Joe Tsaie1f8d502018-11-26 18:55:29 -0800378 return prototype.PlaceholderEnum(name), nil
Joe Tsai23ddbd12018-08-26 22:48:17 -0700379 }
John Wright9a824c92019-05-03 14:05:20 -0600380 if err := validateFileInImports(ed, imps); err != nil {
381 return nil, err
382 }
Damien Neil2300c182019-04-15 13:05:13 -0700383 return ed, nil
Joe Tsai23ddbd12018-08-26 22:48:17 -0700384}
Joe Tsai67c1d9b2019-05-12 02:27:46 -0700385
386func validateFileInImports(d protoreflect.Descriptor, imps importSet) error {
387 fd := d.ParentFile()
388 if fd == nil {
389 return errors.New("%v has no parent FileDescriptor", d.FullName())
390 }
391 if !imps[fd.Path()] {
392 return errors.New("reference to type %v without import of %v", d.FullName(), fd.Path())
393 }
394 return nil
395}