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
32package proto_test
33
34import (
35	"strings"
36	"testing"
37
38	"github.com/golang/protobuf/proto"
39
40	pb "github.com/golang/protobuf/proto/proto3_proto"
41	testpb "github.com/golang/protobuf/proto/test_proto"
42	anypb "github.com/golang/protobuf/ptypes/any"
43)
44
45var (
46	expandedMarshaler        = proto.TextMarshaler{ExpandAny: true}
47	expandedCompactMarshaler = proto.TextMarshaler{Compact: true, ExpandAny: true}
48)
49
50// anyEqual reports whether two messages which may be google.protobuf.Any or may
51// contain google.protobuf.Any fields are equal. We can't use proto.Equal for
52// comparison, because semantically equivalent messages may be marshaled to
53// binary in different tag order. Instead, trust that TextMarshaler with
54// ExpandAny option works and compare the text marshaling results.
55func anyEqual(got, want proto.Message) bool {
56	// if messages are proto.Equal, no need to marshal.
57	if proto.Equal(got, want) {
58		return true
59	}
60	g := expandedMarshaler.Text(got)
61	w := expandedMarshaler.Text(want)
62	return g == w
63}
64
65type golden struct {
66	m    proto.Message
67	t, c string
68}
69
70var goldenMessages = makeGolden()
71
72func makeGolden() []golden {
73	nested := &pb.Nested{Bunny: "Monty"}
74	nb, err := proto.Marshal(nested)
75	if err != nil {
76		panic(err)
77	}
78	m1 := &pb.Message{
79		Name:        "David",
80		ResultCount: 47,
81		Anything:    &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb},
82	}
83	m2 := &pb.Message{
84		Name:        "David",
85		ResultCount: 47,
86		Anything:    &anypb.Any{TypeUrl: "http://[::1]/type.googleapis.com/" + proto.MessageName(nested), Value: nb},
87	}
88	m3 := &pb.Message{
89		Name:        "David",
90		ResultCount: 47,
91		Anything:    &anypb.Any{TypeUrl: `type.googleapis.com/"/` + proto.MessageName(nested), Value: nb},
92	}
93	m4 := &pb.Message{
94		Name:        "David",
95		ResultCount: 47,
96		Anything:    &anypb.Any{TypeUrl: "type.googleapis.com/a/path/" + proto.MessageName(nested), Value: nb},
97	}
98	m5 := &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb}
99
100	any1 := &testpb.MyMessage{Count: proto.Int32(47), Name: proto.String("David")}
101	proto.SetExtension(any1, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("foo")})
102	proto.SetExtension(any1, testpb.E_Ext_Text, proto.String("bar"))
103	any1b, err := proto.Marshal(any1)
104	if err != nil {
105		panic(err)
106	}
107	any2 := &testpb.MyMessage{Count: proto.Int32(42), Bikeshed: testpb.MyMessage_GREEN.Enum(), RepBytes: [][]byte{[]byte("roboto")}}
108	proto.SetExtension(any2, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("baz")})
109	any2b, err := proto.Marshal(any2)
110	if err != nil {
111		panic(err)
112	}
113	m6 := &pb.Message{
114		Name:        "David",
115		ResultCount: 47,
116		Anything:    &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b},
117		ManyThings: []*anypb.Any{
118			&anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any2), Value: any2b},
119			&anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b},
120		},
121	}
122
123	const (
124		m1Golden = `
125name: "David"
126result_count: 47
127anything: <
128  [type.googleapis.com/proto3_proto.Nested]: <
129    bunny: "Monty"
130  >
131>
132`
133		m2Golden = `
134name: "David"
135result_count: 47
136anything: <
137  ["http://[::1]/type.googleapis.com/proto3_proto.Nested"]: <
138    bunny: "Monty"
139  >
140>
141`
142		m3Golden = `
143name: "David"
144result_count: 47
145anything: <
146  ["type.googleapis.com/\"/proto3_proto.Nested"]: <
147    bunny: "Monty"
148  >
149>
150`
151		m4Golden = `
152name: "David"
153result_count: 47
154anything: <
155  [type.googleapis.com/a/path/proto3_proto.Nested]: <
156    bunny: "Monty"
157  >
158>
159`
160		m5Golden = `
161[type.googleapis.com/proto3_proto.Nested]: <
162  bunny: "Monty"
163>
164`
165		m6Golden = `
166name: "David"
167result_count: 47
168anything: <
169  [type.googleapis.com/test_proto.MyMessage]: <
170    count: 47
171    name: "David"
172    [test_proto.Ext.more]: <
173      data: "foo"
174    >
175    [test_proto.Ext.text]: "bar"
176  >
177>
178many_things: <
179  [type.googleapis.com/test_proto.MyMessage]: <
180    count: 42
181    bikeshed: GREEN
182    rep_bytes: "roboto"
183    [test_proto.Ext.more]: <
184      data: "baz"
185    >
186  >
187>
188many_things: <
189  [type.googleapis.com/test_proto.MyMessage]: <
190    count: 47
191    name: "David"
192    [test_proto.Ext.more]: <
193      data: "foo"
194    >
195    [test_proto.Ext.text]: "bar"
196  >
197>
198`
199	)
200	return []golden{
201		{m1, strings.TrimSpace(m1Golden) + "\n", strings.TrimSpace(compact(m1Golden)) + " "},
202		{m2, strings.TrimSpace(m2Golden) + "\n", strings.TrimSpace(compact(m2Golden)) + " "},
203		{m3, strings.TrimSpace(m3Golden) + "\n", strings.TrimSpace(compact(m3Golden)) + " "},
204		{m4, strings.TrimSpace(m4Golden) + "\n", strings.TrimSpace(compact(m4Golden)) + " "},
205		{m5, strings.TrimSpace(m5Golden) + "\n", strings.TrimSpace(compact(m5Golden)) + " "},
206		{m6, strings.TrimSpace(m6Golden) + "\n", strings.TrimSpace(compact(m6Golden)) + " "},
207	}
208}
209
210func TestMarshalGolden(t *testing.T) {
211	for _, tt := range goldenMessages {
212		if got, want := expandedMarshaler.Text(tt.m), tt.t; got != want {
213			t.Errorf("message %v: got:\n%s\nwant:\n%s", tt.m, got, want)
214		}
215		if got, want := expandedCompactMarshaler.Text(tt.m), tt.c; got != want {
216			t.Errorf("message %v: got:\n`%s`\nwant:\n`%s`", tt.m, got, want)
217		}
218	}
219}
220
221func TestUnmarshalGolden(t *testing.T) {
222	for _, tt := range goldenMessages {
223		want := tt.m
224		got := proto.Clone(tt.m)
225		got.Reset()
226		if err := proto.UnmarshalText(tt.t, got); err != nil {
227			t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.t, err)
228		}
229		if !anyEqual(got, want) {
230			t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.t, got, want)
231		}
232		got.Reset()
233		if err := proto.UnmarshalText(tt.c, got); err != nil {
234			t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.c, err)
235		}
236		if !anyEqual(got, want) {
237			t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.c, got, want)
238		}
239	}
240}
241
242func TestMarshalUnknownAny(t *testing.T) {
243	m := &pb.Message{
244		Anything: &anypb.Any{
245			TypeUrl: "foo",
246			Value:   []byte("bar"),
247		},
248	}
249	want := `anything: <
250  type_url: "foo"
251  value: "bar"
252>
253`
254	got := expandedMarshaler.Text(m)
255	if got != want {
256		t.Errorf("got\n`%s`\nwant\n`%s`", got, want)
257	}
258}
259
260func TestAmbiguousAny(t *testing.T) {
261	pb := &anypb.Any{}
262	err := proto.UnmarshalText(`
263	type_url: "ttt/proto3_proto.Nested"
264	value: "\n\x05Monty"
265	`, pb)
266	t.Logf("result: %v (error: %v)", expandedMarshaler.Text(pb), err)
267	if err != nil {
268		t.Errorf("failed to parse ambiguous Any message: %v", err)
269	}
270}
271
272func TestUnmarshalOverwriteAny(t *testing.T) {
273	pb := &anypb.Any{}
274	err := proto.UnmarshalText(`
275  [type.googleapis.com/a/path/proto3_proto.Nested]: <
276    bunny: "Monty"
277  >
278  [type.googleapis.com/a/path/proto3_proto.Nested]: <
279    bunny: "Rabbit of Caerbannog"
280  >
281	`, pb)
282	want := `line 7: Any message unpacked multiple times, or "type_url" already set`
283	if err.Error() != want {
284		t.Errorf("incorrect error.\nHave: %v\nWant: %v", err.Error(), want)
285	}
286}
287
288func TestUnmarshalAnyMixAndMatch(t *testing.T) {
289	pb := &anypb.Any{}
290	err := proto.UnmarshalText(`
291	value: "\n\x05Monty"
292  [type.googleapis.com/a/path/proto3_proto.Nested]: <
293    bunny: "Rabbit of Caerbannog"
294  >
295	`, pb)
296	want := `line 5: Any message unpacked multiple times, or "value" already set`
297	if err.Error() != want {
298		t.Errorf("incorrect error.\nHave: %v\nWant: %v", err.Error(), want)
299	}
300}
301