Skip to content

Commit cf10ca0

Browse files
author
bcmills
committed
proto: Add a descriptor subpackage.
This provides a more reasonable API for obtaining a FileDescriptorProto and DescriptorProto for a given proto.Message — a process that is currently possible (but undocumented) using the public proto API. The major use case for obtaining a DescriptorProto is to decode custom message options, which are encoded as extensions on the MessageOptions submessage. (See https://developers.google.com/protocol-buffers/docs/proto#customoptions.) Fixes #179. PiperOrigin-RevId: 139356528
1 parent 62e782f commit cf10ca0

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed

descriptor/descriptor.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Go support for Protocol Buffers - Google's data interchange format
2+
//
3+
// Copyright 2016 The Go Authors. All rights reserved.
4+
// https://github.com/golang/protobuf
5+
//
6+
// Redistribution and use in source and binary forms, with or without
7+
// modification, are permitted provided that the following conditions are
8+
// met:
9+
//
10+
// * Redistributions of source code must retain the above copyright
11+
// notice, this list of conditions and the following disclaimer.
12+
// * Redistributions in binary form must reproduce the above
13+
// copyright notice, this list of conditions and the following disclaimer
14+
// in the documentation and/or other materials provided with the
15+
// distribution.
16+
// * Neither the name of Google Inc. nor the names of its
17+
// contributors may be used to endorse or promote products derived from
18+
// this software without specific prior written permission.
19+
//
20+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
32+
// Package descriptor provides functions for obtaining protocol buffer
33+
// descriptors for generated Go types.
34+
//
35+
// These functions cannot go in package proto because they depend on the
36+
// generated protobuf descriptor messages, which themselves depend on proto.
37+
package descriptor
38+
39+
import (
40+
"bytes"
41+
"compress/gzip"
42+
"fmt"
43+
"io/ioutil"
44+
45+
"github.com/golang/protobuf/proto"
46+
"google.golang.org/genproto/protobuf"
47+
)
48+
49+
// extractFile extracts a FileDescriptorProto from a gzip'd buffer.
50+
func extractFile(gz []byte) (*protobuf.FileDescriptorProto, error) {
51+
r, err := gzip.NewReader(bytes.NewReader(gz))
52+
if err != nil {
53+
return nil, fmt.Errorf("failed to open gzip reader: %v", err)
54+
}
55+
defer r.Close()
56+
57+
b, err := ioutil.ReadAll(r)
58+
if err != nil {
59+
return nil, fmt.Errorf("failed to uncompress descriptor: %v", err)
60+
}
61+
62+
fd := new(protobuf.FileDescriptorProto)
63+
if err := proto.Unmarshal(b, fd); err != nil {
64+
return nil, fmt.Errorf("malformed FileDescriptorProto: %v", err)
65+
}
66+
67+
return fd, nil
68+
}
69+
70+
// Message is a proto.Message with a method to return its descriptor.
71+
//
72+
// Message types generated by the protocol compiler always satisfy
73+
// the Message interface.
74+
type Message interface {
75+
proto.Message
76+
Descriptor() ([]byte, []int)
77+
}
78+
79+
// ForMessage returns a FileDescriptorProto and a DescriptorProto from within it
80+
// describing the given message.
81+
func ForMessage(msg Message) (fd *protobuf.FileDescriptorProto, md *protobuf.DescriptorProto) {
82+
gz, path := msg.Descriptor()
83+
fd, err := extractFile(gz)
84+
if err != nil {
85+
panic(fmt.Sprintf("invalid FileDescriptorProto for %T: %v", msg, err))
86+
}
87+
88+
md = fd.MessageType[path[0]]
89+
for _, i := range path[1:] {
90+
md = md.NestedType[i]
91+
}
92+
return fd, md
93+
}

descriptor/descriptor_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package descriptor_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/golang/protobuf/descriptor"
8+
tpb "github.com/golang/protobuf/proto/testdata"
9+
"google.golang.org/genproto/protobuf"
10+
)
11+
12+
func TestMessage(t *testing.T) {
13+
var msg *protobuf.DescriptorProto
14+
fd, md := descriptor.ForMessage(msg)
15+
if pkg, want := fd.GetPackage(), "google.protobuf"; pkg != want {
16+
t.Errorf("descriptor.ForMessage(%T).GetPackage() = %q; want %q", msg, pkg, want)
17+
}
18+
if name, want := md.GetName(), "DescriptorProto"; name != want {
19+
t.Fatalf("descriptor.ForMessage(%T).GetName() = %q; want %q", msg, name, want)
20+
}
21+
}
22+
23+
func Example_Options() {
24+
var msg *tpb.MyMessageSet
25+
_, md := descriptor.ForMessage(msg)
26+
if md.GetOptions().GetMessageSetWireFormat() {
27+
fmt.Printf("%v uses option message_set_wire_format.\n", md.GetName())
28+
}
29+
30+
// Output:
31+
// MyMessageSet uses option message_set_wire_format.
32+
}

0 commit comments

Comments
 (0)