|
2 | 2 | // Use of this source code is governed by a BSD-style
|
3 | 3 | // license that can be found in the LICENSE file.
|
4 | 4 |
|
5 |
| -// Package descriptor provides functions for obtaining protocol buffer |
6 |
| -// descriptors for generated Go types. |
| 5 | +// Package descriptor provides functions for obtaining the protocol buffer |
| 6 | +// descriptors of generated Go types. |
7 | 7 | //
|
8 |
| -// Deprecated: Do not use. The new v2 Message interface provides direct support |
9 |
| -// for programmatically interacting with the descriptor information. |
| 8 | +// Deprecated: Use the "google.golang.org/protobuf/reflect/protoreflect" |
| 9 | +// package instead to obtain an EnumDescriptor or MessageDescriptor in order to |
| 10 | +// programatically interact with the protobuf type system. |
10 | 11 | package descriptor
|
11 | 12 |
|
12 | 13 | import (
|
13 | 14 | "bytes"
|
14 | 15 | "compress/gzip"
|
15 |
| - "fmt" |
16 | 16 | "io/ioutil"
|
| 17 | + "sync" |
17 | 18 |
|
18 | 19 | "github.com/golang/protobuf/proto"
|
| 20 | + "google.golang.org/protobuf/reflect/protodesc" |
| 21 | + "google.golang.org/protobuf/reflect/protoreflect" |
| 22 | + "google.golang.org/protobuf/runtime/protoimpl" |
| 23 | + |
19 | 24 | descriptorpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
20 | 25 | )
|
21 | 26 |
|
22 |
| -// extractFile extracts a FileDescriptorProto from a gzip'd buffer. |
23 |
| -func extractFile(gz []byte) (*descriptorpb.FileDescriptorProto, error) { |
24 |
| - r, err := gzip.NewReader(bytes.NewReader(gz)) |
25 |
| - if err != nil { |
26 |
| - return nil, fmt.Errorf("failed to open gzip reader: %v", err) |
| 27 | +// Message is proto.Message with a method to return its descriptor. |
| 28 | +// Not every message is guaranteed to implement this interface. |
| 29 | +type Message interface { |
| 30 | + proto.Message |
| 31 | + Descriptor() ([]byte, []int) |
| 32 | +} |
| 33 | + |
| 34 | +// ForMessage returns the file descriptor proto containing |
| 35 | +// the message and the message descriptor proto for the message itself. |
| 36 | +// The returned proto messages must not be mutated. |
| 37 | +func ForMessage(m Message) (*descriptorpb.FileDescriptorProto, *descriptorpb.DescriptorProto) { |
| 38 | + return MessageDescriptorProtoOf(m) |
| 39 | +} |
| 40 | + |
| 41 | +// GeneratedEnum is any enum type generated by protoc-gen-go |
| 42 | +// which is a named int32 kind. |
| 43 | +type GeneratedEnum interface{} |
| 44 | + |
| 45 | +// GeneratedMessage is any message type generated by protoc-gen-go |
| 46 | +// which is a pointer to a named struct kind. |
| 47 | +type GeneratedMessage interface{} |
| 48 | + |
| 49 | +type rawDesc struct { |
| 50 | + fileDesc []byte |
| 51 | + indexes []int |
| 52 | +} |
| 53 | + |
| 54 | +var rawDescCache sync.Map // map[protoreflect.Descriptor]*rawDesc |
| 55 | + |
| 56 | +func deriveRawDescriptor(d protoreflect.Descriptor) ([]byte, []int) { |
| 57 | + // Fast-path: check whether raw descriptors are already cached. |
| 58 | + origDesc := d |
| 59 | + if v, ok := rawDescCache.Load(origDesc); ok { |
| 60 | + return v.(*rawDesc).fileDesc, v.(*rawDesc).indexes |
27 | 61 | }
|
28 |
| - defer r.Close() |
29 | 62 |
|
30 |
| - b, err := ioutil.ReadAll(r) |
31 |
| - if err != nil { |
32 |
| - return nil, fmt.Errorf("failed to uncompress descriptor: %v", err) |
| 63 | + // Slow-path: derive the raw descriptor from the v2 descriptor. |
| 64 | + |
| 65 | + // Start with the leaf (a given enum or message declaration) and |
| 66 | + // ascend upwards until we hit the parent file descriptor. |
| 67 | + var idxs []int |
| 68 | + for { |
| 69 | + idxs = append(idxs, d.Index()) |
| 70 | + d = d.Parent() |
| 71 | + if d == nil { |
| 72 | + // TODO: We could construct a FileDescriptor stub for standalone |
| 73 | + // descriptors to satisfy the API. |
| 74 | + return nil, nil |
| 75 | + } |
| 76 | + if _, ok := d.(protoreflect.FileDescriptor); ok { |
| 77 | + break |
| 78 | + } |
33 | 79 | }
|
34 | 80 |
|
35 |
| - fd := new(descriptorpb.FileDescriptorProto) |
36 |
| - if err := proto.Unmarshal(b, fd); err != nil { |
37 |
| - return nil, fmt.Errorf("malformed FileDescriptorProto: %v", err) |
| 81 | + // Obtain the raw file descriptor. |
| 82 | + var raw []byte |
| 83 | + switch fd := d.(type) { |
| 84 | + case interface{ ProtoLegacyRawDesc() []byte }: |
| 85 | + raw = fd.ProtoLegacyRawDesc() |
| 86 | + case protoreflect.FileDescriptor: |
| 87 | + raw, _ = proto.Marshal(protodesc.ToFileDescriptorProto(fd)) |
38 | 88 | }
|
| 89 | + file := protoimpl.X.CompressGZIP(raw) |
39 | 90 |
|
40 |
| - return fd, nil |
| 91 | + // Reverse the indexes, since we populated it in reverse. |
| 92 | + for i, j := 0, len(idxs)-1; i < j; i, j = i+1, j-1 { |
| 93 | + idxs[i], idxs[j] = idxs[j], idxs[i] |
| 94 | + } |
| 95 | + |
| 96 | + if v, ok := rawDescCache.LoadOrStore(origDesc, &rawDesc{file, idxs}); ok { |
| 97 | + return v.(*rawDesc).fileDesc, v.(*rawDesc).indexes |
| 98 | + } |
| 99 | + return file, idxs |
41 | 100 | }
|
42 | 101 |
|
43 |
| -// Message is a proto.Message with a method to return its descriptor. |
44 |
| -// |
45 |
| -// Message types generated by the protocol compiler always satisfy |
46 |
| -// the Message interface. |
47 |
| -type Message interface { |
48 |
| - proto.Message |
49 |
| - Descriptor() ([]byte, []int) |
| 102 | +// EnumRawDescriptorOf returns the GZIP'd raw file descriptor containing the |
| 103 | +// enum and the index path to reach the enum declaration. |
| 104 | +// The returned slices must not be mutated. |
| 105 | +func EnumRawDescriptorOf(e GeneratedEnum) ([]byte, []int) { |
| 106 | + if ev, ok := e.(interface{ EnumDescriptor() ([]byte, []int) }); ok { |
| 107 | + return ev.EnumDescriptor() |
| 108 | + } |
| 109 | + ed := protoimpl.X.EnumTypeOf(e) |
| 110 | + return deriveRawDescriptor(ed.Descriptor()) |
| 111 | +} |
| 112 | + |
| 113 | +// MessageRawDescriptorOf returns the GZIP'd raw file descriptor containing |
| 114 | +// the message and the index path to reach the message declaration. |
| 115 | +// The returned slices must not be mutated. |
| 116 | +func MessageRawDescriptorOf(m GeneratedMessage) ([]byte, []int) { |
| 117 | + if mv, ok := m.(interface{ Descriptor() ([]byte, []int) }); ok { |
| 118 | + return mv.Descriptor() |
| 119 | + } |
| 120 | + md := protoimpl.X.MessageTypeOf(m) |
| 121 | + return deriveRawDescriptor(md.Descriptor()) |
50 | 122 | }
|
51 | 123 |
|
52 |
| -// ForMessage returns a FileDescriptorProto and a DescriptorProto from within it |
53 |
| -// describing the given message. |
54 |
| -func ForMessage(msg Message) (fd *descriptorpb.FileDescriptorProto, md *descriptorpb.DescriptorProto) { |
55 |
| - gz, path := msg.Descriptor() |
56 |
| - fd, err := extractFile(gz) |
| 124 | +var fileDescCache sync.Map // map[*byte]*descriptorpb.FileDescriptorProto |
| 125 | + |
| 126 | +func deriveFileDescriptor(rawDesc []byte) *descriptorpb.FileDescriptorProto { |
| 127 | + // Fast-path: check whether descriptor protos are already cached. |
| 128 | + if v, ok := fileDescCache.Load(&rawDesc[0]); ok { |
| 129 | + return v.(*descriptorpb.FileDescriptorProto) |
| 130 | + } |
| 131 | + |
| 132 | + // Slow-path: derive the descriptor proto from the GZIP'd message. |
| 133 | + zr, err := gzip.NewReader(bytes.NewReader(rawDesc)) |
57 | 134 | if err != nil {
|
58 |
| - panic(fmt.Sprintf("invalid FileDescriptorProto for %T: %v", msg, err)) |
| 135 | + panic(err) |
59 | 136 | }
|
| 137 | + b, err := ioutil.ReadAll(zr) |
| 138 | + if err != nil { |
| 139 | + panic(err) |
| 140 | + } |
| 141 | + fd := new(descriptorpb.FileDescriptorProto) |
| 142 | + if err := proto.Unmarshal(b, fd); err != nil { |
| 143 | + panic(err) |
| 144 | + } |
| 145 | + if v, ok := fileDescCache.LoadOrStore(&rawDesc[0], fd); ok { |
| 146 | + return v.(*descriptorpb.FileDescriptorProto) |
| 147 | + } |
| 148 | + return fd |
| 149 | +} |
60 | 150 |
|
61 |
| - md = fd.MessageType[path[0]] |
62 |
| - for _, i := range path[1:] { |
| 151 | +// EnumDescriptorProtoOf returns the file descriptor proto containing |
| 152 | +// the enum and the enum descriptor proto for the enum itself. |
| 153 | +// The returned proto messages must not be mutated. |
| 154 | +func EnumDescriptorProtoOf(e GeneratedEnum) (*descriptorpb.FileDescriptorProto, *descriptorpb.EnumDescriptorProto) { |
| 155 | + rawDesc, idxs := EnumRawDescriptorOf(e) |
| 156 | + if rawDesc == nil || idxs == nil { |
| 157 | + return nil, nil |
| 158 | + } |
| 159 | + fd := deriveFileDescriptor(rawDesc) |
| 160 | + if len(idxs) == 1 { |
| 161 | + return fd, fd.EnumType[idxs[0]] |
| 162 | + } |
| 163 | + md := fd.MessageType[idxs[0]] |
| 164 | + for _, i := range idxs[1 : len(idxs)-1] { |
| 165 | + md = md.NestedType[i] |
| 166 | + } |
| 167 | + ed := md.EnumType[idxs[len(idxs)-1]] |
| 168 | + return fd, ed |
| 169 | +} |
| 170 | + |
| 171 | +// MessageDescriptorProtoOf returns the file descriptor proto containing |
| 172 | +// the message and the message descriptor proto for the message itself. |
| 173 | +// The returned proto messages must not be mutated. |
| 174 | +func MessageDescriptorProtoOf(m GeneratedMessage) (*descriptorpb.FileDescriptorProto, *descriptorpb.DescriptorProto) { |
| 175 | + rawDesc, idxs := MessageRawDescriptorOf(m) |
| 176 | + if rawDesc == nil || idxs == nil { |
| 177 | + return nil, nil |
| 178 | + } |
| 179 | + fd := deriveFileDescriptor(rawDesc) |
| 180 | + md := fd.MessageType[idxs[0]] |
| 181 | + for _, i := range idxs[1:] { |
63 | 182 | md = md.NestedType[i]
|
64 | 183 | }
|
65 | 184 | return fd, md
|
|
0 commit comments