Skip to content

Commit eba8b09

Browse files
googlebergstapelberg
authored andcommitted
cmd/protoc-gen-go: support protobuf retention feature
This change strips out all descriptor option fields marked with [retention = RETENTION_SOURCE] before writing the FileDescriptor to the generated go code bindings. Change-Id: Ie624d9d4b4f211a256661d80a04199ae8401662b Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/472696 Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Lasse Folger <[email protected]>
1 parent fcf5f6c commit eba8b09

File tree

8 files changed

+1478
-1
lines changed

8 files changed

+1478
-1
lines changed

cmd/protoc-gen-go/internal_gengo/reflect.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212

1313
"google.golang.org/protobuf/compiler/protogen"
1414
"google.golang.org/protobuf/proto"
15+
"google.golang.org/protobuf/reflect/protopath"
16+
"google.golang.org/protobuf/reflect/protorange"
1517
"google.golang.org/protobuf/reflect/protoreflect"
1618

1719
"google.golang.org/protobuf/types/descriptorpb"
@@ -233,10 +235,29 @@ func genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f
233235
g.P("}")
234236
}
235237

238+
// stripSourceRetentionFieldsFromMessage walks the given message tree recursively
239+
// and clears any fields with the field option: [retention = RETENTION_SOURCE]
240+
func stripSourceRetentionFieldsFromMessage(m protoreflect.Message) {
241+
protorange.Range(m, func(ppv protopath.Values) error {
242+
m2, ok := ppv.Index(-1).Value.Interface().(protoreflect.Message)
243+
if !ok {
244+
return nil
245+
}
246+
m2.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
247+
fdo, ok := fd.Options().(*descriptorpb.FieldOptions)
248+
if ok && fdo.GetRetention() == descriptorpb.FieldOptions_RETENTION_SOURCE {
249+
m2.Clear(fd)
250+
}
251+
return true
252+
})
253+
return nil
254+
})
255+
}
256+
236257
func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
237258
descProto := proto.Clone(f.Proto).(*descriptorpb.FileDescriptorProto)
238259
descProto.SourceCodeInfo = nil // drop source code information
239-
260+
stripSourceRetentionFieldsFromMessage(descProto.ProtoReflect())
240261
b, err := proto.MarshalOptions{AllowPartial: true, Deterministic: true}.Marshal(descProto)
241262
if err != nil {
242263
gen.Error(err)

cmd/protoc-gen-go/retention_test.go

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// 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+
5+
package main
6+
7+
import (
8+
"testing"
9+
10+
"google.golang.org/protobuf/proto"
11+
"google.golang.org/protobuf/reflect/protoreflect"
12+
13+
retentionpb "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/retention"
14+
)
15+
16+
func TestFileOptionRetention(t *testing.T) {
17+
options := retentionpb.File_cmd_protoc_gen_go_testdata_retention_retention_proto.Options()
18+
tests := []struct {
19+
name string
20+
ext protoreflect.ExtensionType
21+
wantField bool
22+
wantValue int32
23+
}{
24+
{
25+
name: "imported_plain_option",
26+
ext: retentionpb.E_ImportedPlainOption,
27+
wantField: true,
28+
wantValue: 1,
29+
},
30+
{
31+
name: "imported_runtime_option",
32+
ext: retentionpb.E_ImportedRuntimeRetentionOption,
33+
wantField: true,
34+
wantValue: 2,
35+
},
36+
{
37+
name: "imported_source_option",
38+
ext: retentionpb.E_ImportedSourceRetentionOption,
39+
wantField: false,
40+
wantValue: 0,
41+
},
42+
{
43+
name: "plain_option",
44+
ext: retentionpb.E_PlainOption,
45+
wantField: true,
46+
wantValue: 1,
47+
},
48+
{
49+
name: "runtime_option",
50+
ext: retentionpb.E_RuntimeRetentionOption,
51+
wantField: true,
52+
wantValue: 2,
53+
},
54+
{
55+
name: "source_option",
56+
ext: retentionpb.E_SourceRetentionOption,
57+
wantField: false,
58+
wantValue: 0,
59+
},
60+
}
61+
62+
for _, test := range tests {
63+
if test.wantField != proto.HasExtension(options, test.ext) {
64+
t.Errorf("HasExtension(%s): got %v, want %v", test.name, proto.HasExtension(options, test.ext), test.wantField)
65+
}
66+
if test.wantValue != proto.GetExtension(options, test.ext).(int32) {
67+
t.Errorf("GetExtension(%s): got %d, want %d", test.name, proto.GetExtension(options, test.ext).(int32), test.wantValue)
68+
}
69+
}
70+
}
71+
72+
func TestAllEntitiesWithMessageOption(t *testing.T) {
73+
file := retentionpb.File_cmd_protoc_gen_go_testdata_retention_retention_proto
74+
verifyDescriptorOptions(t, string(file.Name()), file.Options())
75+
verifyEnums(t, file.Enums())
76+
verifyMessages(t, file.Messages())
77+
verifyExtensions(t, file.Extensions())
78+
verifyServices(t, file.Services())
79+
}
80+
81+
func verifyExtensions(t *testing.T, extensions protoreflect.ExtensionDescriptors) {
82+
t.Helper()
83+
for i := 0; i < extensions.Len(); i++ {
84+
verifyDescriptorOptions(t, string(extensions.Get(i).Name()), extensions.Get(i).Options())
85+
}
86+
}
87+
88+
func verifyMessages(t *testing.T, messages protoreflect.MessageDescriptors) {
89+
t.Helper()
90+
for i := 0; i < messages.Len(); i++ {
91+
verifyDescriptorOptions(t, string(messages.Get(i).Name()), messages.Get(i).Options())
92+
verifyEnums(t, messages.Get(i).Enums())
93+
verifyMessages(t, messages.Get(i).Messages())
94+
verifyExtensions(t, messages.Get(i).Extensions())
95+
verifyFields(t, messages.Get(i).Fields())
96+
}
97+
}
98+
99+
func verifyFields(t *testing.T, fields protoreflect.FieldDescriptors) {
100+
t.Helper()
101+
for i := 0; i < fields.Len(); i++ {
102+
verifyDescriptorOptions(t, string(fields.Get(i).Name()), fields.Get(i).Options())
103+
}
104+
}
105+
106+
func verifyEnums(t *testing.T, enums protoreflect.EnumDescriptors) {
107+
t.Helper()
108+
for i := 0; i < enums.Len(); i++ {
109+
verifyDescriptorOptions(t, string(enums.Get(i).Name()), enums.Get(i).Options())
110+
verifyEnumValues(t, enums.Get(i).Values())
111+
}
112+
}
113+
114+
func verifyEnumValues(t *testing.T, values protoreflect.EnumValueDescriptors) {
115+
t.Helper()
116+
for i := 0; i < values.Len(); i++ {
117+
verifyDescriptorOptions(t, string(values.Get(i).Name()), values.Get(i).Options())
118+
}
119+
}
120+
121+
func verifyServices(t *testing.T, services protoreflect.ServiceDescriptors) {
122+
t.Helper()
123+
for i := 0; i < services.Len(); i++ {
124+
verifyDescriptorOptions(t, string(services.Get(i).Name()), services.Get(i).Options())
125+
verifyMethods(t, services.Get(i).Methods())
126+
}
127+
}
128+
129+
func verifyMethods(t *testing.T, methods protoreflect.MethodDescriptors) {
130+
t.Helper()
131+
for i := 0; i < methods.Len(); i++ {
132+
verifyDescriptorOptions(t, string(methods.Get(i).Name()), methods.Get(i).Options())
133+
}
134+
}
135+
136+
func verifyDescriptorOptions(t *testing.T, entity string, options protoreflect.ProtoMessage) {
137+
t.Helper()
138+
options.ProtoReflect().Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
139+
maybeVerifyOption(t, fd, v)
140+
return true
141+
})
142+
}
143+
144+
func maybeVerifyOption(t *testing.T, fd protoreflect.FieldDescriptor, v protoreflect.Value) {
145+
t.Helper()
146+
if fd.Kind() == protoreflect.MessageKind && string(fd.Message().FullName()) == "goproto.proto.testretention.OptionsMessage" {
147+
if fd.IsList() {
148+
for i := 0; i < v.List().Len(); i++ {
149+
verifyOptionsMessage(t, string(fd.FullName()), v.List().Get(i).Message().Interface().(*retentionpb.OptionsMessage))
150+
}
151+
} else {
152+
verifyOptionsMessage(t, string(fd.FullName()), v.Message().Interface().(*retentionpb.OptionsMessage))
153+
}
154+
}
155+
}
156+
157+
func verifyOptionsMessage(t *testing.T, entity string, msg *retentionpb.OptionsMessage) {
158+
t.Helper()
159+
if msg.PlainField == nil {
160+
t.Errorf("%s.OptionsMessage.HasField(plain_field): got false, want true", entity)
161+
}
162+
if msg.GetPlainField() != 1 {
163+
t.Errorf("%s.OptionsMessage.GetField(plain_field): got %d, want 1", entity, msg.GetPlainField())
164+
}
165+
if msg.RuntimeRetentionField == nil {
166+
t.Errorf("%s.OptionsMessage.HasField(runtime_retention_field): got false, want true", entity)
167+
}
168+
if msg.GetRuntimeRetentionField() != 2 {
169+
t.Errorf("%s.OptionsMessage.GetField(runtime_retention_field): got %d, want 2", entity, msg.GetRuntimeRetentionField())
170+
}
171+
if msg.SourceRetentionField != nil {
172+
t.Errorf("%s.OptionsMessage.HasField(source_retention_field): got true, want false", entity)
173+
}
174+
if msg.GetSourceRetentionField() != 0 {
175+
// Checking that we get 0 even though this was set to 3 in the source file
176+
t.Errorf("%s.OptionsMessage.GetField(source_retention_field): got %d, want 0", entity, msg.GetSourceRetentionField())
177+
}
178+
}

cmd/protoc-gen-go/testdata/gen_test.go

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)