From 0a27424d861c68eada9cf3e2a1a1fec2b6f97906 Mon Sep 17 00:00:00 2001 From: Walter Schulze Date: Sun, 28 Aug 2016 15:15:29 +0200 Subject: [PATCH] merged 1cc4d6fa4abfabc2fe9cb202f3f1dde0e1032e86 from golang/protobuf --- proto/Makefile | 2 +- proto/any_test.go | 272 ++++++++++++++++++++++++++++++++ proto/proto3_proto/proto3.pb.go | 96 ++++++----- proto/proto3_proto/proto3.proto | 8 +- proto/text.go | 137 +++++++++++++--- proto/text_gogo.go | 6 +- proto/text_parser.go | 97 ++++++++++-- 7 files changed, 533 insertions(+), 85 deletions(-) create mode 100644 proto/any_test.go diff --git a/proto/Makefile b/proto/Makefile index 23a6b17344..b7bbfa08e6 100644 --- a/proto/Makefile +++ b/proto/Makefile @@ -39,5 +39,5 @@ test: install generate-test-pbs generate-test-pbs: make install make -C testdata - protoc-min-version --version="3.0.0" --proto_path=.:../../../../ --gogo_out=. proto3_proto/proto3.proto + protoc-min-version --version="3.0.0" --proto_path=.:../../../../ --gogo_out=Mtestdata/test.proto=github.com/gogo/protobuf/proto/testdata,Mgoogle/protobuf/any.proto=github.com/gogo/protobuf/types:. proto3_proto/proto3.proto make diff --git a/proto/any_test.go b/proto/any_test.go new file mode 100644 index 0000000000..a2d06e19f5 --- /dev/null +++ b/proto/any_test.go @@ -0,0 +1,272 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2016 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "strings" + "testing" + + "github.com/gogo/protobuf/proto" + + pb "github.com/gogo/protobuf/proto/proto3_proto" + testpb "github.com/gogo/protobuf/proto/testdata" + "github.com/gogo/protobuf/types" +) + +var ( + expandedMarshaler = proto.TextMarshaler{ExpandAny: true} + expandedCompactMarshaler = proto.TextMarshaler{Compact: true, ExpandAny: true} +) + +// anyEqual reports whether two messages which may be google.protobuf.Any or may +// contain google.protobuf.Any fields are equal. We can't use proto.Equal for +// comparison, because semantically equivalent messages may be marshaled to +// binary in different tag order. Instead, trust that TextMarshaler with +// ExpandAny option works and compare the text marshaling results. +func anyEqual(got, want proto.Message) bool { + // if messages are proto.Equal, no need to marshal. + if proto.Equal(got, want) { + return true + } + g := expandedMarshaler.Text(got) + w := expandedMarshaler.Text(want) + return g == w +} + +type golden struct { + m proto.Message + t, c string +} + +var goldenMessages = makeGolden() + +func makeGolden() []golden { + nested := &pb.Nested{Bunny: "Monty"} + nb, err := proto.Marshal(nested) + if err != nil { + panic(err) + } + m1 := &pb.Message{ + Name: "David", + ResultCount: 47, + Anything: &types.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb}, + } + m2 := &pb.Message{ + Name: "David", + ResultCount: 47, + Anything: &types.Any{TypeUrl: "http://[::1]/type.googleapis.com/" + proto.MessageName(nested), Value: nb}, + } + m3 := &pb.Message{ + Name: "David", + ResultCount: 47, + Anything: &types.Any{TypeUrl: `type.googleapis.com/"/` + proto.MessageName(nested), Value: nb}, + } + m4 := &pb.Message{ + Name: "David", + ResultCount: 47, + Anything: &types.Any{TypeUrl: "type.googleapis.com/a/path/" + proto.MessageName(nested), Value: nb}, + } + m5 := &types.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb} + + any1 := &testpb.MyMessage{Count: proto.Int32(47), Name: proto.String("David")} + proto.SetExtension(any1, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("foo")}) + proto.SetExtension(any1, testpb.E_Ext_Text, proto.String("bar")) + any1b, err := proto.Marshal(any1) + if err != nil { + panic(err) + } + any2 := &testpb.MyMessage{Count: proto.Int32(42), Bikeshed: testpb.MyMessage_GREEN.Enum(), RepBytes: [][]byte{[]byte("roboto")}} + proto.SetExtension(any2, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("baz")}) + any2b, err := proto.Marshal(any2) + if err != nil { + panic(err) + } + m6 := &pb.Message{ + Name: "David", + ResultCount: 47, + Anything: &types.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b}, + ManyThings: []*types.Any{ + {TypeUrl: "type.googleapis.com/" + proto.MessageName(any2), Value: any2b}, + {TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b}, + }, + } + + const ( + m1Golden = ` +name: "David" +result_count: 47 +anything: < + [type.googleapis.com/proto3_proto.Nested]: < + bunny: "Monty" + > +> +` + m2Golden = ` +name: "David" +result_count: 47 +anything: < + ["http://[::1]/type.googleapis.com/proto3_proto.Nested"]: < + bunny: "Monty" + > +> +` + m3Golden = ` +name: "David" +result_count: 47 +anything: < + ["type.googleapis.com/\"/proto3_proto.Nested"]: < + bunny: "Monty" + > +> +` + m4Golden = ` +name: "David" +result_count: 47 +anything: < + [type.googleapis.com/a/path/proto3_proto.Nested]: < + bunny: "Monty" + > +> +` + m5Golden = ` +[type.googleapis.com/proto3_proto.Nested]: < + bunny: "Monty" +> +` + m6Golden = ` +name: "David" +result_count: 47 +anything: < + [type.googleapis.com/testdata.MyMessage]: < + count: 47 + name: "David" + [testdata.Ext.more]: < + data: "foo" + > + [testdata.Ext.text]: "bar" + > +> +many_things: < + [type.googleapis.com/testdata.MyMessage]: < + count: 42 + bikeshed: GREEN + rep_bytes: "roboto" + [testdata.Ext.more]: < + data: "baz" + > + > +> +many_things: < + [type.googleapis.com/testdata.MyMessage]: < + count: 47 + name: "David" + [testdata.Ext.more]: < + data: "foo" + > + [testdata.Ext.text]: "bar" + > +> +` + ) + return []golden{ + {m1, strings.TrimSpace(m1Golden) + "\n", strings.TrimSpace(compact(m1Golden)) + " "}, + {m2, strings.TrimSpace(m2Golden) + "\n", strings.TrimSpace(compact(m2Golden)) + " "}, + {m3, strings.TrimSpace(m3Golden) + "\n", strings.TrimSpace(compact(m3Golden)) + " "}, + {m4, strings.TrimSpace(m4Golden) + "\n", strings.TrimSpace(compact(m4Golden)) + " "}, + {m5, strings.TrimSpace(m5Golden) + "\n", strings.TrimSpace(compact(m5Golden)) + " "}, + {m6, strings.TrimSpace(m6Golden) + "\n", strings.TrimSpace(compact(m6Golden)) + " "}, + } +} + +func TestMarshalGolden(t *testing.T) { + for _, tt := range goldenMessages { + if got, want := expandedMarshaler.Text(tt.m), tt.t; got != want { + t.Errorf("message %v: got:\n%s\nwant:\n%s", tt.m, got, want) + } + if got, want := expandedCompactMarshaler.Text(tt.m), tt.c; got != want { + t.Errorf("message %v: got:\n`%s`\nwant:\n`%s`", tt.m, got, want) + } + } +} + +func TestUnmarshalGolden(t *testing.T) { + for _, tt := range goldenMessages { + want := tt.m + got := proto.Clone(tt.m) + got.Reset() + if err := proto.UnmarshalText(tt.t, got); err != nil { + t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.t, err) + } + if !anyEqual(got, want) { + t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.t, got, want) + } + got.Reset() + if err := proto.UnmarshalText(tt.c, got); err != nil { + t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.c, err) + } + if !anyEqual(got, want) { + t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.c, got, want) + } + } +} + +func TestMarsahlUnknownAny(t *testing.T) { + m := &pb.Message{ + Anything: &types.Any{ + TypeUrl: "foo", + Value: []byte("bar"), + }, + } + want := `anything: < + type_url: "foo" + value: "bar" +> +` + got := expandedMarshaler.Text(m) + if got != want { + t.Errorf("got\n`%s`\nwant\n`%s`", got, want) + } +} + +func TestAmbiguousAny(t *testing.T) { + pb := &types.Any{} + err := proto.UnmarshalText(` + [type.googleapis.com/proto3_proto.Nested]: < + bunny: "Monty" + > + type_url: "ttt/proto3_proto.Nested" + `, pb) + t.Logf("result: %v (error: %v)", expandedMarshaler.Text(pb), err) + if err != nil { + t.Errorf("failed to parse ambiguous Any message: %v", err) + } +} diff --git a/proto/proto3_proto/proto3.pb.go b/proto/proto3_proto/proto3.pb.go index f1835be366..cc53458b6c 100644 --- a/proto/proto3_proto/proto3.pb.go +++ b/proto/proto3_proto/proto3.pb.go @@ -18,6 +18,7 @@ package proto3_proto import proto "github.com/gogo/protobuf/proto" import fmt "fmt" import math "math" +import google_protobuf "github.com/gogo/protobuf/types" import testdata "github.com/gogo/protobuf/proto/testdata" // Reference imports to suppress errors if they are not otherwise used. @@ -72,6 +73,8 @@ type Message struct { Terrain map[string]*Nested `protobuf:"bytes,10,rep,name=terrain" json:"terrain,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"` Proto2Field *testdata.SubDefaults `protobuf:"bytes,11,opt,name=proto2_field,json=proto2Field" json:"proto2_field,omitempty"` Proto2Value map[string]*testdata.SubDefaults `protobuf:"bytes,13,rep,name=proto2_value,json=proto2Value" json:"proto2_value,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"` + Anything *google_protobuf.Any `protobuf:"bytes,14,opt,name=anything" json:"anything,omitempty"` + ManyThings []*google_protobuf.Any `protobuf:"bytes,15,rep,name=many_things,json=manyThings" json:"many_things,omitempty"` } func (m *Message) Reset() { *m = Message{} } @@ -107,6 +110,20 @@ func (m *Message) GetProto2Value() map[string]*testdata.SubDefaults { return nil } +func (m *Message) GetAnything() *google_protobuf.Any { + if m != nil { + return m.Anything + } + return nil +} + +func (m *Message) GetManyThings() []*google_protobuf.Any { + if m != nil { + return m.ManyThings + } + return nil +} + type Nested struct { Bunny string `protobuf:"bytes,1,opt,name=bunny,proto3" json:"bunny,omitempty"` Cute bool `protobuf:"varint,2,opt,name=cute,proto3" json:"cute,omitempty"` @@ -143,42 +160,45 @@ func init() { func init() { proto.RegisterFile("proto3_proto/proto3.proto", fileDescriptorProto3) } var fileDescriptorProto3 = []byte{ - // 585 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x52, 0xef, 0x6a, 0xdb, 0x3e, - 0x14, 0xfd, 0x29, 0x4e, 0x9d, 0xf4, 0xda, 0xe9, 0xcf, 0x88, 0x0e, 0xb4, 0xb0, 0x0f, 0x5a, 0x06, - 0xc3, 0xec, 0x8f, 0x0b, 0x19, 0x83, 0x32, 0xc6, 0x46, 0xdb, 0xb5, 0x2c, 0x34, 0xcd, 0x82, 0xd2, - 0xae, 0xec, 0x93, 0xb1, 0x53, 0xc5, 0x31, 0x8b, 0xed, 0x60, 0x4b, 0x03, 0x3f, 0xc5, 0xde, 0x61, - 0x4f, 0x3a, 0x2c, 0x39, 0xad, 0x5b, 0xb2, 0xed, 0x93, 0xaf, 0x8e, 0xcf, 0xbd, 0xe7, 0xe8, 0x5c, - 0xc1, 0xe3, 0x75, 0x9e, 0x89, 0xec, 0x8d, 0xaf, 0x3e, 0x07, 0xfa, 0xe0, 0xa9, 0x0f, 0xb6, 0x9b, - 0xbf, 0xfa, 0xc3, 0x28, 0x16, 0x4b, 0x19, 0x7a, 0xf3, 0x2c, 0x39, 0x88, 0xb2, 0xa8, 0xe6, 0x86, - 0x72, 0xa1, 0x8b, 0x03, 0xc1, 0x0b, 0x71, 0x13, 0x88, 0x40, 0x15, 0x7a, 0xc2, 0xe0, 0xa7, 0x09, - 0x9d, 0x0b, 0x5e, 0x14, 0x41, 0xc4, 0x31, 0x86, 0x76, 0x1a, 0x24, 0x9c, 0x20, 0x8a, 0xdc, 0x5d, - 0xa6, 0x6a, 0x7c, 0x08, 0xdd, 0x65, 0xbc, 0x0a, 0xf2, 0x58, 0x94, 0xa4, 0x45, 0x91, 0xbb, 0x37, - 0x7c, 0xe2, 0x35, 0x45, 0xbd, 0xba, 0xd9, 0xfb, 0x2c, 0x93, 0x4c, 0xe6, 0xec, 0x96, 0x8d, 0x29, - 0xd8, 0x4b, 0x1e, 0x47, 0x4b, 0xe1, 0xc7, 0xa9, 0x3f, 0x4f, 0x88, 0x41, 0x91, 0xdb, 0x63, 0xa0, - 0xb1, 0x51, 0x7a, 0x92, 0x54, 0x7a, 0x95, 0x1d, 0xd2, 0xa6, 0xc8, 0xb5, 0x99, 0xaa, 0xf1, 0x53, - 0xb0, 0x73, 0x5e, 0xc8, 0x95, 0xf0, 0xe7, 0x99, 0x4c, 0x05, 0xe9, 0x50, 0xe4, 0x1a, 0xcc, 0xd2, - 0xd8, 0x49, 0x05, 0xe1, 0x67, 0xd0, 0x13, 0xb9, 0xe4, 0x7e, 0x31, 0xcf, 0x44, 0x91, 0x04, 0x29, - 0xe9, 0x52, 0xe4, 0x76, 0x99, 0x5d, 0x81, 0xb3, 0x1a, 0xc3, 0xfb, 0xb0, 0x53, 0xcc, 0xb3, 0x9c, - 0x93, 0x5d, 0x8a, 0xdc, 0x16, 0xd3, 0x07, 0xec, 0x80, 0xf1, 0x9d, 0x97, 0x64, 0x87, 0x1a, 0x6e, - 0x9b, 0x55, 0x25, 0x7e, 0x05, 0x66, 0xca, 0x0b, 0xc1, 0x6f, 0x88, 0x49, 0x91, 0x6b, 0x0d, 0xf7, - 0xef, 0xdf, 0x6e, 0xa2, 0xfe, 0xb1, 0x9a, 0x83, 0xdf, 0x42, 0x27, 0xf7, 0x17, 0x32, 0x4d, 0x4b, - 0xe2, 0x50, 0xe3, 0x9f, 0x61, 0x98, 0xf9, 0x59, 0xc5, 0xc5, 0xef, 0xa1, 0x23, 0x78, 0x9e, 0x07, - 0x71, 0x4a, 0x80, 0x1a, 0xae, 0x35, 0x1c, 0x6c, 0x6f, 0xbb, 0xd4, 0xa4, 0xd3, 0x54, 0xe4, 0x25, - 0xdb, 0xb4, 0xe0, 0x43, 0xd0, 0x6b, 0x1e, 0xfa, 0x8b, 0x98, 0xaf, 0x6e, 0x88, 0xa5, 0x8c, 0x3e, - 0xf2, 0x36, 0xeb, 0xf4, 0x66, 0x32, 0xfc, 0xc4, 0x17, 0x81, 0x5c, 0x89, 0x82, 0x59, 0x9a, 0x7a, - 0x56, 0x31, 0xf1, 0xe8, 0xb6, 0xf3, 0x47, 0xb0, 0x92, 0x9c, 0xf4, 0x94, 0xf8, 0xf3, 0xed, 0xe2, - 0x53, 0xc5, 0xfc, 0x5a, 0x11, 0xb5, 0x81, 0x7a, 0x94, 0x42, 0xfa, 0x53, 0xb0, 0x9b, 0xee, 0x36, - 0x49, 0xea, 0xa7, 0xa2, 0x92, 0x7c, 0x01, 0x3b, 0x5a, 0xa5, 0xf5, 0x97, 0x20, 0x35, 0xe5, 0x5d, - 0xeb, 0x10, 0xf5, 0xaf, 0xc0, 0x79, 0x28, 0xb9, 0x65, 0xea, 0xcb, 0xfb, 0x53, 0xff, 0x70, 0xeb, - 0xbb, 0xb1, 0x83, 0x8f, 0x60, 0xea, 0xf4, 0xb1, 0x05, 0x9d, 0xab, 0xc9, 0xf9, 0xe4, 0xcb, 0xf5, - 0xc4, 0xf9, 0x0f, 0x77, 0xa1, 0x3d, 0xbd, 0x9a, 0xcc, 0x1c, 0x84, 0x7b, 0xb0, 0x3b, 0x1b, 0x1f, - 0x4d, 0x67, 0x97, 0xa3, 0x93, 0x73, 0xa7, 0x85, 0xff, 0x07, 0xeb, 0x78, 0x34, 0x1e, 0xfb, 0xc7, - 0x47, 0xa3, 0xf1, 0xe9, 0x37, 0xc7, 0x18, 0x0c, 0xc1, 0xd4, 0x66, 0xab, 0x37, 0x14, 0xaa, 0x5d, - 0x6b, 0x3f, 0xfa, 0x50, 0xbd, 0xda, 0xb9, 0x14, 0xda, 0x50, 0x97, 0xa9, 0x7a, 0xf0, 0x0b, 0xc1, - 0x5e, 0x9d, 0xe3, 0x75, 0x2c, 0x96, 0x17, 0xc1, 0x1a, 0x4f, 0xc1, 0x0e, 0x4b, 0xc1, 0xfd, 0x24, - 0x58, 0xaf, 0xe3, 0x34, 0x22, 0x48, 0x65, 0xff, 0x7a, 0x6b, 0xf6, 0x75, 0x8f, 0x77, 0x5c, 0x0a, - 0x7e, 0xa1, 0xf9, 0xf5, 0x0a, 0xc2, 0x3b, 0xa4, 0xff, 0x01, 0x9c, 0x87, 0x84, 0x66, 0x60, 0x5d, - 0x1d, 0xd8, 0x7e, 0x33, 0x30, 0xbb, 0x91, 0x4c, 0x68, 0x6a, 0xe9, 0xdf, 0x01, 0x00, 0x00, 0xff, - 0xff, 0x75, 0x74, 0x87, 0xb4, 0x50, 0x04, 0x00, 0x00, + // 625 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x53, 0x5d, 0x6f, 0xd3, 0x30, + 0x14, 0x25, 0x4d, 0x97, 0x66, 0x37, 0xe9, 0x16, 0x99, 0x21, 0x79, 0x15, 0x0f, 0xa1, 0x48, 0x28, + 0xe2, 0x23, 0x43, 0x45, 0x93, 0x26, 0x84, 0x40, 0xdb, 0xd8, 0x44, 0xb5, 0xae, 0x54, 0xee, 0xc6, + 0xc4, 0x53, 0xe4, 0x76, 0x6e, 0x1b, 0xd1, 0x38, 0x55, 0xe2, 0x20, 0xe5, 0xef, 0xf0, 0x2b, 0x79, + 0x44, 0xb6, 0xd3, 0xae, 0x9b, 0x0a, 0x3c, 0xe5, 0xfa, 0xf8, 0xdc, 0x0f, 0x9f, 0x73, 0x03, 0xfb, + 0x8b, 0x2c, 0x15, 0xe9, 0xbb, 0x48, 0x7d, 0x0e, 0xf4, 0x21, 0x54, 0x1f, 0xe4, 0xae, 0x5f, 0xb5, + 0xf6, 0xa7, 0x69, 0x3a, 0x9d, 0x33, 0x4d, 0x19, 0x15, 0x93, 0x03, 0xca, 0x4b, 0x4d, 0x6c, 0x3d, + 0x16, 0x2c, 0x17, 0xb7, 0x54, 0xd0, 0x03, 0x19, 0x68, 0xb0, 0xfd, 0xdb, 0x82, 0xc6, 0x25, 0xcb, + 0x73, 0x3a, 0x65, 0x08, 0x41, 0x9d, 0xd3, 0x84, 0x61, 0xc3, 0x37, 0x82, 0x6d, 0xa2, 0x62, 0x74, + 0x04, 0xf6, 0x2c, 0x9e, 0xd3, 0x2c, 0x16, 0x25, 0xae, 0xf9, 0x46, 0xb0, 0xd3, 0x79, 0x1a, 0xae, + 0x37, 0x0c, 0xab, 0xe4, 0xf0, 0x4b, 0x91, 0xa4, 0x45, 0x46, 0x56, 0x6c, 0xe4, 0x83, 0x3b, 0x63, + 0xf1, 0x74, 0x26, 0xa2, 0x98, 0x47, 0xe3, 0x04, 0x9b, 0xbe, 0x11, 0x34, 0x09, 0x68, 0xac, 0xcb, + 0x4f, 0x13, 0xd9, 0x4f, 0x8e, 0x83, 0xeb, 0xbe, 0x11, 0xb8, 0x44, 0xc5, 0xe8, 0x19, 0xb8, 0x19, + 0xcb, 0x8b, 0xb9, 0x88, 0xc6, 0x69, 0xc1, 0x05, 0x6e, 0xf8, 0x46, 0x60, 0x12, 0x47, 0x63, 0xa7, + 0x12, 0x42, 0xcf, 0xa1, 0x29, 0xb2, 0x82, 0x45, 0xf9, 0x38, 0x15, 0x79, 0x42, 0x39, 0xb6, 0x7d, + 0x23, 0xb0, 0x89, 0x2b, 0xc1, 0x61, 0x85, 0xa1, 0x3d, 0xd8, 0xca, 0xc7, 0x69, 0xc6, 0xf0, 0xb6, + 0x6f, 0x04, 0x35, 0xa2, 0x0f, 0xc8, 0x03, 0xf3, 0x07, 0x2b, 0xf1, 0x96, 0x6f, 0x06, 0x75, 0x22, + 0x43, 0xf4, 0x1a, 0x2c, 0xce, 0x72, 0xc1, 0x6e, 0xb1, 0xe5, 0x1b, 0x81, 0xd3, 0xd9, 0xbb, 0xff, + 0xba, 0xbe, 0xba, 0x23, 0x15, 0x07, 0x1d, 0x42, 0x23, 0x8b, 0x26, 0x05, 0xe7, 0x25, 0xf6, 0x7c, + 0xf3, 0xbf, 0x62, 0x58, 0xd9, 0xb9, 0xe4, 0xa2, 0x0f, 0xd0, 0x10, 0x2c, 0xcb, 0x68, 0xcc, 0x31, + 0xf8, 0x66, 0xe0, 0x74, 0xda, 0x9b, 0xd3, 0xae, 0x34, 0xe9, 0x8c, 0x8b, 0xac, 0x24, 0xcb, 0x14, + 0x74, 0x04, 0xda, 0xe2, 0x4e, 0x34, 0x89, 0xd9, 0xfc, 0x16, 0x3b, 0x6a, 0xd0, 0x27, 0xe1, 0xd2, + 0xce, 0x70, 0x58, 0x8c, 0x3e, 0xb3, 0x09, 0x2d, 0xe6, 0x22, 0x27, 0x8e, 0xa6, 0x9e, 0x4b, 0x26, + 0xea, 0xae, 0x32, 0x7f, 0xd2, 0x79, 0xc1, 0x70, 0x53, 0x35, 0x7f, 0xb1, 0xb9, 0xf9, 0x40, 0x31, + 0xbf, 0x49, 0xa2, 0x1e, 0xa0, 0x2a, 0xa5, 0x10, 0xf4, 0x16, 0x6c, 0xca, 0x4b, 0x31, 0x8b, 0xf9, + 0x14, 0xef, 0x54, 0x4a, 0xe9, 0x55, 0x0b, 0x97, 0xab, 0x16, 0x1e, 0xf3, 0x92, 0xac, 0x58, 0xe8, + 0x10, 0x9c, 0x84, 0xf2, 0x32, 0x52, 0xa7, 0x1c, 0xef, 0xaa, 0xde, 0x9b, 0x93, 0x40, 0x12, 0xaf, + 0x14, 0xaf, 0x35, 0x00, 0x77, 0x5d, 0x86, 0xa5, 0x65, 0x7a, 0x27, 0x95, 0x65, 0x2f, 0x61, 0x4b, + 0x3f, 0xa7, 0xf6, 0x0f, 0xc7, 0x34, 0xe5, 0x7d, 0xed, 0xc8, 0x68, 0x5d, 0x83, 0xf7, 0xf0, 0x6d, + 0x1b, 0xaa, 0xbe, 0xba, 0x5f, 0xf5, 0x2f, 0xf2, 0xde, 0x95, 0x6d, 0x7f, 0x02, 0x4b, 0xdb, 0x8c, + 0x1c, 0x68, 0x5c, 0xf7, 0x2f, 0xfa, 0x5f, 0x6f, 0xfa, 0xde, 0x23, 0x64, 0x43, 0x7d, 0x70, 0xdd, + 0x1f, 0x7a, 0x06, 0x6a, 0xc2, 0xf6, 0xb0, 0x77, 0x3c, 0x18, 0x5e, 0x75, 0x4f, 0x2f, 0xbc, 0x1a, + 0xda, 0x05, 0xe7, 0xa4, 0xdb, 0xeb, 0x45, 0x27, 0xc7, 0xdd, 0xde, 0xd9, 0x77, 0xcf, 0x6c, 0x77, + 0xc0, 0xd2, 0xc3, 0xca, 0x65, 0x1d, 0xa9, 0xa5, 0xd2, 0xf3, 0xe8, 0x83, 0xfc, 0x3d, 0xc6, 0x85, + 0xd0, 0x03, 0xd9, 0x44, 0xc5, 0xed, 0x5f, 0x06, 0xec, 0x54, 0x86, 0xdd, 0xc4, 0x62, 0x76, 0x49, + 0x17, 0x68, 0x00, 0xee, 0xa8, 0x14, 0x2c, 0x4a, 0xe8, 0x62, 0x21, 0xdd, 0x31, 0x94, 0xd0, 0x6f, + 0x36, 0x9a, 0x5c, 0xe5, 0x84, 0x27, 0xa5, 0x60, 0x97, 0x9a, 0x5f, 0x79, 0x3d, 0xba, 0x43, 0x5a, + 0x1f, 0xc1, 0x7b, 0x48, 0x58, 0x17, 0xcc, 0xd6, 0x82, 0xed, 0xad, 0x0b, 0xe6, 0xae, 0x29, 0x33, + 0xb2, 0x74, 0xeb, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x78, 0xc9, 0x75, 0x36, 0xb5, 0x04, 0x00, + 0x00, } diff --git a/proto/proto3_proto/proto3.proto b/proto/proto3_proto/proto3.proto index 5169d75c55..3e9a1abbdb 100644 --- a/proto/proto3_proto/proto3.proto +++ b/proto/proto3_proto/proto3.proto @@ -31,9 +31,10 @@ syntax = "proto3"; -package proto3_proto; +import "google/protobuf/any.proto"; +import "testdata/test.proto"; -import "github.com/gogo/protobuf/proto/testdata/test.proto"; +package proto3_proto; message Message { enum Humour { @@ -58,6 +59,9 @@ message Message { map terrain = 10; testdata.SubDefaults proto2_field = 11; map proto2_value = 13; + + google.protobuf.Any anything = 14; + repeated google.protobuf.Any many_things = 15; } message Nested { diff --git a/proto/text.go b/proto/text.go index b3e12e2684..3f68b5d7dd 100644 --- a/proto/text.go +++ b/proto/text.go @@ -181,7 +181,93 @@ type raw interface { Bytes() []byte } -func writeStruct(w *textWriter, sv reflect.Value) error { +func requiresQuotes(u string) bool { + // When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted. + for _, ch := range u { + switch { + case ch == '.' || ch == '/' || ch == '_': + continue + case '0' <= ch && ch <= '9': + continue + case 'A' <= ch && ch <= 'Z': + continue + case 'a' <= ch && ch <= 'z': + continue + default: + return true + } + } + return false +} + +// isAny reports whether sv is a google.protobuf.Any message +func isAny(sv reflect.Value) bool { + type wkt interface { + XXX_WellKnownType() string + } + t, ok := sv.Addr().Interface().(wkt) + return ok && t.XXX_WellKnownType() == "Any" +} + +// writeProto3Any writes an expanded google.protobuf.Any message. +// +// It returns (false, nil) if sv value can't be unmarshaled (e.g. because +// required messages are not linked in). +// +// It returns (true, error) when sv was written in expanded format or an error +// was encountered. +func (tm *TextMarshaler) writeProto3Any(w *textWriter, sv reflect.Value) (bool, error) { + turl := sv.FieldByName("TypeUrl") + val := sv.FieldByName("Value") + if !turl.IsValid() || !val.IsValid() { + return true, errors.New("proto: invalid google.protobuf.Any message") + } + + b, ok := val.Interface().([]byte) + if !ok { + return true, errors.New("proto: invalid google.protobuf.Any message") + } + + parts := strings.Split(turl.String(), "/") + mt := MessageType(parts[len(parts)-1]) + if mt == nil { + return false, nil + } + m := reflect.New(mt.Elem()) + if err := Unmarshal(b, m.Interface().(Message)); err != nil { + return false, nil + } + w.Write([]byte("[")) + u := turl.String() + if requiresQuotes(u) { + writeString(w, u) + } else { + w.Write([]byte(u)) + } + if w.compact { + w.Write([]byte("]:<")) + } else { + w.Write([]byte("]: <\n")) + w.ind++ + } + if err := tm.writeStruct(w, m.Elem()); err != nil { + return true, err + } + if w.compact { + w.Write([]byte("> ")) + } else { + w.ind-- + w.Write([]byte(">\n")) + } + return true, nil +} + +func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error { + if tm.ExpandAny && isAny(sv) { + if canExpand, err := tm.writeProto3Any(w, sv); canExpand { + return err + } + } st := sv.Type() sprops := GetProperties(st) for i := 0; i < sv.NumField(); i++ { @@ -234,10 +320,10 @@ func writeStruct(w *textWriter, sv reflect.Value) error { continue } if len(props.Enum) > 0 { - if err := writeEnum(w, v, props); err != nil { + if err := tm.writeEnum(w, v, props); err != nil { return err } - } else if err := writeAny(w, v, props); err != nil { + } else if err := tm.writeAny(w, v, props); err != nil { return err } if err := w.WriteByte('\n'); err != nil { @@ -279,7 +365,7 @@ func writeStruct(w *textWriter, sv reflect.Value) error { return err } } - if err := writeAny(w, key, props.mkeyprop); err != nil { + if err := tm.writeAny(w, key, props.mkeyprop); err != nil { return err } if err := w.WriteByte('\n'); err != nil { @@ -296,7 +382,7 @@ func writeStruct(w *textWriter, sv reflect.Value) error { return err } } - if err := writeAny(w, val, props.mvalprop); err != nil { + if err := tm.writeAny(w, val, props.mvalprop); err != nil { return err } if err := w.WriteByte('\n'); err != nil { @@ -368,10 +454,10 @@ func writeStruct(w *textWriter, sv reflect.Value) error { } if len(props.Enum) > 0 { - if err := writeEnum(w, fv, props); err != nil { + if err := tm.writeEnum(w, fv, props); err != nil { return err } - } else if err := writeAny(w, fv, props); err != nil { + } else if err := tm.writeAny(w, fv, props); err != nil { return err } @@ -389,7 +475,7 @@ func writeStruct(w *textWriter, sv reflect.Value) error { pv.Elem().Set(sv) } if pv.Type().Implements(extensionRangeType) { - if err := writeExtensions(w, pv); err != nil { + if err := tm.writeExtensions(w, pv); err != nil { return err } } @@ -419,7 +505,7 @@ func writeRaw(w *textWriter, b []byte) error { } // writeAny writes an arbitrary field. -func writeAny(w *textWriter, v reflect.Value, props *Properties) error { +func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Properties) error { v = reflect.Indirect(v) if props != nil && len(props.CustomType) > 0 { @@ -482,15 +568,15 @@ func writeAny(w *textWriter, v reflect.Value, props *Properties) error { } } w.indent() - if tm, ok := v.Interface().(encoding.TextMarshaler); ok { - text, err := tm.MarshalText() + if etm, ok := v.Interface().(encoding.TextMarshaler); ok { + text, err := etm.MarshalText() if err != nil { return err } if _, err = w.Write(text); err != nil { return err } - } else if err := writeStruct(w, v); err != nil { + } else if err := tm.writeStruct(w, v); err != nil { return err } w.unindent() @@ -634,7 +720,7 @@ func (s int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // writeExtensions writes all the extensions in pv. // pv is assumed to be a pointer to a protocol message struct that is extendable. -func writeExtensions(w *textWriter, pv reflect.Value) error { +func (tm *TextMarshaler) writeExtensions(w *textWriter, pv reflect.Value) error { emap := extensionMaps[pv.Type().Elem()] e := pv.Interface().(Message) @@ -689,13 +775,13 @@ func writeExtensions(w *textWriter, pv reflect.Value) error { // Repeated extensions will appear as a slice. if !desc.repeated() { - if err := writeExtension(w, desc.Name, pb); err != nil { + if err := tm.writeExtension(w, desc.Name, pb); err != nil { return err } } else { v := reflect.ValueOf(pb) for i := 0; i < v.Len(); i++ { - if err := writeExtension(w, desc.Name, v.Index(i).Interface()); err != nil { + if err := tm.writeExtension(w, desc.Name, v.Index(i).Interface()); err != nil { return err } } @@ -704,7 +790,7 @@ func writeExtensions(w *textWriter, pv reflect.Value) error { return nil } -func writeExtension(w *textWriter, name string, pb interface{}) error { +func (tm *TextMarshaler) writeExtension(w *textWriter, name string, pb interface{}) error { if _, err := fmt.Fprintf(w, "[%s]:", name); err != nil { return err } @@ -713,7 +799,7 @@ func writeExtension(w *textWriter, name string, pb interface{}) error { return err } } - if err := writeAny(w, reflect.ValueOf(pb), nil); err != nil { + if err := tm.writeAny(w, reflect.ValueOf(pb), nil); err != nil { return err } if err := w.WriteByte('\n'); err != nil { @@ -740,12 +826,13 @@ func (w *textWriter) writeIndent() { // TextMarshaler is a configurable text format marshaler. type TextMarshaler struct { - Compact bool // use compact text format (one line). + Compact bool // use compact text format (one line). + ExpandAny bool // expand google.protobuf.Any messages of known types } // Marshal writes a given protocol buffer in text format. // The only errors returned are from w. -func (m *TextMarshaler) Marshal(w io.Writer, pb Message) error { +func (tm *TextMarshaler) Marshal(w io.Writer, pb Message) error { val := reflect.ValueOf(pb) if pb == nil || val.IsNil() { w.Write([]byte("")) @@ -760,11 +847,11 @@ func (m *TextMarshaler) Marshal(w io.Writer, pb Message) error { aw := &textWriter{ w: ww, complete: true, - compact: m.Compact, + compact: tm.Compact, } - if tm, ok := pb.(encoding.TextMarshaler); ok { - text, err := tm.MarshalText() + if etm, ok := pb.(encoding.TextMarshaler); ok { + text, err := etm.MarshalText() if err != nil { return err } @@ -778,7 +865,7 @@ func (m *TextMarshaler) Marshal(w io.Writer, pb Message) error { } // Dereference the received pointer so we don't have outer < and >. v := reflect.Indirect(val) - if err := writeStruct(aw, v); err != nil { + if err := tm.writeStruct(aw, v); err != nil { return err } if bw != nil { @@ -788,9 +875,9 @@ func (m *TextMarshaler) Marshal(w io.Writer, pb Message) error { } // Text is the same as Marshal, but returns the string directly. -func (m *TextMarshaler) Text(pb Message) string { +func (tm *TextMarshaler) Text(pb Message) string { var buf bytes.Buffer - m.Marshal(&buf, pb) + tm.Marshal(&buf, pb) return buf.String() } diff --git a/proto/text_gogo.go b/proto/text_gogo.go index 5892674197..1d6c6aa0e4 100644 --- a/proto/text_gogo.go +++ b/proto/text_gogo.go @@ -33,10 +33,10 @@ import ( "reflect" ) -func writeEnum(w *textWriter, v reflect.Value, props *Properties) error { +func (tm *TextMarshaler) writeEnum(w *textWriter, v reflect.Value, props *Properties) error { m, ok := enumStringMaps[props.Enum] if !ok { - if err := writeAny(w, v, props); err != nil { + if err := tm.writeAny(w, v, props); err != nil { return err } } @@ -48,7 +48,7 @@ func writeEnum(w *textWriter, v reflect.Value, props *Properties) error { } s, ok := m[key] if !ok { - if err := writeAny(w, v, props); err != nil { + if err := tm.writeAny(w, v, props); err != nil { return err } } diff --git a/proto/text_parser.go b/proto/text_parser.go index bcd732c3c4..a821d419ce 100644 --- a/proto/text_parser.go +++ b/proto/text_parser.go @@ -168,7 +168,7 @@ func (p *textParser) advance() { p.cur.offset, p.cur.line = p.offset, p.line p.cur.unquoted = "" switch p.s[0] { - case '<', '>', '{', '}', ':', '[', ']', ';', ',': + case '<', '>', '{', '}', ':', '[', ']', ';', ',', '/': // Single symbol p.cur.value, p.s = p.s[0:1], p.s[1:len(p.s)] case '"', '\'': @@ -456,7 +456,10 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) error { fieldSet := make(map[string]bool) // A struct is a sequence of "name: value", terminated by one of // '>' or '}', or the end of the input. A name may also be - // "[extension]". + // "[extension]" or "[type/url]". + // + // The whole struct can also be an expanded Any message, like: + // [type/url] < ... struct contents ... > for { tok := p.next() if tok.err != nil { @@ -466,33 +469,66 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) error { break } if tok.value == "[" { - // Looks like an extension. + // Looks like an extension or an Any. // // TODO: Check whether we need to handle // namespace rooted names (e.g. ".something.Foo"). - tok = p.next() - if tok.err != nil { - return tok.err + extName, err := p.consumeExtName() + if err != nil { + return err } + + if s := strings.LastIndex(extName, "/"); s >= 0 { + // If it contains a slash, it's an Any type URL. + messageName := extName[s+1:] + mt := MessageType(messageName) + if mt == nil { + return p.errorf("unrecognized message %q in google.protobuf.Any", messageName) + } + tok = p.next() + if tok.err != nil { + return tok.err + } + // consume an optional colon + if tok.value == ":" { + tok = p.next() + if tok.err != nil { + return tok.err + } + } + var terminator string + switch tok.value { + case "<": + terminator = ">" + case "{": + terminator = "}" + default: + return p.errorf("expected '{' or '<', found %q", tok.value) + } + v := reflect.New(mt.Elem()) + if pe := p.readStruct(v.Elem(), terminator); pe != nil { + return pe + } + b, err := Marshal(v.Interface().(Message)) + if err != nil { + return p.errorf("failed to marshal message of type %q: %v", messageName, err) + } + sv.FieldByName("TypeUrl").SetString(extName) + sv.FieldByName("Value").SetBytes(b) + continue + } + var desc *ExtensionDesc // This could be faster, but it's functional. // TODO: Do something smarter than a linear scan. for _, d := range RegisteredExtensions(reflect.New(st).Interface().(Message)) { - if d.Name == tok.value { + if d.Name == extName { desc = d break } } if desc == nil { - return p.errorf("unrecognized extension %q", tok.value) - } - // Check the extension terminator. - tok = p.next() - if tok.err != nil { - return tok.err - } - if tok.value != "]" { - return p.errorf("unrecognized extension terminator %q", tok.value) + return p.errorf("unrecognized extension %q", extName) } props := &Properties{} @@ -657,6 +693,35 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) error { return reqFieldErr } +// consumeExtName consumes extension name or expanded Any type URL and the +// following ']'. It returns the name or URL consumed. +func (p *textParser) consumeExtName() (string, error) { + tok := p.next() + if tok.err != nil { + return "", tok.err + } + + // If extension name or type url is quoted, it's a single token. + if len(tok.value) > 2 && isQuote(tok.value[0]) && tok.value[len(tok.value)-1] == tok.value[0] { + name, err := unquoteC(tok.value[1:len(tok.value)-1], rune(tok.value[0])) + if err != nil { + return "", err + } + return name, p.consumeToken("]") + } + + // Consume everything up to "]" + var parts []string + for tok.value != "]" { + parts = append(parts, tok.value) + tok = p.next() + if tok.err != nil { + return "", p.errorf("unrecognized type_url or extension name: %s", tok.err) + } + } + return strings.Join(parts, ""), nil +} + // consumeOptionalSeparator consumes an optional semicolon or comma. // It is used in readStruct to provide backward compatibility. func (p *textParser) consumeOptionalSeparator() error {