blob: 174a6ba643881fbee55261037378482c4a5abe14 [file] [log] [blame]
Herbie Ong7b828bc2019-02-08 19:56:24 -08001// 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
Damien Neil5c5b5312019-05-14 12:44:37 -07005package protojson_test
Herbie Ong7b828bc2019-02-08 19:56:24 -08006
7import (
Damien Neilbc310b52019-04-11 11:46:55 -07008 "bytes"
Herbie Ong0b0f4032019-03-18 19:06:15 -07009 "encoding/hex"
Herbie Ong7b828bc2019-02-08 19:56:24 -080010 "math"
11 "strings"
12 "testing"
13
Herbie Ong7b828bc2019-02-08 19:56:24 -080014 "github.com/google/go-cmp/cmp"
15 "github.com/google/go-cmp/cmp/cmpopts"
Damien Neil5c5b5312019-05-14 12:44:37 -070016 "google.golang.org/protobuf/encoding/protojson"
Damien Neile89e6242019-05-13 23:55:40 -070017 "google.golang.org/protobuf/internal/encoding/pack"
Damien Neile89e6242019-05-13 23:55:40 -070018 pimpl "google.golang.org/protobuf/internal/impl"
19 "google.golang.org/protobuf/internal/scalar"
20 "google.golang.org/protobuf/proto"
21 preg "google.golang.org/protobuf/reflect/protoregistry"
22 "google.golang.org/protobuf/runtime/protoiface"
Herbie Ong7b828bc2019-02-08 19:56:24 -080023
Damien Neile89e6242019-05-13 23:55:40 -070024 "google.golang.org/protobuf/encoding/testprotos/pb2"
25 "google.golang.org/protobuf/encoding/testprotos/pb3"
Joe Tsaia95b29f2019-05-16 12:47:20 -070026 "google.golang.org/protobuf/types/known/anypb"
27 "google.golang.org/protobuf/types/known/durationpb"
28 "google.golang.org/protobuf/types/known/emptypb"
29 "google.golang.org/protobuf/types/known/fieldmaskpb"
30 "google.golang.org/protobuf/types/known/structpb"
31 "google.golang.org/protobuf/types/known/timestamppb"
32 "google.golang.org/protobuf/types/known/wrapperspb"
Herbie Ong7b828bc2019-02-08 19:56:24 -080033)
34
35// splitLines is a cmpopts.Option for comparing strings with line breaks.
36var splitLines = cmpopts.AcyclicTransformer("SplitLines", func(s string) []string {
37 return strings.Split(s, "\n")
38})
39
40func pb2Enum(i int32) *pb2.Enum {
41 p := new(pb2.Enum)
42 *p = pb2.Enum(i)
43 return p
44}
45
46func pb2Enums_NestedEnum(i int32) *pb2.Enums_NestedEnum {
47 p := new(pb2.Enums_NestedEnum)
48 *p = pb2.Enums_NestedEnum(i)
49 return p
50}
51
Joe Tsai378c1322019-04-25 23:48:08 -070052// TODO: Replace this with proto.SetExtension.
Joe Tsai4fddeba2019-03-20 18:29:32 -070053func setExtension(m proto.Message, xd *protoiface.ExtensionDescV1, val interface{}) {
Joe Tsai378c1322019-04-25 23:48:08 -070054 m.ProtoReflect().Set(xd.Type, xd.Type.ValueOf(val))
Herbie Ongf83d5bb2019-03-14 00:01:27 -070055}
56
Herbie Ong0b0f4032019-03-18 19:06:15 -070057// dhex decodes a hex-string and returns the bytes and panics if s is invalid.
58func dhex(s string) []byte {
59 b, err := hex.DecodeString(s)
60 if err != nil {
61 panic(err)
62 }
63 return b
64}
65
Herbie Ong7b828bc2019-02-08 19:56:24 -080066func TestMarshal(t *testing.T) {
67 tests := []struct {
Herbie Ong0b0f4032019-03-18 19:06:15 -070068 desc string
Damien Neil5c5b5312019-05-14 12:44:37 -070069 mo protojson.MarshalOptions
Herbie Ong0b0f4032019-03-18 19:06:15 -070070 input proto.Message
71 want string
72 wantErr bool // TODO: Verify error message substring.
Herbie Ong7b828bc2019-02-08 19:56:24 -080073 }{{
74 desc: "proto2 optional scalars not set",
75 input: &pb2.Scalars{},
76 want: "{}",
77 }, {
78 desc: "proto3 scalars not set",
79 input: &pb3.Scalars{},
80 want: "{}",
81 }, {
82 desc: "proto2 optional scalars set to zero values",
83 input: &pb2.Scalars{
84 OptBool: scalar.Bool(false),
85 OptInt32: scalar.Int32(0),
86 OptInt64: scalar.Int64(0),
87 OptUint32: scalar.Uint32(0),
88 OptUint64: scalar.Uint64(0),
89 OptSint32: scalar.Int32(0),
90 OptSint64: scalar.Int64(0),
91 OptFixed32: scalar.Uint32(0),
92 OptFixed64: scalar.Uint64(0),
93 OptSfixed32: scalar.Int32(0),
94 OptSfixed64: scalar.Int64(0),
95 OptFloat: scalar.Float32(0),
96 OptDouble: scalar.Float64(0),
97 OptBytes: []byte{},
98 OptString: scalar.String(""),
99 },
100 want: `{
101 "optBool": false,
102 "optInt32": 0,
103 "optInt64": "0",
104 "optUint32": 0,
105 "optUint64": "0",
106 "optSint32": 0,
107 "optSint64": "0",
108 "optFixed32": 0,
109 "optFixed64": "0",
110 "optSfixed32": 0,
111 "optSfixed64": "0",
112 "optFloat": 0,
113 "optDouble": 0,
114 "optBytes": "",
115 "optString": ""
116}`,
117 }, {
118 desc: "proto2 optional scalars set to some values",
119 input: &pb2.Scalars{
120 OptBool: scalar.Bool(true),
121 OptInt32: scalar.Int32(0xff),
122 OptInt64: scalar.Int64(0xdeadbeef),
123 OptUint32: scalar.Uint32(47),
124 OptUint64: scalar.Uint64(0xdeadbeef),
125 OptSint32: scalar.Int32(-1001),
126 OptSint64: scalar.Int64(-0xffff),
127 OptFixed64: scalar.Uint64(64),
128 OptSfixed32: scalar.Int32(-32),
129 OptFloat: scalar.Float32(1.02),
130 OptDouble: scalar.Float64(1.234),
Herbie Ong87608a72019-03-06 14:32:24 -0800131 OptBytes: []byte("谷歌"),
Herbie Ong7b828bc2019-02-08 19:56:24 -0800132 OptString: scalar.String("谷歌"),
133 },
134 want: `{
135 "optBool": true,
136 "optInt32": 255,
137 "optInt64": "3735928559",
138 "optUint32": 47,
139 "optUint64": "3735928559",
140 "optSint32": -1001,
141 "optSint64": "-65535",
142 "optFixed64": "64",
143 "optSfixed32": -32,
144 "optFloat": 1.02,
145 "optDouble": 1.234,
146 "optBytes": "6LC35q2M",
147 "optString": "谷歌"
148}`,
149 }, {
Herbie Ong0b0f4032019-03-18 19:06:15 -0700150 desc: "string",
151 input: &pb3.Scalars{
152 SString: "谷歌",
153 },
154 want: `{
155 "sString": "谷歌"
156}`,
157 }, {
158 desc: "string with invalid UTF8",
159 input: &pb3.Scalars{
160 SString: "abc\xff",
161 },
162 want: "{\n \"sString\": \"abc\xff\"\n}",
163 wantErr: true,
164 }, {
Herbie Ong7b828bc2019-02-08 19:56:24 -0800165 desc: "float nan",
166 input: &pb3.Scalars{
167 SFloat: float32(math.NaN()),
168 },
169 want: `{
170 "sFloat": "NaN"
171}`,
172 }, {
173 desc: "float positive infinity",
174 input: &pb3.Scalars{
175 SFloat: float32(math.Inf(1)),
176 },
177 want: `{
178 "sFloat": "Infinity"
179}`,
180 }, {
181 desc: "float negative infinity",
182 input: &pb3.Scalars{
183 SFloat: float32(math.Inf(-1)),
184 },
185 want: `{
186 "sFloat": "-Infinity"
187}`,
188 }, {
189 desc: "double nan",
190 input: &pb3.Scalars{
191 SDouble: math.NaN(),
192 },
193 want: `{
194 "sDouble": "NaN"
195}`,
196 }, {
197 desc: "double positive infinity",
198 input: &pb3.Scalars{
199 SDouble: math.Inf(1),
200 },
201 want: `{
202 "sDouble": "Infinity"
203}`,
204 }, {
205 desc: "double negative infinity",
206 input: &pb3.Scalars{
207 SDouble: math.Inf(-1),
208 },
209 want: `{
210 "sDouble": "-Infinity"
211}`,
212 }, {
213 desc: "proto2 enum not set",
214 input: &pb2.Enums{},
215 want: "{}",
216 }, {
217 desc: "proto2 enum set to zero value",
218 input: &pb2.Enums{
219 OptEnum: pb2Enum(0),
220 OptNestedEnum: pb2Enums_NestedEnum(0),
221 },
222 want: `{
223 "optEnum": 0,
224 "optNestedEnum": 0
225}`,
226 }, {
227 desc: "proto2 enum",
228 input: &pb2.Enums{
229 OptEnum: pb2.Enum_ONE.Enum(),
230 OptNestedEnum: pb2.Enums_UNO.Enum(),
231 },
232 want: `{
233 "optEnum": "ONE",
234 "optNestedEnum": "UNO"
235}`,
236 }, {
237 desc: "proto2 enum set to numeric values",
238 input: &pb2.Enums{
239 OptEnum: pb2Enum(2),
240 OptNestedEnum: pb2Enums_NestedEnum(2),
241 },
242 want: `{
243 "optEnum": "TWO",
244 "optNestedEnum": "DOS"
245}`,
246 }, {
247 desc: "proto2 enum set to unnamed numeric values",
248 input: &pb2.Enums{
249 OptEnum: pb2Enum(101),
250 OptNestedEnum: pb2Enums_NestedEnum(-101),
251 },
252 want: `{
253 "optEnum": 101,
254 "optNestedEnum": -101
255}`,
256 }, {
257 desc: "proto3 enum not set",
258 input: &pb3.Enums{},
259 want: "{}",
260 }, {
261 desc: "proto3 enum set to zero value",
262 input: &pb3.Enums{
263 SEnum: pb3.Enum_ZERO,
264 SNestedEnum: pb3.Enums_CERO,
265 },
266 want: "{}",
267 }, {
268 desc: "proto3 enum",
269 input: &pb3.Enums{
270 SEnum: pb3.Enum_ONE,
271 SNestedEnum: pb3.Enums_UNO,
272 },
273 want: `{
274 "sEnum": "ONE",
275 "sNestedEnum": "UNO"
276}`,
277 }, {
278 desc: "proto3 enum set to numeric values",
279 input: &pb3.Enums{
280 SEnum: 2,
281 SNestedEnum: 2,
282 },
283 want: `{
284 "sEnum": "TWO",
285 "sNestedEnum": "DOS"
286}`,
287 }, {
288 desc: "proto3 enum set to unnamed numeric values",
289 input: &pb3.Enums{
290 SEnum: -47,
291 SNestedEnum: 47,
292 },
293 want: `{
294 "sEnum": -47,
295 "sNestedEnum": 47
296}`,
297 }, {
298 desc: "proto2 nested message not set",
299 input: &pb2.Nests{},
300 want: "{}",
301 }, {
302 desc: "proto2 nested message set to empty",
303 input: &pb2.Nests{
304 OptNested: &pb2.Nested{},
305 Optgroup: &pb2.Nests_OptGroup{},
306 },
307 want: `{
308 "optNested": {},
309 "optgroup": {}
310}`,
311 }, {
312 desc: "proto2 nested messages",
313 input: &pb2.Nests{
314 OptNested: &pb2.Nested{
315 OptString: scalar.String("nested message"),
316 OptNested: &pb2.Nested{
317 OptString: scalar.String("another nested message"),
318 },
319 },
320 },
321 want: `{
322 "optNested": {
323 "optString": "nested message",
324 "optNested": {
325 "optString": "another nested message"
326 }
327 }
328}`,
329 }, {
330 desc: "proto2 groups",
331 input: &pb2.Nests{
332 Optgroup: &pb2.Nests_OptGroup{
333 OptString: scalar.String("inside a group"),
334 OptNested: &pb2.Nested{
335 OptString: scalar.String("nested message inside a group"),
336 },
337 Optnestedgroup: &pb2.Nests_OptGroup_OptNestedGroup{
338 OptFixed32: scalar.Uint32(47),
339 },
340 },
341 },
342 want: `{
343 "optgroup": {
344 "optString": "inside a group",
345 "optNested": {
346 "optString": "nested message inside a group"
347 },
348 "optnestedgroup": {
349 "optFixed32": 47
350 }
351 }
352}`,
353 }, {
354 desc: "proto3 nested message not set",
355 input: &pb3.Nests{},
356 want: "{}",
357 }, {
358 desc: "proto3 nested message set to empty",
359 input: &pb3.Nests{
360 SNested: &pb3.Nested{},
361 },
362 want: `{
363 "sNested": {}
364}`,
365 }, {
366 desc: "proto3 nested message",
367 input: &pb3.Nests{
368 SNested: &pb3.Nested{
369 SString: "nested message",
370 SNested: &pb3.Nested{
371 SString: "another nested message",
372 },
373 },
374 },
375 want: `{
376 "sNested": {
377 "sString": "nested message",
378 "sNested": {
379 "sString": "another nested message"
380 }
381 }
382}`,
383 }, {
384 desc: "oneof not set",
385 input: &pb3.Oneofs{},
386 want: "{}",
387 }, {
388 desc: "oneof set to empty string",
389 input: &pb3.Oneofs{
390 Union: &pb3.Oneofs_OneofString{},
391 },
392 want: `{
393 "oneofString": ""
394}`,
395 }, {
396 desc: "oneof set to string",
397 input: &pb3.Oneofs{
398 Union: &pb3.Oneofs_OneofString{
399 OneofString: "hello",
400 },
401 },
402 want: `{
403 "oneofString": "hello"
404}`,
405 }, {
406 desc: "oneof set to enum",
407 input: &pb3.Oneofs{
408 Union: &pb3.Oneofs_OneofEnum{
409 OneofEnum: pb3.Enum_ZERO,
410 },
411 },
412 want: `{
413 "oneofEnum": "ZERO"
414}`,
415 }, {
416 desc: "oneof set to empty message",
417 input: &pb3.Oneofs{
418 Union: &pb3.Oneofs_OneofNested{
419 OneofNested: &pb3.Nested{},
420 },
421 },
422 want: `{
423 "oneofNested": {}
424}`,
425 }, {
426 desc: "oneof set to message",
427 input: &pb3.Oneofs{
428 Union: &pb3.Oneofs_OneofNested{
429 OneofNested: &pb3.Nested{
430 SString: "nested message",
431 },
432 },
433 },
434 want: `{
435 "oneofNested": {
436 "sString": "nested message"
437 }
438}`,
439 }, {
440 desc: "repeated fields not set",
441 input: &pb2.Repeats{},
442 want: "{}",
443 }, {
444 desc: "repeated fields set to empty slices",
445 input: &pb2.Repeats{
446 RptBool: []bool{},
447 RptInt32: []int32{},
448 RptInt64: []int64{},
449 RptUint32: []uint32{},
450 RptUint64: []uint64{},
451 RptFloat: []float32{},
452 RptDouble: []float64{},
453 RptBytes: [][]byte{},
454 },
455 want: "{}",
456 }, {
457 desc: "repeated fields set to some values",
458 input: &pb2.Repeats{
459 RptBool: []bool{true, false, true, true},
460 RptInt32: []int32{1, 6, 0, 0},
461 RptInt64: []int64{-64, 47},
462 RptUint32: []uint32{0xff, 0xffff},
463 RptUint64: []uint64{0xdeadbeef},
464 RptFloat: []float32{float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1)), 1.034},
465 RptDouble: []float64{math.NaN(), math.Inf(1), math.Inf(-1), 1.23e-308},
466 RptString: []string{"hello", "世界"},
467 RptBytes: [][]byte{
468 []byte("hello"),
469 []byte("\xe4\xb8\x96\xe7\x95\x8c"),
470 },
471 },
472 want: `{
473 "rptBool": [
474 true,
475 false,
476 true,
477 true
478 ],
479 "rptInt32": [
480 1,
481 6,
482 0,
483 0
484 ],
485 "rptInt64": [
486 "-64",
487 "47"
488 ],
489 "rptUint32": [
490 255,
491 65535
492 ],
493 "rptUint64": [
494 "3735928559"
495 ],
496 "rptFloat": [
497 "NaN",
498 "Infinity",
499 "-Infinity",
500 1.034
501 ],
502 "rptDouble": [
503 "NaN",
504 "Infinity",
505 "-Infinity",
506 1.23e-308
507 ],
508 "rptString": [
509 "hello",
510 "世界"
511 ],
512 "rptBytes": [
513 "aGVsbG8=",
514 "5LiW55WM"
515 ]
516}`,
517 }, {
518 desc: "repeated enums",
519 input: &pb2.Enums{
520 RptEnum: []pb2.Enum{pb2.Enum_ONE, 2, pb2.Enum_TEN, 42},
521 RptNestedEnum: []pb2.Enums_NestedEnum{2, 47, 10},
522 },
523 want: `{
524 "rptEnum": [
525 "ONE",
526 "TWO",
527 "TEN",
528 42
529 ],
530 "rptNestedEnum": [
531 "DOS",
532 47,
533 "DIEZ"
534 ]
535}`,
536 }, {
537 desc: "repeated messages set to empty",
538 input: &pb2.Nests{
539 RptNested: []*pb2.Nested{},
540 Rptgroup: []*pb2.Nests_RptGroup{},
541 },
542 want: "{}",
543 }, {
544 desc: "repeated messages",
545 input: &pb2.Nests{
546 RptNested: []*pb2.Nested{
547 {
548 OptString: scalar.String("repeat nested one"),
549 },
550 {
551 OptString: scalar.String("repeat nested two"),
552 OptNested: &pb2.Nested{
553 OptString: scalar.String("inside repeat nested two"),
554 },
555 },
556 {},
557 },
558 },
559 want: `{
560 "rptNested": [
561 {
562 "optString": "repeat nested one"
563 },
564 {
565 "optString": "repeat nested two",
566 "optNested": {
567 "optString": "inside repeat nested two"
568 }
569 },
570 {}
571 ]
572}`,
573 }, {
574 desc: "repeated messages contains nil value",
575 input: &pb2.Nests{
576 RptNested: []*pb2.Nested{nil, {}},
577 },
578 want: `{
579 "rptNested": [
580 {},
581 {}
582 ]
583}`,
584 }, {
585 desc: "repeated groups",
586 input: &pb2.Nests{
587 Rptgroup: []*pb2.Nests_RptGroup{
588 {
589 RptString: []string{"hello", "world"},
590 },
591 {},
592 nil,
593 },
594 },
595 want: `{
596 "rptgroup": [
597 {
598 "rptString": [
599 "hello",
600 "world"
601 ]
602 },
603 {},
604 {}
605 ]
606}`,
607 }, {
608 desc: "map fields not set",
609 input: &pb3.Maps{},
610 want: "{}",
611 }, {
612 desc: "map fields set to empty",
613 input: &pb3.Maps{
614 Int32ToStr: map[int32]string{},
615 BoolToUint32: map[bool]uint32{},
616 Uint64ToEnum: map[uint64]pb3.Enum{},
617 StrToNested: map[string]*pb3.Nested{},
618 StrToOneofs: map[string]*pb3.Oneofs{},
619 },
620 want: "{}",
621 }, {
622 desc: "map fields 1",
623 input: &pb3.Maps{
624 BoolToUint32: map[bool]uint32{
625 true: 42,
626 false: 101,
627 },
628 },
629 want: `{
630 "boolToUint32": {
631 "false": 101,
632 "true": 42
633 }
634}`,
635 }, {
636 desc: "map fields 2",
637 input: &pb3.Maps{
638 Int32ToStr: map[int32]string{
639 -101: "-101",
640 0xff: "0xff",
641 0: "zero",
642 },
643 },
644 want: `{
645 "int32ToStr": {
646 "-101": "-101",
647 "0": "zero",
648 "255": "0xff"
649 }
650}`,
651 }, {
652 desc: "map fields 3",
653 input: &pb3.Maps{
654 Uint64ToEnum: map[uint64]pb3.Enum{
655 1: pb3.Enum_ONE,
656 2: pb3.Enum_TWO,
657 10: pb3.Enum_TEN,
658 47: 47,
659 },
660 },
661 want: `{
662 "uint64ToEnum": {
663 "1": "ONE",
664 "2": "TWO",
665 "10": "TEN",
666 "47": 47
667 }
668}`,
669 }, {
670 desc: "map fields 4",
671 input: &pb3.Maps{
672 StrToNested: map[string]*pb3.Nested{
673 "nested": &pb3.Nested{
674 SString: "nested in a map",
675 },
676 },
677 },
678 want: `{
679 "strToNested": {
680 "nested": {
681 "sString": "nested in a map"
682 }
683 }
684}`,
685 }, {
686 desc: "map fields 5",
687 input: &pb3.Maps{
688 StrToOneofs: map[string]*pb3.Oneofs{
689 "string": &pb3.Oneofs{
690 Union: &pb3.Oneofs_OneofString{
691 OneofString: "hello",
692 },
693 },
694 "nested": &pb3.Oneofs{
695 Union: &pb3.Oneofs_OneofNested{
696 OneofNested: &pb3.Nested{
697 SString: "nested oneof in map field value",
698 },
699 },
700 },
701 },
702 },
703 want: `{
704 "strToOneofs": {
705 "nested": {
706 "oneofNested": {
707 "sString": "nested oneof in map field value"
708 }
709 },
710 "string": {
711 "oneofString": "hello"
712 }
713 }
714}`,
715 }, {
716 desc: "map field contains nil value",
717 input: &pb3.Maps{
718 StrToNested: map[string]*pb3.Nested{
719 "nil": nil,
720 },
721 },
722 want: `{
723 "strToNested": {
724 "nil": {}
725 }
726}`,
727 }, {
Herbie Ong329be5b2019-03-27 14:47:59 -0700728 desc: "required fields not set",
729 input: &pb2.Requireds{},
730 want: `{}`,
731 wantErr: true,
732 }, {
733 desc: "required fields partially set",
734 input: &pb2.Requireds{
735 ReqBool: scalar.Bool(false),
736 ReqSfixed64: scalar.Int64(0),
737 ReqDouble: scalar.Float64(1.23),
738 ReqString: scalar.String("hello"),
739 ReqEnum: pb2.Enum_ONE.Enum(),
740 },
741 want: `{
742 "reqBool": false,
743 "reqSfixed64": "0",
744 "reqDouble": 1.23,
745 "reqString": "hello",
746 "reqEnum": "ONE"
747}`,
748 wantErr: true,
749 }, {
750 desc: "required fields not set with AllowPartial",
Damien Neil5c5b5312019-05-14 12:44:37 -0700751 mo: protojson.MarshalOptions{AllowPartial: true},
Herbie Ong329be5b2019-03-27 14:47:59 -0700752 input: &pb2.Requireds{
753 ReqBool: scalar.Bool(false),
754 ReqSfixed64: scalar.Int64(0),
755 ReqDouble: scalar.Float64(1.23),
756 ReqString: scalar.String("hello"),
757 ReqEnum: pb2.Enum_ONE.Enum(),
758 },
759 want: `{
760 "reqBool": false,
761 "reqSfixed64": "0",
762 "reqDouble": 1.23,
763 "reqString": "hello",
764 "reqEnum": "ONE"
765}`,
766 }, {
767 desc: "required fields all set",
768 input: &pb2.Requireds{
769 ReqBool: scalar.Bool(false),
770 ReqSfixed64: scalar.Int64(0),
771 ReqDouble: scalar.Float64(1.23),
772 ReqString: scalar.String("hello"),
773 ReqEnum: pb2.Enum_ONE.Enum(),
774 ReqNested: &pb2.Nested{},
775 },
776 want: `{
777 "reqBool": false,
778 "reqSfixed64": "0",
779 "reqDouble": 1.23,
780 "reqString": "hello",
781 "reqEnum": "ONE",
782 "reqNested": {}
783}`,
784 }, {
785 desc: "indirect required field",
786 input: &pb2.IndirectRequired{
787 OptNested: &pb2.NestedWithRequired{},
788 },
789 want: `{
790 "optNested": {}
791}`,
792 wantErr: true,
793 }, {
794 desc: "indirect required field with AllowPartial",
Damien Neil5c5b5312019-05-14 12:44:37 -0700795 mo: protojson.MarshalOptions{AllowPartial: true},
Herbie Ong329be5b2019-03-27 14:47:59 -0700796 input: &pb2.IndirectRequired{
797 OptNested: &pb2.NestedWithRequired{},
798 },
799 want: `{
800 "optNested": {}
801}`,
802 }, {
803 desc: "indirect required field in empty repeated",
804 input: &pb2.IndirectRequired{
805 RptNested: []*pb2.NestedWithRequired{},
806 },
807 want: `{}`,
808 }, {
809 desc: "indirect required field in repeated",
810 input: &pb2.IndirectRequired{
811 RptNested: []*pb2.NestedWithRequired{
812 &pb2.NestedWithRequired{},
813 },
814 },
815 want: `{
816 "rptNested": [
817 {}
818 ]
819}`,
820 wantErr: true,
821 }, {
822 desc: "indirect required field in repeated with AllowPartial",
Damien Neil5c5b5312019-05-14 12:44:37 -0700823 mo: protojson.MarshalOptions{AllowPartial: true},
Herbie Ong329be5b2019-03-27 14:47:59 -0700824 input: &pb2.IndirectRequired{
825 RptNested: []*pb2.NestedWithRequired{
826 &pb2.NestedWithRequired{},
827 },
828 },
829 want: `{
830 "rptNested": [
831 {}
832 ]
833}`,
834 }, {
835 desc: "indirect required field in empty map",
836 input: &pb2.IndirectRequired{
837 StrToNested: map[string]*pb2.NestedWithRequired{},
838 },
839 want: "{}",
840 }, {
841 desc: "indirect required field in map",
842 input: &pb2.IndirectRequired{
843 StrToNested: map[string]*pb2.NestedWithRequired{
844 "fail": &pb2.NestedWithRequired{},
845 },
846 },
847 want: `{
848 "strToNested": {
849 "fail": {}
850 }
851}`,
852 wantErr: true,
853 }, {
854 desc: "indirect required field in map with AllowPartial",
Damien Neil5c5b5312019-05-14 12:44:37 -0700855 mo: protojson.MarshalOptions{AllowPartial: true},
Herbie Ong329be5b2019-03-27 14:47:59 -0700856 input: &pb2.IndirectRequired{
857 StrToNested: map[string]*pb2.NestedWithRequired{
858 "fail": &pb2.NestedWithRequired{},
859 },
860 },
861 want: `{
862 "strToNested": {
863 "fail": {}
864 }
865}`,
866 }, {
867 desc: "indirect required field in oneof",
868 input: &pb2.IndirectRequired{
869 Union: &pb2.IndirectRequired_OneofNested{
870 OneofNested: &pb2.NestedWithRequired{},
871 },
872 },
873 want: `{
874 "oneofNested": {}
875}`,
876 wantErr: true,
877 }, {
878 desc: "indirect required field in oneof with AllowPartial",
Damien Neil5c5b5312019-05-14 12:44:37 -0700879 mo: protojson.MarshalOptions{AllowPartial: true},
Herbie Ong329be5b2019-03-27 14:47:59 -0700880 input: &pb2.IndirectRequired{
881 Union: &pb2.IndirectRequired_OneofNested{
882 OneofNested: &pb2.NestedWithRequired{},
883 },
884 },
885 want: `{
886 "oneofNested": {}
887}`,
888 }, {
Herbie Ong7b828bc2019-02-08 19:56:24 -0800889 desc: "unknown fields are ignored",
890 input: &pb2.Scalars{
891 OptString: scalar.String("no unknowns"),
892 XXX_unrecognized: pack.Message{
893 pack.Tag{101, pack.BytesType}, pack.String("hello world"),
894 }.Marshal(),
895 },
896 want: `{
897 "optString": "no unknowns"
898}`,
899 }, {
900 desc: "json_name",
901 input: &pb3.JSONNames{
902 SString: "json_name",
903 },
904 want: `{
905 "foo_bar": "json_name"
906}`,
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700907 }, {
908 desc: "extensions of non-repeated fields",
909 input: func() proto.Message {
910 m := &pb2.Extensions{
911 OptString: scalar.String("non-extension field"),
912 OptBool: scalar.Bool(true),
913 OptInt32: scalar.Int32(42),
914 }
915 setExtension(m, pb2.E_OptExtBool, true)
916 setExtension(m, pb2.E_OptExtString, "extension field")
917 setExtension(m, pb2.E_OptExtEnum, pb2.Enum_TEN)
918 setExtension(m, pb2.E_OptExtNested, &pb2.Nested{
919 OptString: scalar.String("nested in an extension"),
920 OptNested: &pb2.Nested{
921 OptString: scalar.String("another nested in an extension"),
922 },
923 })
924 return m
925 }(),
926 want: `{
927 "optString": "non-extension field",
928 "optBool": true,
929 "optInt32": 42,
930 "[pb2.opt_ext_bool]": true,
931 "[pb2.opt_ext_enum]": "TEN",
932 "[pb2.opt_ext_nested]": {
933 "optString": "nested in an extension",
934 "optNested": {
935 "optString": "another nested in an extension"
936 }
937 },
938 "[pb2.opt_ext_string]": "extension field"
939}`,
940 }, {
Herbie Ongf83d5bb2019-03-14 00:01:27 -0700941 desc: "extensions of repeated fields",
942 input: func() proto.Message {
943 m := &pb2.Extensions{}
944 setExtension(m, pb2.E_RptExtEnum, &[]pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE})
945 setExtension(m, pb2.E_RptExtFixed32, &[]uint32{42, 47})
946 setExtension(m, pb2.E_RptExtNested, &[]*pb2.Nested{
947 &pb2.Nested{OptString: scalar.String("one")},
948 &pb2.Nested{OptString: scalar.String("two")},
949 &pb2.Nested{OptString: scalar.String("three")},
950 })
951 return m
952 }(),
953 want: `{
954 "[pb2.rpt_ext_enum]": [
955 "TEN",
956 101,
957 "ONE"
958 ],
959 "[pb2.rpt_ext_fixed32]": [
960 42,
961 47
962 ],
963 "[pb2.rpt_ext_nested]": [
964 {
965 "optString": "one"
966 },
967 {
968 "optString": "two"
969 },
970 {
971 "optString": "three"
972 }
973 ]
974}`,
975 }, {
976 desc: "extensions of non-repeated fields in another message",
977 input: func() proto.Message {
978 m := &pb2.Extensions{}
979 setExtension(m, pb2.E_ExtensionsContainer_OptExtBool, true)
980 setExtension(m, pb2.E_ExtensionsContainer_OptExtString, "extension field")
981 setExtension(m, pb2.E_ExtensionsContainer_OptExtEnum, pb2.Enum_TEN)
982 setExtension(m, pb2.E_ExtensionsContainer_OptExtNested, &pb2.Nested{
983 OptString: scalar.String("nested in an extension"),
984 OptNested: &pb2.Nested{
985 OptString: scalar.String("another nested in an extension"),
986 },
987 })
988 return m
989 }(),
990 want: `{
991 "[pb2.ExtensionsContainer.opt_ext_bool]": true,
992 "[pb2.ExtensionsContainer.opt_ext_enum]": "TEN",
993 "[pb2.ExtensionsContainer.opt_ext_nested]": {
994 "optString": "nested in an extension",
995 "optNested": {
996 "optString": "another nested in an extension"
997 }
998 },
999 "[pb2.ExtensionsContainer.opt_ext_string]": "extension field"
1000}`,
1001 }, {
1002 desc: "extensions of repeated fields in another message",
1003 input: func() proto.Message {
1004 m := &pb2.Extensions{
1005 OptString: scalar.String("non-extension field"),
1006 OptBool: scalar.Bool(true),
1007 OptInt32: scalar.Int32(42),
1008 }
1009 setExtension(m, pb2.E_ExtensionsContainer_RptExtEnum, &[]pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE})
1010 setExtension(m, pb2.E_ExtensionsContainer_RptExtString, &[]string{"hello", "world"})
1011 setExtension(m, pb2.E_ExtensionsContainer_RptExtNested, &[]*pb2.Nested{
1012 &pb2.Nested{OptString: scalar.String("one")},
1013 &pb2.Nested{OptString: scalar.String("two")},
1014 &pb2.Nested{OptString: scalar.String("three")},
1015 })
1016 return m
1017 }(),
1018 want: `{
1019 "optString": "non-extension field",
1020 "optBool": true,
1021 "optInt32": 42,
1022 "[pb2.ExtensionsContainer.rpt_ext_enum]": [
1023 "TEN",
1024 101,
1025 "ONE"
1026 ],
1027 "[pb2.ExtensionsContainer.rpt_ext_nested]": [
1028 {
1029 "optString": "one"
1030 },
1031 {
1032 "optString": "two"
1033 },
1034 {
1035 "optString": "three"
1036 }
1037 ],
1038 "[pb2.ExtensionsContainer.rpt_ext_string]": [
1039 "hello",
1040 "world"
1041 ]
1042}`,
1043 }, {
1044 desc: "MessageSet",
1045 input: func() proto.Message {
1046 m := &pb2.MessageSet{}
1047 setExtension(m, pb2.E_MessageSetExtension_MessageSetExtension, &pb2.MessageSetExtension{
1048 OptString: scalar.String("a messageset extension"),
1049 })
1050 setExtension(m, pb2.E_MessageSetExtension_NotMessageSetExtension, &pb2.MessageSetExtension{
1051 OptString: scalar.String("not a messageset extension"),
1052 })
1053 setExtension(m, pb2.E_MessageSetExtension_ExtNested, &pb2.Nested{
1054 OptString: scalar.String("just a regular extension"),
1055 })
1056 return m
1057 }(),
1058 want: `{
1059 "[pb2.MessageSetExtension]": {
1060 "optString": "a messageset extension"
1061 },
1062 "[pb2.MessageSetExtension.ext_nested]": {
1063 "optString": "just a regular extension"
1064 },
1065 "[pb2.MessageSetExtension.not_message_set_extension]": {
1066 "optString": "not a messageset extension"
1067 }
1068}`,
1069 }, {
1070 desc: "not real MessageSet 1",
1071 input: func() proto.Message {
1072 m := &pb2.FakeMessageSet{}
1073 setExtension(m, pb2.E_FakeMessageSetExtension_MessageSetExtension, &pb2.FakeMessageSetExtension{
1074 OptString: scalar.String("not a messageset extension"),
1075 })
1076 return m
1077 }(),
1078 want: `{
1079 "[pb2.FakeMessageSetExtension.message_set_extension]": {
1080 "optString": "not a messageset extension"
1081 }
1082}`,
1083 }, {
1084 desc: "not real MessageSet 2",
1085 input: func() proto.Message {
1086 m := &pb2.MessageSet{}
1087 setExtension(m, pb2.E_MessageSetExtension, &pb2.FakeMessageSetExtension{
1088 OptString: scalar.String("another not a messageset extension"),
1089 })
1090 return m
1091 }(),
1092 want: `{
1093 "[pb2.message_set_extension]": {
1094 "optString": "another not a messageset extension"
1095 }
1096}`,
Herbie Ong0b0f4032019-03-18 19:06:15 -07001097 }, {
1098 desc: "BoolValue empty",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001099 input: &wrapperspb.BoolValue{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001100 want: `false`,
1101 }, {
1102 desc: "BoolValue",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001103 input: &wrapperspb.BoolValue{Value: true},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001104 want: `true`,
1105 }, {
1106 desc: "Int32Value empty",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001107 input: &wrapperspb.Int32Value{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001108 want: `0`,
1109 }, {
1110 desc: "Int32Value",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001111 input: &wrapperspb.Int32Value{Value: 42},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001112 want: `42`,
1113 }, {
1114 desc: "Int64Value",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001115 input: &wrapperspb.Int64Value{Value: 42},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001116 want: `"42"`,
1117 }, {
1118 desc: "UInt32Value",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001119 input: &wrapperspb.UInt32Value{Value: 42},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001120 want: `42`,
1121 }, {
1122 desc: "UInt64Value",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001123 input: &wrapperspb.UInt64Value{Value: 42},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001124 want: `"42"`,
1125 }, {
1126 desc: "FloatValue",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001127 input: &wrapperspb.FloatValue{Value: 1.02},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001128 want: `1.02`,
1129 }, {
Herbie Onge63c4c42019-03-22 22:20:22 -07001130 desc: "FloatValue Infinity",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001131 input: &wrapperspb.FloatValue{Value: float32(math.Inf(-1))},
Herbie Onge63c4c42019-03-22 22:20:22 -07001132 want: `"-Infinity"`,
1133 }, {
Herbie Ong0b0f4032019-03-18 19:06:15 -07001134 desc: "DoubleValue",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001135 input: &wrapperspb.DoubleValue{Value: 1.02},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001136 want: `1.02`,
1137 }, {
Herbie Onge63c4c42019-03-22 22:20:22 -07001138 desc: "DoubleValue NaN",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001139 input: &wrapperspb.DoubleValue{Value: math.NaN()},
Herbie Onge63c4c42019-03-22 22:20:22 -07001140 want: `"NaN"`,
1141 }, {
Herbie Ong0b0f4032019-03-18 19:06:15 -07001142 desc: "StringValue empty",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001143 input: &wrapperspb.StringValue{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001144 want: `""`,
1145 }, {
1146 desc: "StringValue",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001147 input: &wrapperspb.StringValue{Value: "谷歌"},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001148 want: `"谷歌"`,
1149 }, {
1150 desc: "StringValue with invalid UTF8 error",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001151 input: &wrapperspb.StringValue{Value: "abc\xff"},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001152 want: "\"abc\xff\"",
1153 wantErr: true,
1154 }, {
1155 desc: "StringValue field with invalid UTF8 error",
1156 input: &pb2.KnownTypes{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001157 OptString: &wrapperspb.StringValue{Value: "abc\xff"},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001158 },
1159 want: "{\n \"optString\": \"abc\xff\"\n}",
1160 wantErr: true,
1161 }, {
1162 desc: "BytesValue",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001163 input: &wrapperspb.BytesValue{Value: []byte("hello")},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001164 want: `"aGVsbG8="`,
1165 }, {
1166 desc: "Empty",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001167 input: &emptypb.Empty{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001168 want: `{}`,
1169 }, {
Herbie Ong300b9fe2019-03-29 15:42:20 -07001170 desc: "NullValue field",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001171 input: &pb2.KnownTypes{OptNull: new(structpb.NullValue)},
Herbie Ong300b9fe2019-03-29 15:42:20 -07001172 want: `{
1173 "optNull": null
1174}`,
1175 }, {
Herbie Ong1c7462c2019-03-22 17:56:55 -07001176 desc: "Value empty",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001177 input: &structpb.Value{},
Herbie Ong1c7462c2019-03-22 17:56:55 -07001178 wantErr: true,
Herbie Ong0b0f4032019-03-18 19:06:15 -07001179 }, {
1180 desc: "Value empty field",
1181 input: &pb2.KnownTypes{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001182 OptValue: &structpb.Value{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001183 },
Herbie Ong1c7462c2019-03-22 17:56:55 -07001184 wantErr: true,
Herbie Ong0b0f4032019-03-18 19:06:15 -07001185 }, {
1186 desc: "Value contains NullValue",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001187 input: &structpb.Value{Kind: &structpb.Value_NullValue{}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001188 want: `null`,
1189 }, {
1190 desc: "Value contains BoolValue",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001191 input: &structpb.Value{Kind: &structpb.Value_BoolValue{}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001192 want: `false`,
1193 }, {
1194 desc: "Value contains NumberValue",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001195 input: &structpb.Value{Kind: &structpb.Value_NumberValue{1.02}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001196 want: `1.02`,
1197 }, {
1198 desc: "Value contains StringValue",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001199 input: &structpb.Value{Kind: &structpb.Value_StringValue{"hello"}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001200 want: `"hello"`,
1201 }, {
1202 desc: "Value contains StringValue with invalid UTF8",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001203 input: &structpb.Value{Kind: &structpb.Value_StringValue{"\xff"}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001204 want: "\"\xff\"",
1205 wantErr: true,
1206 }, {
1207 desc: "Value contains Struct",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001208 input: &structpb.Value{
1209 Kind: &structpb.Value_StructValue{
1210 &structpb.Struct{
1211 Fields: map[string]*structpb.Value{
1212 "null": {Kind: &structpb.Value_NullValue{}},
1213 "number": {Kind: &structpb.Value_NumberValue{}},
1214 "string": {Kind: &structpb.Value_StringValue{}},
1215 "struct": {Kind: &structpb.Value_StructValue{}},
1216 "list": {Kind: &structpb.Value_ListValue{}},
1217 "bool": {Kind: &structpb.Value_BoolValue{}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001218 },
1219 },
1220 },
1221 },
1222 want: `{
1223 "bool": false,
1224 "list": [],
1225 "null": null,
1226 "number": 0,
1227 "string": "",
1228 "struct": {}
1229}`,
1230 }, {
1231 desc: "Value contains ListValue",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001232 input: &structpb.Value{
1233 Kind: &structpb.Value_ListValue{
1234 &structpb.ListValue{
1235 Values: []*structpb.Value{
1236 {Kind: &structpb.Value_BoolValue{}},
1237 {Kind: &structpb.Value_NullValue{}},
1238 {Kind: &structpb.Value_NumberValue{}},
1239 {Kind: &structpb.Value_StringValue{}},
1240 {Kind: &structpb.Value_StructValue{}},
1241 {Kind: &structpb.Value_ListValue{}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001242 },
1243 },
1244 },
1245 },
1246 want: `[
1247 false,
1248 null,
1249 0,
1250 "",
1251 {},
1252 []
1253]`,
1254 }, {
1255 desc: "Struct with nil map",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001256 input: &structpb.Struct{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001257 want: `{}`,
1258 }, {
1259 desc: "Struct with empty map",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001260 input: &structpb.Struct{
1261 Fields: map[string]*structpb.Value{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001262 },
1263 want: `{}`,
1264 }, {
1265 desc: "Struct",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001266 input: &structpb.Struct{
1267 Fields: map[string]*structpb.Value{
1268 "bool": {Kind: &structpb.Value_BoolValue{true}},
1269 "null": {Kind: &structpb.Value_NullValue{}},
1270 "number": {Kind: &structpb.Value_NumberValue{3.1415}},
1271 "string": {Kind: &structpb.Value_StringValue{"hello"}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001272 "struct": {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001273 Kind: &structpb.Value_StructValue{
1274 &structpb.Struct{
1275 Fields: map[string]*structpb.Value{
1276 "string": {Kind: &structpb.Value_StringValue{"world"}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001277 },
1278 },
1279 },
1280 },
1281 "list": {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001282 Kind: &structpb.Value_ListValue{
1283 &structpb.ListValue{
1284 Values: []*structpb.Value{
1285 {Kind: &structpb.Value_BoolValue{}},
1286 {Kind: &structpb.Value_NullValue{}},
1287 {Kind: &structpb.Value_NumberValue{}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001288 },
1289 },
1290 },
1291 },
1292 },
1293 },
1294 want: `{
1295 "bool": true,
1296 "list": [
1297 false,
1298 null,
1299 0
1300 ],
1301 "null": null,
1302 "number": 3.1415,
1303 "string": "hello",
1304 "struct": {
1305 "string": "world"
1306 }
1307}`,
1308 }, {
1309 desc: "Struct message with invalid UTF8 string",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001310 input: &structpb.Struct{
1311 Fields: map[string]*structpb.Value{
1312 "string": {Kind: &structpb.Value_StringValue{"\xff"}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001313 },
1314 },
1315 want: "{\n \"string\": \"\xff\"\n}",
1316 wantErr: true,
1317 }, {
1318 desc: "ListValue with nil values",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001319 input: &structpb.ListValue{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001320 want: `[]`,
1321 }, {
1322 desc: "ListValue with empty values",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001323 input: &structpb.ListValue{
1324 Values: []*structpb.Value{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001325 },
1326 want: `[]`,
1327 }, {
1328 desc: "ListValue",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001329 input: &structpb.ListValue{
1330 Values: []*structpb.Value{
1331 {Kind: &structpb.Value_BoolValue{true}},
1332 {Kind: &structpb.Value_NullValue{}},
1333 {Kind: &structpb.Value_NumberValue{3.1415}},
1334 {Kind: &structpb.Value_StringValue{"hello"}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001335 {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001336 Kind: &structpb.Value_ListValue{
1337 &structpb.ListValue{
1338 Values: []*structpb.Value{
1339 {Kind: &structpb.Value_BoolValue{}},
1340 {Kind: &structpb.Value_NullValue{}},
1341 {Kind: &structpb.Value_NumberValue{}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001342 },
1343 },
1344 },
1345 },
1346 {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001347 Kind: &structpb.Value_StructValue{
1348 &structpb.Struct{
1349 Fields: map[string]*structpb.Value{
1350 "string": {Kind: &structpb.Value_StringValue{"world"}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001351 },
1352 },
1353 },
1354 },
1355 },
1356 },
1357 want: `[
1358 true,
1359 null,
1360 3.1415,
1361 "hello",
1362 [
1363 false,
1364 null,
1365 0
1366 ],
1367 {
1368 "string": "world"
1369 }
1370]`,
1371 }, {
1372 desc: "ListValue with invalid UTF8 string",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001373 input: &structpb.ListValue{
1374 Values: []*structpb.Value{
1375 {Kind: &structpb.Value_StringValue{"\xff"}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001376 },
1377 },
1378 want: "[\n \"\xff\"\n]",
1379 wantErr: true,
1380 }, {
1381 desc: "Duration empty",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001382 input: &durationpb.Duration{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001383 want: `"0s"`,
1384 }, {
1385 desc: "Duration with secs",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001386 input: &durationpb.Duration{Seconds: 3},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001387 want: `"3s"`,
1388 }, {
1389 desc: "Duration with -secs",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001390 input: &durationpb.Duration{Seconds: -3},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001391 want: `"-3s"`,
1392 }, {
1393 desc: "Duration with nanos",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001394 input: &durationpb.Duration{Nanos: 1e6},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001395 want: `"0.001s"`,
1396 }, {
1397 desc: "Duration with -nanos",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001398 input: &durationpb.Duration{Nanos: -1e6},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001399 want: `"-0.001s"`,
1400 }, {
1401 desc: "Duration with large secs",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001402 input: &durationpb.Duration{Seconds: 1e10, Nanos: 1},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001403 want: `"10000000000.000000001s"`,
1404 }, {
1405 desc: "Duration with 6-digit nanos",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001406 input: &durationpb.Duration{Nanos: 1e4},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001407 want: `"0.000010s"`,
1408 }, {
1409 desc: "Duration with 3-digit nanos",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001410 input: &durationpb.Duration{Nanos: 1e6},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001411 want: `"0.001s"`,
1412 }, {
1413 desc: "Duration with -secs -nanos",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001414 input: &durationpb.Duration{Seconds: -123, Nanos: -450},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001415 want: `"-123.000000450s"`,
1416 }, {
Herbie Ongad9c1252019-04-24 20:51:28 -07001417 desc: "Duration max value",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001418 input: &durationpb.Duration{Seconds: 315576000000, Nanos: 999999999},
Herbie Ongad9c1252019-04-24 20:51:28 -07001419 want: `"315576000000.999999999s"`,
1420 }, {
1421 desc: "Duration min value",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001422 input: &durationpb.Duration{Seconds: -315576000000, Nanos: -999999999},
Herbie Ongad9c1252019-04-24 20:51:28 -07001423 want: `"-315576000000.999999999s"`,
1424 }, {
Herbie Ong0b0f4032019-03-18 19:06:15 -07001425 desc: "Duration with +secs -nanos",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001426 input: &durationpb.Duration{Seconds: 1, Nanos: -1},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001427 wantErr: true,
1428 }, {
1429 desc: "Duration with -secs +nanos",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001430 input: &durationpb.Duration{Seconds: -1, Nanos: 1},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001431 wantErr: true,
1432 }, {
1433 desc: "Duration with +secs out of range",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001434 input: &durationpb.Duration{Seconds: 315576000001},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001435 wantErr: true,
1436 }, {
1437 desc: "Duration with -secs out of range",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001438 input: &durationpb.Duration{Seconds: -315576000001},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001439 wantErr: true,
1440 }, {
1441 desc: "Duration with +nanos out of range",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001442 input: &durationpb.Duration{Seconds: 0, Nanos: 1e9},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001443 wantErr: true,
1444 }, {
1445 desc: "Duration with -nanos out of range",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001446 input: &durationpb.Duration{Seconds: 0, Nanos: -1e9},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001447 wantErr: true,
1448 }, {
1449 desc: "Timestamp zero",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001450 input: &timestamppb.Timestamp{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001451 want: `"1970-01-01T00:00:00Z"`,
1452 }, {
1453 desc: "Timestamp",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001454 input: &timestamppb.Timestamp{Seconds: 1553036601},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001455 want: `"2019-03-19T23:03:21Z"`,
1456 }, {
1457 desc: "Timestamp with nanos",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001458 input: &timestamppb.Timestamp{Seconds: 1553036601, Nanos: 1},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001459 want: `"2019-03-19T23:03:21.000000001Z"`,
1460 }, {
1461 desc: "Timestamp with 6-digit nanos",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001462 input: &timestamppb.Timestamp{Nanos: 1e3},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001463 want: `"1970-01-01T00:00:00.000001Z"`,
1464 }, {
1465 desc: "Timestamp with 3-digit nanos",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001466 input: &timestamppb.Timestamp{Nanos: 1e7},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001467 want: `"1970-01-01T00:00:00.010Z"`,
1468 }, {
Herbie Ongad9c1252019-04-24 20:51:28 -07001469 desc: "Timestamp max value",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001470 input: &timestamppb.Timestamp{Seconds: 253402300799, Nanos: 999999999},
Herbie Ongad9c1252019-04-24 20:51:28 -07001471 want: `"9999-12-31T23:59:59.999999999Z"`,
1472 }, {
1473 desc: "Timestamp min value",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001474 input: &timestamppb.Timestamp{Seconds: -62135596800},
Herbie Ongad9c1252019-04-24 20:51:28 -07001475 want: `"0001-01-01T00:00:00Z"`,
1476 }, {
Herbie Ong0b0f4032019-03-18 19:06:15 -07001477 desc: "Timestamp with +secs out of range",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001478 input: &timestamppb.Timestamp{Seconds: 253402300800},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001479 wantErr: true,
1480 }, {
1481 desc: "Timestamp with -secs out of range",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001482 input: &timestamppb.Timestamp{Seconds: -62135596801},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001483 wantErr: true,
1484 }, {
1485 desc: "Timestamp with -nanos",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001486 input: &timestamppb.Timestamp{Nanos: -1},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001487 wantErr: true,
1488 }, {
1489 desc: "Timestamp with +nanos out of range",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001490 input: &timestamppb.Timestamp{Nanos: 1e9},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001491 wantErr: true,
1492 }, {
1493 desc: "FieldMask empty",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001494 input: &fieldmaskpb.FieldMask{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001495 want: `""`,
1496 }, {
1497 desc: "FieldMask",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001498 input: &fieldmaskpb.FieldMask{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001499 Paths: []string{
1500 "foo",
1501 "foo_bar",
1502 "foo.bar_qux",
1503 "_foo",
1504 },
1505 },
1506 want: `"foo,fooBar,foo.barQux,Foo"`,
1507 }, {
1508 desc: "FieldMask error 1",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001509 input: &fieldmaskpb.FieldMask{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001510 Paths: []string{"foo_"},
1511 },
1512 wantErr: true,
1513 }, {
1514 desc: "FieldMask error 2",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001515 input: &fieldmaskpb.FieldMask{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001516 Paths: []string{"foo__bar"},
1517 },
1518 wantErr: true,
1519 }, {
1520 desc: "Any empty",
Joe Tsaia95b29f2019-05-16 12:47:20 -07001521 input: &anypb.Any{},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001522 want: `{}`,
1523 }, {
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001524 desc: "Any with non-custom message",
Damien Neil5c5b5312019-05-14 12:44:37 -07001525 mo: protojson.MarshalOptions{
Joe Tsai0fc49f82019-05-01 12:29:25 -07001526 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001527 },
1528 input: func() proto.Message {
1529 m := &pb2.Nested{
1530 OptString: scalar.String("embedded inside Any"),
1531 OptNested: &pb2.Nested{
1532 OptString: scalar.String("inception"),
1533 },
1534 }
1535 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1536 if err != nil {
1537 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1538 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001539 return &anypb.Any{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001540 TypeUrl: "foo/pb2.Nested",
1541 Value: b,
1542 }
1543 }(),
1544 want: `{
1545 "@type": "foo/pb2.Nested",
1546 "optString": "embedded inside Any",
1547 "optNested": {
1548 "optString": "inception"
1549 }
1550}`,
1551 }, {
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001552 desc: "Any with empty embedded message",
Damien Neil5c5b5312019-05-14 12:44:37 -07001553 mo: protojson.MarshalOptions{
Joe Tsai0fc49f82019-05-01 12:29:25 -07001554 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001555 },
Joe Tsaia95b29f2019-05-16 12:47:20 -07001556 input: &anypb.Any{TypeUrl: "foo/pb2.Nested"},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001557 want: `{
1558 "@type": "foo/pb2.Nested"
1559}`,
1560 }, {
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001561 desc: "Any without registered type",
Damien Neil5c5b5312019-05-14 12:44:37 -07001562 mo: protojson.MarshalOptions{Resolver: preg.NewTypes()},
Joe Tsaia95b29f2019-05-16 12:47:20 -07001563 input: &anypb.Any{TypeUrl: "foo/pb2.Nested"},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001564 wantErr: true,
1565 }, {
Damien Neil0c9f0a92019-06-19 10:41:09 -07001566 desc: "Any with missing required",
Damien Neil5c5b5312019-05-14 12:44:37 -07001567 mo: protojson.MarshalOptions{
Joe Tsai0fc49f82019-05-01 12:29:25 -07001568 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.PartialRequired{})),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001569 },
1570 input: func() proto.Message {
1571 m := &pb2.PartialRequired{
1572 OptString: scalar.String("embedded inside Any"),
1573 }
Damien Neil96c229a2019-04-03 12:17:24 -07001574 b, err := proto.MarshalOptions{
1575 AllowPartial: true,
1576 Deterministic: true,
1577 }.Marshal(m)
Herbie Ong0b0f4032019-03-18 19:06:15 -07001578 if err != nil {
1579 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1580 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001581 return &anypb.Any{
Joe Tsai0fc49f82019-05-01 12:29:25 -07001582 TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001583 Value: b,
1584 }
1585 }(),
1586 want: `{
1587 "@type": "pb2.PartialRequired",
1588 "optString": "embedded inside Any"
1589}`,
Herbie Ong0b0f4032019-03-18 19:06:15 -07001590 }, {
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001591 desc: "Any with partial required and AllowPartial",
Damien Neil5c5b5312019-05-14 12:44:37 -07001592 mo: protojson.MarshalOptions{
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001593 AllowPartial: true,
Joe Tsai0fc49f82019-05-01 12:29:25 -07001594 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.PartialRequired{})),
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001595 },
1596 input: func() proto.Message {
1597 m := &pb2.PartialRequired{
1598 OptString: scalar.String("embedded inside Any"),
1599 }
Damien Neil96c229a2019-04-03 12:17:24 -07001600 b, err := proto.MarshalOptions{
1601 AllowPartial: true,
1602 Deterministic: true,
1603 }.Marshal(m)
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001604 if err != nil {
1605 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1606 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001607 return &anypb.Any{
Joe Tsai0fc49f82019-05-01 12:29:25 -07001608 TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001609 Value: b,
1610 }
1611 }(),
1612 want: `{
1613 "@type": "pb2.PartialRequired",
1614 "optString": "embedded inside Any"
1615}`,
1616 }, {
Herbie Ong0b0f4032019-03-18 19:06:15 -07001617 desc: "Any with invalid UTF8",
Damien Neil5c5b5312019-05-14 12:44:37 -07001618 mo: protojson.MarshalOptions{
Joe Tsai0fc49f82019-05-01 12:29:25 -07001619 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001620 },
1621 input: func() proto.Message {
1622 m := &pb2.Nested{
1623 OptString: scalar.String("abc\xff"),
1624 }
1625 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1626 if err != nil {
1627 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1628 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001629 return &anypb.Any{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001630 TypeUrl: "foo/pb2.Nested",
1631 Value: b,
1632 }
1633 }(),
1634 want: `{
1635 "@type": "foo/pb2.Nested",
1636 "optString": "` + "abc\xff" + `"
1637}`,
1638 wantErr: true,
1639 }, {
1640 desc: "Any with invalid value",
Damien Neil5c5b5312019-05-14 12:44:37 -07001641 mo: protojson.MarshalOptions{
Joe Tsai0fc49f82019-05-01 12:29:25 -07001642 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001643 },
Joe Tsaia95b29f2019-05-16 12:47:20 -07001644 input: &anypb.Any{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001645 TypeUrl: "foo/pb2.Nested",
1646 Value: dhex("80"),
1647 },
1648 wantErr: true,
1649 }, {
1650 desc: "Any with BoolValue",
Damien Neil5c5b5312019-05-14 12:44:37 -07001651 mo: protojson.MarshalOptions{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001652 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&wrapperspb.BoolValue{})),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001653 },
1654 input: func() proto.Message {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001655 m := &wrapperspb.BoolValue{Value: true}
Herbie Ong0b0f4032019-03-18 19:06:15 -07001656 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1657 if err != nil {
1658 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1659 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001660 return &anypb.Any{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001661 TypeUrl: "type.googleapis.com/google.protobuf.BoolValue",
1662 Value: b,
1663 }
1664 }(),
1665 want: `{
1666 "@type": "type.googleapis.com/google.protobuf.BoolValue",
1667 "value": true
1668}`,
1669 }, {
Herbie Ong0b0f4032019-03-18 19:06:15 -07001670 desc: "Any with Empty",
Damien Neil5c5b5312019-05-14 12:44:37 -07001671 mo: protojson.MarshalOptions{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001672 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&emptypb.Empty{})),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001673 },
1674 input: func() proto.Message {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001675 m := &emptypb.Empty{}
Herbie Ong0b0f4032019-03-18 19:06:15 -07001676 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1677 if err != nil {
1678 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1679 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001680 return &anypb.Any{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001681 TypeUrl: "type.googleapis.com/google.protobuf.Empty",
1682 Value: b,
1683 }
1684 }(),
1685 want: `{
Herbie Ong82014a52019-03-27 15:21:43 -07001686 "@type": "type.googleapis.com/google.protobuf.Empty",
1687 "value": {}
Herbie Ong0b0f4032019-03-18 19:06:15 -07001688}`,
1689 }, {
1690 desc: "Any with StringValue containing invalid UTF8",
Damien Neil5c5b5312019-05-14 12:44:37 -07001691 mo: protojson.MarshalOptions{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001692 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&wrapperspb.StringValue{})),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001693 },
1694 input: func() proto.Message {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001695 m := &wrapperspb.StringValue{Value: "abcd"}
Herbie Ong0b0f4032019-03-18 19:06:15 -07001696 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1697 if err != nil {
1698 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1699 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001700 return &anypb.Any{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001701 TypeUrl: "google.protobuf.StringValue",
Damien Neilbc310b52019-04-11 11:46:55 -07001702 Value: bytes.Replace(b, []byte("abcd"), []byte("abc\xff"), -1),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001703 }
1704 }(),
1705 want: `{
1706 "@type": "google.protobuf.StringValue",
1707 "value": "` + "abc\xff" + `"
1708}`,
1709 wantErr: true,
1710 }, {
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001711 desc: "Any with Int64Value",
Damien Neil5c5b5312019-05-14 12:44:37 -07001712 mo: protojson.MarshalOptions{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001713 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&wrapperspb.Int64Value{})),
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001714 },
1715 input: func() proto.Message {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001716 m := &wrapperspb.Int64Value{Value: 42}
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001717 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1718 if err != nil {
1719 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1720 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001721 return &anypb.Any{
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001722 TypeUrl: "google.protobuf.Int64Value",
1723 Value: b,
1724 }
1725 }(),
1726 want: `{
1727 "@type": "google.protobuf.Int64Value",
1728 "value": "42"
1729}`,
1730 }, {
1731 desc: "Any with Duration",
Damien Neil5c5b5312019-05-14 12:44:37 -07001732 mo: protojson.MarshalOptions{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001733 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&durationpb.Duration{})),
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001734 },
1735 input: func() proto.Message {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001736 m := &durationpb.Duration{}
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001737 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1738 if err != nil {
1739 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1740 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001741 return &anypb.Any{
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001742 TypeUrl: "type.googleapis.com/google.protobuf.Duration",
1743 Value: b,
1744 }
1745 }(),
1746 want: `{
1747 "@type": "type.googleapis.com/google.protobuf.Duration",
1748 "value": "0s"
1749}`,
1750 }, {
1751 desc: "Any with empty Value",
Damien Neil5c5b5312019-05-14 12:44:37 -07001752 mo: protojson.MarshalOptions{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001753 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&structpb.Value{})),
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001754 },
1755 input: func() proto.Message {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001756 m := &structpb.Value{}
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001757 b, err := proto.Marshal(m)
1758 if err != nil {
1759 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1760 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001761 return &anypb.Any{
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001762 TypeUrl: "type.googleapis.com/google.protobuf.Value",
1763 Value: b,
1764 }
1765 }(),
1766 wantErr: true,
1767 }, {
Herbie Ong0b0f4032019-03-18 19:06:15 -07001768 desc: "Any with Value of StringValue",
Damien Neil5c5b5312019-05-14 12:44:37 -07001769 mo: protojson.MarshalOptions{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001770 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&structpb.Value{})),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001771 },
1772 input: func() proto.Message {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001773 m := &structpb.Value{Kind: &structpb.Value_StringValue{"abcd"}}
Herbie Ong0b0f4032019-03-18 19:06:15 -07001774 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1775 if err != nil {
1776 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1777 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001778 return &anypb.Any{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001779 TypeUrl: "type.googleapis.com/google.protobuf.Value",
Damien Neilbc310b52019-04-11 11:46:55 -07001780 Value: bytes.Replace(b, []byte("abcd"), []byte("abc\xff"), -1),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001781 }
1782 }(),
1783 want: `{
1784 "@type": "type.googleapis.com/google.protobuf.Value",
1785 "value": "` + "abc\xff" + `"
1786}`,
1787 wantErr: true,
1788 }, {
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001789 desc: "Any with Value of NullValue",
Damien Neil5c5b5312019-05-14 12:44:37 -07001790 mo: protojson.MarshalOptions{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001791 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&structpb.Value{})),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001792 },
1793 input: func() proto.Message {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001794 m := &structpb.Value{Kind: &structpb.Value_NullValue{}}
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001795 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
Herbie Ong0b0f4032019-03-18 19:06:15 -07001796 if err != nil {
1797 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1798 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001799 return &anypb.Any{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001800 TypeUrl: "type.googleapis.com/google.protobuf.Value",
1801 Value: b,
1802 }
1803 }(),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001804 want: `{
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001805 "@type": "type.googleapis.com/google.protobuf.Value",
1806 "value": null
Herbie Ong0b0f4032019-03-18 19:06:15 -07001807}`,
1808 }, {
1809 desc: "Any with Struct",
Damien Neil5c5b5312019-05-14 12:44:37 -07001810 mo: protojson.MarshalOptions{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001811 Resolver: preg.NewTypes(
Joe Tsaia95b29f2019-05-16 12:47:20 -07001812 pimpl.Export{}.MessageTypeOf(&structpb.Struct{}),
1813 pimpl.Export{}.MessageTypeOf(&structpb.Value{}),
1814 pimpl.Export{}.MessageTypeOf(&wrapperspb.BoolValue{}),
1815 pimpl.Export{}.EnumTypeOf(structpb.NullValue_NULL_VALUE),
1816 pimpl.Export{}.MessageTypeOf(&wrapperspb.StringValue{}),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001817 ),
1818 },
1819 input: func() proto.Message {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001820 m := &structpb.Struct{
1821 Fields: map[string]*structpb.Value{
1822 "bool": {Kind: &structpb.Value_BoolValue{true}},
1823 "null": {Kind: &structpb.Value_NullValue{}},
1824 "string": {Kind: &structpb.Value_StringValue{"hello"}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001825 "struct": {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001826 Kind: &structpb.Value_StructValue{
1827 &structpb.Struct{
1828 Fields: map[string]*structpb.Value{
1829 "string": {Kind: &structpb.Value_StringValue{"world"}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001830 },
1831 },
1832 },
1833 },
1834 },
1835 }
1836 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1837 if err != nil {
1838 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1839 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001840 return &anypb.Any{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001841 TypeUrl: "google.protobuf.Struct",
1842 Value: b,
1843 }
1844 }(),
1845 want: `{
1846 "@type": "google.protobuf.Struct",
1847 "value": {
1848 "bool": true,
1849 "null": null,
1850 "string": "hello",
1851 "struct": {
1852 "string": "world"
1853 }
1854 }
1855}`,
1856 }, {
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001857 desc: "Any with missing type_url",
Damien Neil5c5b5312019-05-14 12:44:37 -07001858 mo: protojson.MarshalOptions{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001859 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&wrapperspb.BoolValue{})),
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001860 },
1861 input: func() proto.Message {
Joe Tsaia95b29f2019-05-16 12:47:20 -07001862 m := &wrapperspb.BoolValue{Value: true}
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001863 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1864 if err != nil {
1865 t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1866 }
Joe Tsaia95b29f2019-05-16 12:47:20 -07001867 return &anypb.Any{
Herbie Ong8ac9dd22019-03-27 12:20:50 -07001868 Value: b,
1869 }
1870 }(),
1871 wantErr: true,
1872 }, {
Herbie Ong0b0f4032019-03-18 19:06:15 -07001873 desc: "well known types as field values",
Damien Neil5c5b5312019-05-14 12:44:37 -07001874 mo: protojson.MarshalOptions{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001875 Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&emptypb.Empty{})),
Herbie Ong0b0f4032019-03-18 19:06:15 -07001876 },
1877 input: &pb2.KnownTypes{
Joe Tsaia95b29f2019-05-16 12:47:20 -07001878 OptBool: &wrapperspb.BoolValue{Value: false},
1879 OptInt32: &wrapperspb.Int32Value{Value: 42},
1880 OptInt64: &wrapperspb.Int64Value{Value: 42},
1881 OptUint32: &wrapperspb.UInt32Value{Value: 42},
1882 OptUint64: &wrapperspb.UInt64Value{Value: 42},
1883 OptFloat: &wrapperspb.FloatValue{Value: 1.23},
1884 OptDouble: &wrapperspb.DoubleValue{Value: 3.1415},
1885 OptString: &wrapperspb.StringValue{Value: "hello"},
1886 OptBytes: &wrapperspb.BytesValue{Value: []byte("hello")},
1887 OptDuration: &durationpb.Duration{Seconds: 123},
1888 OptTimestamp: &timestamppb.Timestamp{Seconds: 1553036601},
1889 OptStruct: &structpb.Struct{
1890 Fields: map[string]*structpb.Value{
1891 "string": {Kind: &structpb.Value_StringValue{"hello"}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001892 },
1893 },
Joe Tsaia95b29f2019-05-16 12:47:20 -07001894 OptList: &structpb.ListValue{
1895 Values: []*structpb.Value{
1896 {Kind: &structpb.Value_NullValue{}},
1897 {Kind: &structpb.Value_StringValue{}},
1898 {Kind: &structpb.Value_StructValue{}},
1899 {Kind: &structpb.Value_ListValue{}},
Herbie Ong0b0f4032019-03-18 19:06:15 -07001900 },
1901 },
Joe Tsaia95b29f2019-05-16 12:47:20 -07001902 OptValue: &structpb.Value{
1903 Kind: &structpb.Value_StringValue{"world"},
Herbie Ong1c7462c2019-03-22 17:56:55 -07001904 },
Joe Tsaia95b29f2019-05-16 12:47:20 -07001905 OptEmpty: &emptypb.Empty{},
1906 OptAny: &anypb.Any{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001907 TypeUrl: "google.protobuf.Empty",
1908 },
Joe Tsaia95b29f2019-05-16 12:47:20 -07001909 OptFieldmask: &fieldmaskpb.FieldMask{
Herbie Ong0b0f4032019-03-18 19:06:15 -07001910 Paths: []string{"foo_bar", "bar_foo"},
1911 },
1912 },
1913 want: `{
1914 "optBool": false,
1915 "optInt32": 42,
1916 "optInt64": "42",
1917 "optUint32": 42,
1918 "optUint64": "42",
1919 "optFloat": 1.23,
1920 "optDouble": 3.1415,
1921 "optString": "hello",
1922 "optBytes": "aGVsbG8=",
1923 "optDuration": "123s",
1924 "optTimestamp": "2019-03-19T23:03:21Z",
1925 "optStruct": {
1926 "string": "hello"
1927 },
1928 "optList": [
1929 null,
1930 "",
1931 {},
1932 []
1933 ],
Herbie Ong1c7462c2019-03-22 17:56:55 -07001934 "optValue": "world",
Herbie Ong0b0f4032019-03-18 19:06:15 -07001935 "optEmpty": {},
1936 "optAny": {
Herbie Ong82014a52019-03-27 15:21:43 -07001937 "@type": "google.protobuf.Empty",
1938 "value": {}
Herbie Ong0b0f4032019-03-18 19:06:15 -07001939 },
1940 "optFieldmask": "fooBar,barFoo"
1941}`,
Herbie Ong7b828bc2019-02-08 19:56:24 -08001942 }}
1943
1944 for _, tt := range tests {
1945 tt := tt
1946 t.Run(tt.desc, func(t *testing.T) {
Herbie Ong0b0f4032019-03-18 19:06:15 -07001947 // Use 2-space indentation on all MarshalOptions.
1948 tt.mo.Indent = " "
Herbie Ong7b828bc2019-02-08 19:56:24 -08001949 b, err := tt.mo.Marshal(tt.input)
Herbie Ong0b0f4032019-03-18 19:06:15 -07001950 if err != nil && !tt.wantErr {
Herbie Ong7b828bc2019-02-08 19:56:24 -08001951 t.Errorf("Marshal() returned error: %v\n", err)
1952 }
Herbie Ong0b0f4032019-03-18 19:06:15 -07001953 if err == nil && tt.wantErr {
1954 t.Errorf("Marshal() got nil error, want error\n")
1955 }
Herbie Ong7b828bc2019-02-08 19:56:24 -08001956 got := string(b)
1957 if got != tt.want {
1958 t.Errorf("Marshal()\n<got>\n%v\n<want>\n%v\n", got, tt.want)
1959 if diff := cmp.Diff(tt.want, got, splitLines); diff != "" {
1960 t.Errorf("Marshal() diff -want +got\n%v\n", diff)
1961 }
1962 }
1963 })
1964 }
1965}