mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-27 03:35:32 +00:00
378c1329de
Added API: Message.Len Message.Range Message.Has Message.Clear Message.Get Message.Set Message.Mutable Message.NewMessage Message.WhichOneof Message.GetUnknown Message.SetUnknown Deprecated API (to be removed in subsequent CL): Message.KnownFields Message.UnknownFields The primary difference with the new API is that the top-level Message methods are keyed by FieldDescriptor rather than FieldNumber with the following semantics: * For known fields, the FieldDescriptor must exactly match the field descriptor known by the message. * For extension fields, the FieldDescriptor must implement ExtensionType, where ContainingMessage.FullName matches the message name, and the field number is within the message's extension range. When setting an extension field, it automatically stores the extension type information. * Extension fields are always considered nullable, implying that repeated extension fields are nullable. That is, you can distinguish between a unpopulated list and an empty list. * Message.Get always returns a valid Value even if unpopulated. The behavior is already well-defined for scalars, but for unpopulated composite types, it now returns an empty read-only version of it. Change-Id: Ia120630b4db221aeaaf743d0f64160e1a61a0f61 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/175458 Reviewed-by: Damien Neil <dneil@google.com>
1967 lines
46 KiB
Go
1967 lines
46 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package protojson_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"math"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
"google.golang.org/protobuf/internal/encoding/pack"
|
|
pimpl "google.golang.org/protobuf/internal/impl"
|
|
"google.golang.org/protobuf/internal/scalar"
|
|
"google.golang.org/protobuf/proto"
|
|
preg "google.golang.org/protobuf/reflect/protoregistry"
|
|
"google.golang.org/protobuf/runtime/protoiface"
|
|
|
|
"google.golang.org/protobuf/encoding/testprotos/pb2"
|
|
"google.golang.org/protobuf/encoding/testprotos/pb3"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
)
|
|
|
|
// splitLines is a cmpopts.Option for comparing strings with line breaks.
|
|
var splitLines = cmpopts.AcyclicTransformer("SplitLines", func(s string) []string {
|
|
return strings.Split(s, "\n")
|
|
})
|
|
|
|
func pb2Enum(i int32) *pb2.Enum {
|
|
p := new(pb2.Enum)
|
|
*p = pb2.Enum(i)
|
|
return p
|
|
}
|
|
|
|
func pb2Enums_NestedEnum(i int32) *pb2.Enums_NestedEnum {
|
|
p := new(pb2.Enums_NestedEnum)
|
|
*p = pb2.Enums_NestedEnum(i)
|
|
return p
|
|
}
|
|
|
|
// TODO: Replace this with proto.SetExtension.
|
|
func setExtension(m proto.Message, xd *protoiface.ExtensionDescV1, val interface{}) {
|
|
m.ProtoReflect().Set(xd.Type, xd.Type.ValueOf(val))
|
|
}
|
|
|
|
// dhex decodes a hex-string and returns the bytes and panics if s is invalid.
|
|
func dhex(s string) []byte {
|
|
b, err := hex.DecodeString(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func TestMarshal(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
mo protojson.MarshalOptions
|
|
input proto.Message
|
|
want string
|
|
wantErr bool // TODO: Verify error message substring.
|
|
}{{
|
|
desc: "proto2 optional scalars not set",
|
|
input: &pb2.Scalars{},
|
|
want: "{}",
|
|
}, {
|
|
desc: "proto3 scalars not set",
|
|
input: &pb3.Scalars{},
|
|
want: "{}",
|
|
}, {
|
|
desc: "proto2 optional scalars set to zero values",
|
|
input: &pb2.Scalars{
|
|
OptBool: scalar.Bool(false),
|
|
OptInt32: scalar.Int32(0),
|
|
OptInt64: scalar.Int64(0),
|
|
OptUint32: scalar.Uint32(0),
|
|
OptUint64: scalar.Uint64(0),
|
|
OptSint32: scalar.Int32(0),
|
|
OptSint64: scalar.Int64(0),
|
|
OptFixed32: scalar.Uint32(0),
|
|
OptFixed64: scalar.Uint64(0),
|
|
OptSfixed32: scalar.Int32(0),
|
|
OptSfixed64: scalar.Int64(0),
|
|
OptFloat: scalar.Float32(0),
|
|
OptDouble: scalar.Float64(0),
|
|
OptBytes: []byte{},
|
|
OptString: scalar.String(""),
|
|
},
|
|
want: `{
|
|
"optBool": false,
|
|
"optInt32": 0,
|
|
"optInt64": "0",
|
|
"optUint32": 0,
|
|
"optUint64": "0",
|
|
"optSint32": 0,
|
|
"optSint64": "0",
|
|
"optFixed32": 0,
|
|
"optFixed64": "0",
|
|
"optSfixed32": 0,
|
|
"optSfixed64": "0",
|
|
"optFloat": 0,
|
|
"optDouble": 0,
|
|
"optBytes": "",
|
|
"optString": ""
|
|
}`,
|
|
}, {
|
|
desc: "proto2 optional scalars set to some values",
|
|
input: &pb2.Scalars{
|
|
OptBool: scalar.Bool(true),
|
|
OptInt32: scalar.Int32(0xff),
|
|
OptInt64: scalar.Int64(0xdeadbeef),
|
|
OptUint32: scalar.Uint32(47),
|
|
OptUint64: scalar.Uint64(0xdeadbeef),
|
|
OptSint32: scalar.Int32(-1001),
|
|
OptSint64: scalar.Int64(-0xffff),
|
|
OptFixed64: scalar.Uint64(64),
|
|
OptSfixed32: scalar.Int32(-32),
|
|
OptFloat: scalar.Float32(1.02),
|
|
OptDouble: scalar.Float64(1.234),
|
|
OptBytes: []byte("谷歌"),
|
|
OptString: scalar.String("谷歌"),
|
|
},
|
|
want: `{
|
|
"optBool": true,
|
|
"optInt32": 255,
|
|
"optInt64": "3735928559",
|
|
"optUint32": 47,
|
|
"optUint64": "3735928559",
|
|
"optSint32": -1001,
|
|
"optSint64": "-65535",
|
|
"optFixed64": "64",
|
|
"optSfixed32": -32,
|
|
"optFloat": 1.02,
|
|
"optDouble": 1.234,
|
|
"optBytes": "6LC35q2M",
|
|
"optString": "谷歌"
|
|
}`,
|
|
}, {
|
|
desc: "string",
|
|
input: &pb3.Scalars{
|
|
SString: "谷歌",
|
|
},
|
|
want: `{
|
|
"sString": "谷歌"
|
|
}`,
|
|
}, {
|
|
desc: "string with invalid UTF8",
|
|
input: &pb3.Scalars{
|
|
SString: "abc\xff",
|
|
},
|
|
want: "{\n \"sString\": \"abc\xff\"\n}",
|
|
wantErr: true,
|
|
}, {
|
|
desc: "float nan",
|
|
input: &pb3.Scalars{
|
|
SFloat: float32(math.NaN()),
|
|
},
|
|
want: `{
|
|
"sFloat": "NaN"
|
|
}`,
|
|
}, {
|
|
desc: "float positive infinity",
|
|
input: &pb3.Scalars{
|
|
SFloat: float32(math.Inf(1)),
|
|
},
|
|
want: `{
|
|
"sFloat": "Infinity"
|
|
}`,
|
|
}, {
|
|
desc: "float negative infinity",
|
|
input: &pb3.Scalars{
|
|
SFloat: float32(math.Inf(-1)),
|
|
},
|
|
want: `{
|
|
"sFloat": "-Infinity"
|
|
}`,
|
|
}, {
|
|
desc: "double nan",
|
|
input: &pb3.Scalars{
|
|
SDouble: math.NaN(),
|
|
},
|
|
want: `{
|
|
"sDouble": "NaN"
|
|
}`,
|
|
}, {
|
|
desc: "double positive infinity",
|
|
input: &pb3.Scalars{
|
|
SDouble: math.Inf(1),
|
|
},
|
|
want: `{
|
|
"sDouble": "Infinity"
|
|
}`,
|
|
}, {
|
|
desc: "double negative infinity",
|
|
input: &pb3.Scalars{
|
|
SDouble: math.Inf(-1),
|
|
},
|
|
want: `{
|
|
"sDouble": "-Infinity"
|
|
}`,
|
|
}, {
|
|
desc: "proto2 enum not set",
|
|
input: &pb2.Enums{},
|
|
want: "{}",
|
|
}, {
|
|
desc: "proto2 enum set to zero value",
|
|
input: &pb2.Enums{
|
|
OptEnum: pb2Enum(0),
|
|
OptNestedEnum: pb2Enums_NestedEnum(0),
|
|
},
|
|
want: `{
|
|
"optEnum": 0,
|
|
"optNestedEnum": 0
|
|
}`,
|
|
}, {
|
|
desc: "proto2 enum",
|
|
input: &pb2.Enums{
|
|
OptEnum: pb2.Enum_ONE.Enum(),
|
|
OptNestedEnum: pb2.Enums_UNO.Enum(),
|
|
},
|
|
want: `{
|
|
"optEnum": "ONE",
|
|
"optNestedEnum": "UNO"
|
|
}`,
|
|
}, {
|
|
desc: "proto2 enum set to numeric values",
|
|
input: &pb2.Enums{
|
|
OptEnum: pb2Enum(2),
|
|
OptNestedEnum: pb2Enums_NestedEnum(2),
|
|
},
|
|
want: `{
|
|
"optEnum": "TWO",
|
|
"optNestedEnum": "DOS"
|
|
}`,
|
|
}, {
|
|
desc: "proto2 enum set to unnamed numeric values",
|
|
input: &pb2.Enums{
|
|
OptEnum: pb2Enum(101),
|
|
OptNestedEnum: pb2Enums_NestedEnum(-101),
|
|
},
|
|
want: `{
|
|
"optEnum": 101,
|
|
"optNestedEnum": -101
|
|
}`,
|
|
}, {
|
|
desc: "proto3 enum not set",
|
|
input: &pb3.Enums{},
|
|
want: "{}",
|
|
}, {
|
|
desc: "proto3 enum set to zero value",
|
|
input: &pb3.Enums{
|
|
SEnum: pb3.Enum_ZERO,
|
|
SNestedEnum: pb3.Enums_CERO,
|
|
},
|
|
want: "{}",
|
|
}, {
|
|
desc: "proto3 enum",
|
|
input: &pb3.Enums{
|
|
SEnum: pb3.Enum_ONE,
|
|
SNestedEnum: pb3.Enums_UNO,
|
|
},
|
|
want: `{
|
|
"sEnum": "ONE",
|
|
"sNestedEnum": "UNO"
|
|
}`,
|
|
}, {
|
|
desc: "proto3 enum set to numeric values",
|
|
input: &pb3.Enums{
|
|
SEnum: 2,
|
|
SNestedEnum: 2,
|
|
},
|
|
want: `{
|
|
"sEnum": "TWO",
|
|
"sNestedEnum": "DOS"
|
|
}`,
|
|
}, {
|
|
desc: "proto3 enum set to unnamed numeric values",
|
|
input: &pb3.Enums{
|
|
SEnum: -47,
|
|
SNestedEnum: 47,
|
|
},
|
|
want: `{
|
|
"sEnum": -47,
|
|
"sNestedEnum": 47
|
|
}`,
|
|
}, {
|
|
desc: "proto2 nested message not set",
|
|
input: &pb2.Nests{},
|
|
want: "{}",
|
|
}, {
|
|
desc: "proto2 nested message set to empty",
|
|
input: &pb2.Nests{
|
|
OptNested: &pb2.Nested{},
|
|
Optgroup: &pb2.Nests_OptGroup{},
|
|
},
|
|
want: `{
|
|
"optNested": {},
|
|
"optgroup": {}
|
|
}`,
|
|
}, {
|
|
desc: "proto2 nested messages",
|
|
input: &pb2.Nests{
|
|
OptNested: &pb2.Nested{
|
|
OptString: scalar.String("nested message"),
|
|
OptNested: &pb2.Nested{
|
|
OptString: scalar.String("another nested message"),
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"optNested": {
|
|
"optString": "nested message",
|
|
"optNested": {
|
|
"optString": "another nested message"
|
|
}
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "proto2 groups",
|
|
input: &pb2.Nests{
|
|
Optgroup: &pb2.Nests_OptGroup{
|
|
OptString: scalar.String("inside a group"),
|
|
OptNested: &pb2.Nested{
|
|
OptString: scalar.String("nested message inside a group"),
|
|
},
|
|
Optnestedgroup: &pb2.Nests_OptGroup_OptNestedGroup{
|
|
OptFixed32: scalar.Uint32(47),
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"optgroup": {
|
|
"optString": "inside a group",
|
|
"optNested": {
|
|
"optString": "nested message inside a group"
|
|
},
|
|
"optnestedgroup": {
|
|
"optFixed32": 47
|
|
}
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "proto3 nested message not set",
|
|
input: &pb3.Nests{},
|
|
want: "{}",
|
|
}, {
|
|
desc: "proto3 nested message set to empty",
|
|
input: &pb3.Nests{
|
|
SNested: &pb3.Nested{},
|
|
},
|
|
want: `{
|
|
"sNested": {}
|
|
}`,
|
|
}, {
|
|
desc: "proto3 nested message",
|
|
input: &pb3.Nests{
|
|
SNested: &pb3.Nested{
|
|
SString: "nested message",
|
|
SNested: &pb3.Nested{
|
|
SString: "another nested message",
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"sNested": {
|
|
"sString": "nested message",
|
|
"sNested": {
|
|
"sString": "another nested message"
|
|
}
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "oneof not set",
|
|
input: &pb3.Oneofs{},
|
|
want: "{}",
|
|
}, {
|
|
desc: "oneof set to empty string",
|
|
input: &pb3.Oneofs{
|
|
Union: &pb3.Oneofs_OneofString{},
|
|
},
|
|
want: `{
|
|
"oneofString": ""
|
|
}`,
|
|
}, {
|
|
desc: "oneof set to string",
|
|
input: &pb3.Oneofs{
|
|
Union: &pb3.Oneofs_OneofString{
|
|
OneofString: "hello",
|
|
},
|
|
},
|
|
want: `{
|
|
"oneofString": "hello"
|
|
}`,
|
|
}, {
|
|
desc: "oneof set to enum",
|
|
input: &pb3.Oneofs{
|
|
Union: &pb3.Oneofs_OneofEnum{
|
|
OneofEnum: pb3.Enum_ZERO,
|
|
},
|
|
},
|
|
want: `{
|
|
"oneofEnum": "ZERO"
|
|
}`,
|
|
}, {
|
|
desc: "oneof set to empty message",
|
|
input: &pb3.Oneofs{
|
|
Union: &pb3.Oneofs_OneofNested{
|
|
OneofNested: &pb3.Nested{},
|
|
},
|
|
},
|
|
want: `{
|
|
"oneofNested": {}
|
|
}`,
|
|
}, {
|
|
desc: "oneof set to message",
|
|
input: &pb3.Oneofs{
|
|
Union: &pb3.Oneofs_OneofNested{
|
|
OneofNested: &pb3.Nested{
|
|
SString: "nested message",
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"oneofNested": {
|
|
"sString": "nested message"
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "repeated fields not set",
|
|
input: &pb2.Repeats{},
|
|
want: "{}",
|
|
}, {
|
|
desc: "repeated fields set to empty slices",
|
|
input: &pb2.Repeats{
|
|
RptBool: []bool{},
|
|
RptInt32: []int32{},
|
|
RptInt64: []int64{},
|
|
RptUint32: []uint32{},
|
|
RptUint64: []uint64{},
|
|
RptFloat: []float32{},
|
|
RptDouble: []float64{},
|
|
RptBytes: [][]byte{},
|
|
},
|
|
want: "{}",
|
|
}, {
|
|
desc: "repeated fields set to some values",
|
|
input: &pb2.Repeats{
|
|
RptBool: []bool{true, false, true, true},
|
|
RptInt32: []int32{1, 6, 0, 0},
|
|
RptInt64: []int64{-64, 47},
|
|
RptUint32: []uint32{0xff, 0xffff},
|
|
RptUint64: []uint64{0xdeadbeef},
|
|
RptFloat: []float32{float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1)), 1.034},
|
|
RptDouble: []float64{math.NaN(), math.Inf(1), math.Inf(-1), 1.23e-308},
|
|
RptString: []string{"hello", "世界"},
|
|
RptBytes: [][]byte{
|
|
[]byte("hello"),
|
|
[]byte("\xe4\xb8\x96\xe7\x95\x8c"),
|
|
},
|
|
},
|
|
want: `{
|
|
"rptBool": [
|
|
true,
|
|
false,
|
|
true,
|
|
true
|
|
],
|
|
"rptInt32": [
|
|
1,
|
|
6,
|
|
0,
|
|
0
|
|
],
|
|
"rptInt64": [
|
|
"-64",
|
|
"47"
|
|
],
|
|
"rptUint32": [
|
|
255,
|
|
65535
|
|
],
|
|
"rptUint64": [
|
|
"3735928559"
|
|
],
|
|
"rptFloat": [
|
|
"NaN",
|
|
"Infinity",
|
|
"-Infinity",
|
|
1.034
|
|
],
|
|
"rptDouble": [
|
|
"NaN",
|
|
"Infinity",
|
|
"-Infinity",
|
|
1.23e-308
|
|
],
|
|
"rptString": [
|
|
"hello",
|
|
"世界"
|
|
],
|
|
"rptBytes": [
|
|
"aGVsbG8=",
|
|
"5LiW55WM"
|
|
]
|
|
}`,
|
|
}, {
|
|
desc: "repeated enums",
|
|
input: &pb2.Enums{
|
|
RptEnum: []pb2.Enum{pb2.Enum_ONE, 2, pb2.Enum_TEN, 42},
|
|
RptNestedEnum: []pb2.Enums_NestedEnum{2, 47, 10},
|
|
},
|
|
want: `{
|
|
"rptEnum": [
|
|
"ONE",
|
|
"TWO",
|
|
"TEN",
|
|
42
|
|
],
|
|
"rptNestedEnum": [
|
|
"DOS",
|
|
47,
|
|
"DIEZ"
|
|
]
|
|
}`,
|
|
}, {
|
|
desc: "repeated messages set to empty",
|
|
input: &pb2.Nests{
|
|
RptNested: []*pb2.Nested{},
|
|
Rptgroup: []*pb2.Nests_RptGroup{},
|
|
},
|
|
want: "{}",
|
|
}, {
|
|
desc: "repeated messages",
|
|
input: &pb2.Nests{
|
|
RptNested: []*pb2.Nested{
|
|
{
|
|
OptString: scalar.String("repeat nested one"),
|
|
},
|
|
{
|
|
OptString: scalar.String("repeat nested two"),
|
|
OptNested: &pb2.Nested{
|
|
OptString: scalar.String("inside repeat nested two"),
|
|
},
|
|
},
|
|
{},
|
|
},
|
|
},
|
|
want: `{
|
|
"rptNested": [
|
|
{
|
|
"optString": "repeat nested one"
|
|
},
|
|
{
|
|
"optString": "repeat nested two",
|
|
"optNested": {
|
|
"optString": "inside repeat nested two"
|
|
}
|
|
},
|
|
{}
|
|
]
|
|
}`,
|
|
}, {
|
|
desc: "repeated messages contains nil value",
|
|
input: &pb2.Nests{
|
|
RptNested: []*pb2.Nested{nil, {}},
|
|
},
|
|
want: `{
|
|
"rptNested": [
|
|
{},
|
|
{}
|
|
]
|
|
}`,
|
|
}, {
|
|
desc: "repeated groups",
|
|
input: &pb2.Nests{
|
|
Rptgroup: []*pb2.Nests_RptGroup{
|
|
{
|
|
RptString: []string{"hello", "world"},
|
|
},
|
|
{},
|
|
nil,
|
|
},
|
|
},
|
|
want: `{
|
|
"rptgroup": [
|
|
{
|
|
"rptString": [
|
|
"hello",
|
|
"world"
|
|
]
|
|
},
|
|
{},
|
|
{}
|
|
]
|
|
}`,
|
|
}, {
|
|
desc: "map fields not set",
|
|
input: &pb3.Maps{},
|
|
want: "{}",
|
|
}, {
|
|
desc: "map fields set to empty",
|
|
input: &pb3.Maps{
|
|
Int32ToStr: map[int32]string{},
|
|
BoolToUint32: map[bool]uint32{},
|
|
Uint64ToEnum: map[uint64]pb3.Enum{},
|
|
StrToNested: map[string]*pb3.Nested{},
|
|
StrToOneofs: map[string]*pb3.Oneofs{},
|
|
},
|
|
want: "{}",
|
|
}, {
|
|
desc: "map fields 1",
|
|
input: &pb3.Maps{
|
|
BoolToUint32: map[bool]uint32{
|
|
true: 42,
|
|
false: 101,
|
|
},
|
|
},
|
|
want: `{
|
|
"boolToUint32": {
|
|
"false": 101,
|
|
"true": 42
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "map fields 2",
|
|
input: &pb3.Maps{
|
|
Int32ToStr: map[int32]string{
|
|
-101: "-101",
|
|
0xff: "0xff",
|
|
0: "zero",
|
|
},
|
|
},
|
|
want: `{
|
|
"int32ToStr": {
|
|
"-101": "-101",
|
|
"0": "zero",
|
|
"255": "0xff"
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "map fields 3",
|
|
input: &pb3.Maps{
|
|
Uint64ToEnum: map[uint64]pb3.Enum{
|
|
1: pb3.Enum_ONE,
|
|
2: pb3.Enum_TWO,
|
|
10: pb3.Enum_TEN,
|
|
47: 47,
|
|
},
|
|
},
|
|
want: `{
|
|
"uint64ToEnum": {
|
|
"1": "ONE",
|
|
"2": "TWO",
|
|
"10": "TEN",
|
|
"47": 47
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "map fields 4",
|
|
input: &pb3.Maps{
|
|
StrToNested: map[string]*pb3.Nested{
|
|
"nested": &pb3.Nested{
|
|
SString: "nested in a map",
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"strToNested": {
|
|
"nested": {
|
|
"sString": "nested in a map"
|
|
}
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "map fields 5",
|
|
input: &pb3.Maps{
|
|
StrToOneofs: map[string]*pb3.Oneofs{
|
|
"string": &pb3.Oneofs{
|
|
Union: &pb3.Oneofs_OneofString{
|
|
OneofString: "hello",
|
|
},
|
|
},
|
|
"nested": &pb3.Oneofs{
|
|
Union: &pb3.Oneofs_OneofNested{
|
|
OneofNested: &pb3.Nested{
|
|
SString: "nested oneof in map field value",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"strToOneofs": {
|
|
"nested": {
|
|
"oneofNested": {
|
|
"sString": "nested oneof in map field value"
|
|
}
|
|
},
|
|
"string": {
|
|
"oneofString": "hello"
|
|
}
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "map field contains nil value",
|
|
input: &pb3.Maps{
|
|
StrToNested: map[string]*pb3.Nested{
|
|
"nil": nil,
|
|
},
|
|
},
|
|
want: `{
|
|
"strToNested": {
|
|
"nil": {}
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "required fields not set",
|
|
input: &pb2.Requireds{},
|
|
want: `{}`,
|
|
wantErr: true,
|
|
}, {
|
|
desc: "required fields partially set",
|
|
input: &pb2.Requireds{
|
|
ReqBool: scalar.Bool(false),
|
|
ReqSfixed64: scalar.Int64(0),
|
|
ReqDouble: scalar.Float64(1.23),
|
|
ReqString: scalar.String("hello"),
|
|
ReqEnum: pb2.Enum_ONE.Enum(),
|
|
},
|
|
want: `{
|
|
"reqBool": false,
|
|
"reqSfixed64": "0",
|
|
"reqDouble": 1.23,
|
|
"reqString": "hello",
|
|
"reqEnum": "ONE"
|
|
}`,
|
|
wantErr: true,
|
|
}, {
|
|
desc: "required fields not set with AllowPartial",
|
|
mo: protojson.MarshalOptions{AllowPartial: true},
|
|
input: &pb2.Requireds{
|
|
ReqBool: scalar.Bool(false),
|
|
ReqSfixed64: scalar.Int64(0),
|
|
ReqDouble: scalar.Float64(1.23),
|
|
ReqString: scalar.String("hello"),
|
|
ReqEnum: pb2.Enum_ONE.Enum(),
|
|
},
|
|
want: `{
|
|
"reqBool": false,
|
|
"reqSfixed64": "0",
|
|
"reqDouble": 1.23,
|
|
"reqString": "hello",
|
|
"reqEnum": "ONE"
|
|
}`,
|
|
}, {
|
|
desc: "required fields all set",
|
|
input: &pb2.Requireds{
|
|
ReqBool: scalar.Bool(false),
|
|
ReqSfixed64: scalar.Int64(0),
|
|
ReqDouble: scalar.Float64(1.23),
|
|
ReqString: scalar.String("hello"),
|
|
ReqEnum: pb2.Enum_ONE.Enum(),
|
|
ReqNested: &pb2.Nested{},
|
|
},
|
|
want: `{
|
|
"reqBool": false,
|
|
"reqSfixed64": "0",
|
|
"reqDouble": 1.23,
|
|
"reqString": "hello",
|
|
"reqEnum": "ONE",
|
|
"reqNested": {}
|
|
}`,
|
|
}, {
|
|
desc: "indirect required field",
|
|
input: &pb2.IndirectRequired{
|
|
OptNested: &pb2.NestedWithRequired{},
|
|
},
|
|
want: `{
|
|
"optNested": {}
|
|
}`,
|
|
wantErr: true,
|
|
}, {
|
|
desc: "indirect required field with AllowPartial",
|
|
mo: protojson.MarshalOptions{AllowPartial: true},
|
|
input: &pb2.IndirectRequired{
|
|
OptNested: &pb2.NestedWithRequired{},
|
|
},
|
|
want: `{
|
|
"optNested": {}
|
|
}`,
|
|
}, {
|
|
desc: "indirect required field in empty repeated",
|
|
input: &pb2.IndirectRequired{
|
|
RptNested: []*pb2.NestedWithRequired{},
|
|
},
|
|
want: `{}`,
|
|
}, {
|
|
desc: "indirect required field in repeated",
|
|
input: &pb2.IndirectRequired{
|
|
RptNested: []*pb2.NestedWithRequired{
|
|
&pb2.NestedWithRequired{},
|
|
},
|
|
},
|
|
want: `{
|
|
"rptNested": [
|
|
{}
|
|
]
|
|
}`,
|
|
wantErr: true,
|
|
}, {
|
|
desc: "indirect required field in repeated with AllowPartial",
|
|
mo: protojson.MarshalOptions{AllowPartial: true},
|
|
input: &pb2.IndirectRequired{
|
|
RptNested: []*pb2.NestedWithRequired{
|
|
&pb2.NestedWithRequired{},
|
|
},
|
|
},
|
|
want: `{
|
|
"rptNested": [
|
|
{}
|
|
]
|
|
}`,
|
|
}, {
|
|
desc: "indirect required field in empty map",
|
|
input: &pb2.IndirectRequired{
|
|
StrToNested: map[string]*pb2.NestedWithRequired{},
|
|
},
|
|
want: "{}",
|
|
}, {
|
|
desc: "indirect required field in map",
|
|
input: &pb2.IndirectRequired{
|
|
StrToNested: map[string]*pb2.NestedWithRequired{
|
|
"fail": &pb2.NestedWithRequired{},
|
|
},
|
|
},
|
|
want: `{
|
|
"strToNested": {
|
|
"fail": {}
|
|
}
|
|
}`,
|
|
wantErr: true,
|
|
}, {
|
|
desc: "indirect required field in map with AllowPartial",
|
|
mo: protojson.MarshalOptions{AllowPartial: true},
|
|
input: &pb2.IndirectRequired{
|
|
StrToNested: map[string]*pb2.NestedWithRequired{
|
|
"fail": &pb2.NestedWithRequired{},
|
|
},
|
|
},
|
|
want: `{
|
|
"strToNested": {
|
|
"fail": {}
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "indirect required field in oneof",
|
|
input: &pb2.IndirectRequired{
|
|
Union: &pb2.IndirectRequired_OneofNested{
|
|
OneofNested: &pb2.NestedWithRequired{},
|
|
},
|
|
},
|
|
want: `{
|
|
"oneofNested": {}
|
|
}`,
|
|
wantErr: true,
|
|
}, {
|
|
desc: "indirect required field in oneof with AllowPartial",
|
|
mo: protojson.MarshalOptions{AllowPartial: true},
|
|
input: &pb2.IndirectRequired{
|
|
Union: &pb2.IndirectRequired_OneofNested{
|
|
OneofNested: &pb2.NestedWithRequired{},
|
|
},
|
|
},
|
|
want: `{
|
|
"oneofNested": {}
|
|
}`,
|
|
}, {
|
|
desc: "unknown fields are ignored",
|
|
input: &pb2.Scalars{
|
|
OptString: scalar.String("no unknowns"),
|
|
XXX_unrecognized: pack.Message{
|
|
pack.Tag{101, pack.BytesType}, pack.String("hello world"),
|
|
}.Marshal(),
|
|
},
|
|
want: `{
|
|
"optString": "no unknowns"
|
|
}`,
|
|
}, {
|
|
desc: "json_name",
|
|
input: &pb3.JSONNames{
|
|
SString: "json_name",
|
|
},
|
|
want: `{
|
|
"foo_bar": "json_name"
|
|
}`,
|
|
}, {
|
|
desc: "extensions of non-repeated fields",
|
|
input: func() proto.Message {
|
|
m := &pb2.Extensions{
|
|
OptString: scalar.String("non-extension field"),
|
|
OptBool: scalar.Bool(true),
|
|
OptInt32: scalar.Int32(42),
|
|
}
|
|
setExtension(m, pb2.E_OptExtBool, true)
|
|
setExtension(m, pb2.E_OptExtString, "extension field")
|
|
setExtension(m, pb2.E_OptExtEnum, pb2.Enum_TEN)
|
|
setExtension(m, pb2.E_OptExtNested, &pb2.Nested{
|
|
OptString: scalar.String("nested in an extension"),
|
|
OptNested: &pb2.Nested{
|
|
OptString: scalar.String("another nested in an extension"),
|
|
},
|
|
})
|
|
return m
|
|
}(),
|
|
want: `{
|
|
"optString": "non-extension field",
|
|
"optBool": true,
|
|
"optInt32": 42,
|
|
"[pb2.opt_ext_bool]": true,
|
|
"[pb2.opt_ext_enum]": "TEN",
|
|
"[pb2.opt_ext_nested]": {
|
|
"optString": "nested in an extension",
|
|
"optNested": {
|
|
"optString": "another nested in an extension"
|
|
}
|
|
},
|
|
"[pb2.opt_ext_string]": "extension field"
|
|
}`,
|
|
}, {
|
|
desc: "extensions of repeated fields",
|
|
input: func() proto.Message {
|
|
m := &pb2.Extensions{}
|
|
setExtension(m, pb2.E_RptExtEnum, &[]pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE})
|
|
setExtension(m, pb2.E_RptExtFixed32, &[]uint32{42, 47})
|
|
setExtension(m, pb2.E_RptExtNested, &[]*pb2.Nested{
|
|
&pb2.Nested{OptString: scalar.String("one")},
|
|
&pb2.Nested{OptString: scalar.String("two")},
|
|
&pb2.Nested{OptString: scalar.String("three")},
|
|
})
|
|
return m
|
|
}(),
|
|
want: `{
|
|
"[pb2.rpt_ext_enum]": [
|
|
"TEN",
|
|
101,
|
|
"ONE"
|
|
],
|
|
"[pb2.rpt_ext_fixed32]": [
|
|
42,
|
|
47
|
|
],
|
|
"[pb2.rpt_ext_nested]": [
|
|
{
|
|
"optString": "one"
|
|
},
|
|
{
|
|
"optString": "two"
|
|
},
|
|
{
|
|
"optString": "three"
|
|
}
|
|
]
|
|
}`,
|
|
}, {
|
|
desc: "extensions of non-repeated fields in another message",
|
|
input: func() proto.Message {
|
|
m := &pb2.Extensions{}
|
|
setExtension(m, pb2.E_ExtensionsContainer_OptExtBool, true)
|
|
setExtension(m, pb2.E_ExtensionsContainer_OptExtString, "extension field")
|
|
setExtension(m, pb2.E_ExtensionsContainer_OptExtEnum, pb2.Enum_TEN)
|
|
setExtension(m, pb2.E_ExtensionsContainer_OptExtNested, &pb2.Nested{
|
|
OptString: scalar.String("nested in an extension"),
|
|
OptNested: &pb2.Nested{
|
|
OptString: scalar.String("another nested in an extension"),
|
|
},
|
|
})
|
|
return m
|
|
}(),
|
|
want: `{
|
|
"[pb2.ExtensionsContainer.opt_ext_bool]": true,
|
|
"[pb2.ExtensionsContainer.opt_ext_enum]": "TEN",
|
|
"[pb2.ExtensionsContainer.opt_ext_nested]": {
|
|
"optString": "nested in an extension",
|
|
"optNested": {
|
|
"optString": "another nested in an extension"
|
|
}
|
|
},
|
|
"[pb2.ExtensionsContainer.opt_ext_string]": "extension field"
|
|
}`,
|
|
}, {
|
|
desc: "extensions of repeated fields in another message",
|
|
input: func() proto.Message {
|
|
m := &pb2.Extensions{
|
|
OptString: scalar.String("non-extension field"),
|
|
OptBool: scalar.Bool(true),
|
|
OptInt32: scalar.Int32(42),
|
|
}
|
|
setExtension(m, pb2.E_ExtensionsContainer_RptExtEnum, &[]pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE})
|
|
setExtension(m, pb2.E_ExtensionsContainer_RptExtString, &[]string{"hello", "world"})
|
|
setExtension(m, pb2.E_ExtensionsContainer_RptExtNested, &[]*pb2.Nested{
|
|
&pb2.Nested{OptString: scalar.String("one")},
|
|
&pb2.Nested{OptString: scalar.String("two")},
|
|
&pb2.Nested{OptString: scalar.String("three")},
|
|
})
|
|
return m
|
|
}(),
|
|
want: `{
|
|
"optString": "non-extension field",
|
|
"optBool": true,
|
|
"optInt32": 42,
|
|
"[pb2.ExtensionsContainer.rpt_ext_enum]": [
|
|
"TEN",
|
|
101,
|
|
"ONE"
|
|
],
|
|
"[pb2.ExtensionsContainer.rpt_ext_nested]": [
|
|
{
|
|
"optString": "one"
|
|
},
|
|
{
|
|
"optString": "two"
|
|
},
|
|
{
|
|
"optString": "three"
|
|
}
|
|
],
|
|
"[pb2.ExtensionsContainer.rpt_ext_string]": [
|
|
"hello",
|
|
"world"
|
|
]
|
|
}`,
|
|
}, {
|
|
desc: "MessageSet",
|
|
input: func() proto.Message {
|
|
m := &pb2.MessageSet{}
|
|
setExtension(m, pb2.E_MessageSetExtension_MessageSetExtension, &pb2.MessageSetExtension{
|
|
OptString: scalar.String("a messageset extension"),
|
|
})
|
|
setExtension(m, pb2.E_MessageSetExtension_NotMessageSetExtension, &pb2.MessageSetExtension{
|
|
OptString: scalar.String("not a messageset extension"),
|
|
})
|
|
setExtension(m, pb2.E_MessageSetExtension_ExtNested, &pb2.Nested{
|
|
OptString: scalar.String("just a regular extension"),
|
|
})
|
|
return m
|
|
}(),
|
|
want: `{
|
|
"[pb2.MessageSetExtension]": {
|
|
"optString": "a messageset extension"
|
|
},
|
|
"[pb2.MessageSetExtension.ext_nested]": {
|
|
"optString": "just a regular extension"
|
|
},
|
|
"[pb2.MessageSetExtension.not_message_set_extension]": {
|
|
"optString": "not a messageset extension"
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "not real MessageSet 1",
|
|
input: func() proto.Message {
|
|
m := &pb2.FakeMessageSet{}
|
|
setExtension(m, pb2.E_FakeMessageSetExtension_MessageSetExtension, &pb2.FakeMessageSetExtension{
|
|
OptString: scalar.String("not a messageset extension"),
|
|
})
|
|
return m
|
|
}(),
|
|
want: `{
|
|
"[pb2.FakeMessageSetExtension.message_set_extension]": {
|
|
"optString": "not a messageset extension"
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "not real MessageSet 2",
|
|
input: func() proto.Message {
|
|
m := &pb2.MessageSet{}
|
|
setExtension(m, pb2.E_MessageSetExtension, &pb2.FakeMessageSetExtension{
|
|
OptString: scalar.String("another not a messageset extension"),
|
|
})
|
|
return m
|
|
}(),
|
|
want: `{
|
|
"[pb2.message_set_extension]": {
|
|
"optString": "another not a messageset extension"
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "BoolValue empty",
|
|
input: &wrapperspb.BoolValue{},
|
|
want: `false`,
|
|
}, {
|
|
desc: "BoolValue",
|
|
input: &wrapperspb.BoolValue{Value: true},
|
|
want: `true`,
|
|
}, {
|
|
desc: "Int32Value empty",
|
|
input: &wrapperspb.Int32Value{},
|
|
want: `0`,
|
|
}, {
|
|
desc: "Int32Value",
|
|
input: &wrapperspb.Int32Value{Value: 42},
|
|
want: `42`,
|
|
}, {
|
|
desc: "Int64Value",
|
|
input: &wrapperspb.Int64Value{Value: 42},
|
|
want: `"42"`,
|
|
}, {
|
|
desc: "UInt32Value",
|
|
input: &wrapperspb.UInt32Value{Value: 42},
|
|
want: `42`,
|
|
}, {
|
|
desc: "UInt64Value",
|
|
input: &wrapperspb.UInt64Value{Value: 42},
|
|
want: `"42"`,
|
|
}, {
|
|
desc: "FloatValue",
|
|
input: &wrapperspb.FloatValue{Value: 1.02},
|
|
want: `1.02`,
|
|
}, {
|
|
desc: "FloatValue Infinity",
|
|
input: &wrapperspb.FloatValue{Value: float32(math.Inf(-1))},
|
|
want: `"-Infinity"`,
|
|
}, {
|
|
desc: "DoubleValue",
|
|
input: &wrapperspb.DoubleValue{Value: 1.02},
|
|
want: `1.02`,
|
|
}, {
|
|
desc: "DoubleValue NaN",
|
|
input: &wrapperspb.DoubleValue{Value: math.NaN()},
|
|
want: `"NaN"`,
|
|
}, {
|
|
desc: "StringValue empty",
|
|
input: &wrapperspb.StringValue{},
|
|
want: `""`,
|
|
}, {
|
|
desc: "StringValue",
|
|
input: &wrapperspb.StringValue{Value: "谷歌"},
|
|
want: `"谷歌"`,
|
|
}, {
|
|
desc: "StringValue with invalid UTF8 error",
|
|
input: &wrapperspb.StringValue{Value: "abc\xff"},
|
|
want: "\"abc\xff\"",
|
|
wantErr: true,
|
|
}, {
|
|
desc: "StringValue field with invalid UTF8 error",
|
|
input: &pb2.KnownTypes{
|
|
OptString: &wrapperspb.StringValue{Value: "abc\xff"},
|
|
},
|
|
want: "{\n \"optString\": \"abc\xff\"\n}",
|
|
wantErr: true,
|
|
}, {
|
|
desc: "BytesValue",
|
|
input: &wrapperspb.BytesValue{Value: []byte("hello")},
|
|
want: `"aGVsbG8="`,
|
|
}, {
|
|
desc: "Empty",
|
|
input: &emptypb.Empty{},
|
|
want: `{}`,
|
|
}, {
|
|
desc: "NullValue field",
|
|
input: &pb2.KnownTypes{OptNull: new(structpb.NullValue)},
|
|
want: `{
|
|
"optNull": null
|
|
}`,
|
|
}, {
|
|
desc: "Value empty",
|
|
input: &structpb.Value{},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Value empty field",
|
|
input: &pb2.KnownTypes{
|
|
OptValue: &structpb.Value{},
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Value contains NullValue",
|
|
input: &structpb.Value{Kind: &structpb.Value_NullValue{}},
|
|
want: `null`,
|
|
}, {
|
|
desc: "Value contains BoolValue",
|
|
input: &structpb.Value{Kind: &structpb.Value_BoolValue{}},
|
|
want: `false`,
|
|
}, {
|
|
desc: "Value contains NumberValue",
|
|
input: &structpb.Value{Kind: &structpb.Value_NumberValue{1.02}},
|
|
want: `1.02`,
|
|
}, {
|
|
desc: "Value contains StringValue",
|
|
input: &structpb.Value{Kind: &structpb.Value_StringValue{"hello"}},
|
|
want: `"hello"`,
|
|
}, {
|
|
desc: "Value contains StringValue with invalid UTF8",
|
|
input: &structpb.Value{Kind: &structpb.Value_StringValue{"\xff"}},
|
|
want: "\"\xff\"",
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Value contains Struct",
|
|
input: &structpb.Value{
|
|
Kind: &structpb.Value_StructValue{
|
|
&structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"null": {Kind: &structpb.Value_NullValue{}},
|
|
"number": {Kind: &structpb.Value_NumberValue{}},
|
|
"string": {Kind: &structpb.Value_StringValue{}},
|
|
"struct": {Kind: &structpb.Value_StructValue{}},
|
|
"list": {Kind: &structpb.Value_ListValue{}},
|
|
"bool": {Kind: &structpb.Value_BoolValue{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"bool": false,
|
|
"list": [],
|
|
"null": null,
|
|
"number": 0,
|
|
"string": "",
|
|
"struct": {}
|
|
}`,
|
|
}, {
|
|
desc: "Value contains ListValue",
|
|
input: &structpb.Value{
|
|
Kind: &structpb.Value_ListValue{
|
|
&structpb.ListValue{
|
|
Values: []*structpb.Value{
|
|
{Kind: &structpb.Value_BoolValue{}},
|
|
{Kind: &structpb.Value_NullValue{}},
|
|
{Kind: &structpb.Value_NumberValue{}},
|
|
{Kind: &structpb.Value_StringValue{}},
|
|
{Kind: &structpb.Value_StructValue{}},
|
|
{Kind: &structpb.Value_ListValue{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: `[
|
|
false,
|
|
null,
|
|
0,
|
|
"",
|
|
{},
|
|
[]
|
|
]`,
|
|
}, {
|
|
desc: "Struct with nil map",
|
|
input: &structpb.Struct{},
|
|
want: `{}`,
|
|
}, {
|
|
desc: "Struct with empty map",
|
|
input: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{},
|
|
},
|
|
want: `{}`,
|
|
}, {
|
|
desc: "Struct",
|
|
input: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"bool": {Kind: &structpb.Value_BoolValue{true}},
|
|
"null": {Kind: &structpb.Value_NullValue{}},
|
|
"number": {Kind: &structpb.Value_NumberValue{3.1415}},
|
|
"string": {Kind: &structpb.Value_StringValue{"hello"}},
|
|
"struct": {
|
|
Kind: &structpb.Value_StructValue{
|
|
&structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"string": {Kind: &structpb.Value_StringValue{"world"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"list": {
|
|
Kind: &structpb.Value_ListValue{
|
|
&structpb.ListValue{
|
|
Values: []*structpb.Value{
|
|
{Kind: &structpb.Value_BoolValue{}},
|
|
{Kind: &structpb.Value_NullValue{}},
|
|
{Kind: &structpb.Value_NumberValue{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"bool": true,
|
|
"list": [
|
|
false,
|
|
null,
|
|
0
|
|
],
|
|
"null": null,
|
|
"number": 3.1415,
|
|
"string": "hello",
|
|
"struct": {
|
|
"string": "world"
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "Struct message with invalid UTF8 string",
|
|
input: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"string": {Kind: &structpb.Value_StringValue{"\xff"}},
|
|
},
|
|
},
|
|
want: "{\n \"string\": \"\xff\"\n}",
|
|
wantErr: true,
|
|
}, {
|
|
desc: "ListValue with nil values",
|
|
input: &structpb.ListValue{},
|
|
want: `[]`,
|
|
}, {
|
|
desc: "ListValue with empty values",
|
|
input: &structpb.ListValue{
|
|
Values: []*structpb.Value{},
|
|
},
|
|
want: `[]`,
|
|
}, {
|
|
desc: "ListValue",
|
|
input: &structpb.ListValue{
|
|
Values: []*structpb.Value{
|
|
{Kind: &structpb.Value_BoolValue{true}},
|
|
{Kind: &structpb.Value_NullValue{}},
|
|
{Kind: &structpb.Value_NumberValue{3.1415}},
|
|
{Kind: &structpb.Value_StringValue{"hello"}},
|
|
{
|
|
Kind: &structpb.Value_ListValue{
|
|
&structpb.ListValue{
|
|
Values: []*structpb.Value{
|
|
{Kind: &structpb.Value_BoolValue{}},
|
|
{Kind: &structpb.Value_NullValue{}},
|
|
{Kind: &structpb.Value_NumberValue{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Kind: &structpb.Value_StructValue{
|
|
&structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"string": {Kind: &structpb.Value_StringValue{"world"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: `[
|
|
true,
|
|
null,
|
|
3.1415,
|
|
"hello",
|
|
[
|
|
false,
|
|
null,
|
|
0
|
|
],
|
|
{
|
|
"string": "world"
|
|
}
|
|
]`,
|
|
}, {
|
|
desc: "ListValue with invalid UTF8 string",
|
|
input: &structpb.ListValue{
|
|
Values: []*structpb.Value{
|
|
{Kind: &structpb.Value_StringValue{"\xff"}},
|
|
},
|
|
},
|
|
want: "[\n \"\xff\"\n]",
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Duration empty",
|
|
input: &durationpb.Duration{},
|
|
want: `"0s"`,
|
|
}, {
|
|
desc: "Duration with secs",
|
|
input: &durationpb.Duration{Seconds: 3},
|
|
want: `"3s"`,
|
|
}, {
|
|
desc: "Duration with -secs",
|
|
input: &durationpb.Duration{Seconds: -3},
|
|
want: `"-3s"`,
|
|
}, {
|
|
desc: "Duration with nanos",
|
|
input: &durationpb.Duration{Nanos: 1e6},
|
|
want: `"0.001s"`,
|
|
}, {
|
|
desc: "Duration with -nanos",
|
|
input: &durationpb.Duration{Nanos: -1e6},
|
|
want: `"-0.001s"`,
|
|
}, {
|
|
desc: "Duration with large secs",
|
|
input: &durationpb.Duration{Seconds: 1e10, Nanos: 1},
|
|
want: `"10000000000.000000001s"`,
|
|
}, {
|
|
desc: "Duration with 6-digit nanos",
|
|
input: &durationpb.Duration{Nanos: 1e4},
|
|
want: `"0.000010s"`,
|
|
}, {
|
|
desc: "Duration with 3-digit nanos",
|
|
input: &durationpb.Duration{Nanos: 1e6},
|
|
want: `"0.001s"`,
|
|
}, {
|
|
desc: "Duration with -secs -nanos",
|
|
input: &durationpb.Duration{Seconds: -123, Nanos: -450},
|
|
want: `"-123.000000450s"`,
|
|
}, {
|
|
desc: "Duration max value",
|
|
input: &durationpb.Duration{Seconds: 315576000000, Nanos: 999999999},
|
|
want: `"315576000000.999999999s"`,
|
|
}, {
|
|
desc: "Duration min value",
|
|
input: &durationpb.Duration{Seconds: -315576000000, Nanos: -999999999},
|
|
want: `"-315576000000.999999999s"`,
|
|
}, {
|
|
desc: "Duration with +secs -nanos",
|
|
input: &durationpb.Duration{Seconds: 1, Nanos: -1},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Duration with -secs +nanos",
|
|
input: &durationpb.Duration{Seconds: -1, Nanos: 1},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Duration with +secs out of range",
|
|
input: &durationpb.Duration{Seconds: 315576000001},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Duration with -secs out of range",
|
|
input: &durationpb.Duration{Seconds: -315576000001},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Duration with +nanos out of range",
|
|
input: &durationpb.Duration{Seconds: 0, Nanos: 1e9},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Duration with -nanos out of range",
|
|
input: &durationpb.Duration{Seconds: 0, Nanos: -1e9},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Timestamp zero",
|
|
input: ×tamppb.Timestamp{},
|
|
want: `"1970-01-01T00:00:00Z"`,
|
|
}, {
|
|
desc: "Timestamp",
|
|
input: ×tamppb.Timestamp{Seconds: 1553036601},
|
|
want: `"2019-03-19T23:03:21Z"`,
|
|
}, {
|
|
desc: "Timestamp with nanos",
|
|
input: ×tamppb.Timestamp{Seconds: 1553036601, Nanos: 1},
|
|
want: `"2019-03-19T23:03:21.000000001Z"`,
|
|
}, {
|
|
desc: "Timestamp with 6-digit nanos",
|
|
input: ×tamppb.Timestamp{Nanos: 1e3},
|
|
want: `"1970-01-01T00:00:00.000001Z"`,
|
|
}, {
|
|
desc: "Timestamp with 3-digit nanos",
|
|
input: ×tamppb.Timestamp{Nanos: 1e7},
|
|
want: `"1970-01-01T00:00:00.010Z"`,
|
|
}, {
|
|
desc: "Timestamp max value",
|
|
input: ×tamppb.Timestamp{Seconds: 253402300799, Nanos: 999999999},
|
|
want: `"9999-12-31T23:59:59.999999999Z"`,
|
|
}, {
|
|
desc: "Timestamp min value",
|
|
input: ×tamppb.Timestamp{Seconds: -62135596800},
|
|
want: `"0001-01-01T00:00:00Z"`,
|
|
}, {
|
|
desc: "Timestamp with +secs out of range",
|
|
input: ×tamppb.Timestamp{Seconds: 253402300800},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Timestamp with -secs out of range",
|
|
input: ×tamppb.Timestamp{Seconds: -62135596801},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Timestamp with -nanos",
|
|
input: ×tamppb.Timestamp{Nanos: -1},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Timestamp with +nanos out of range",
|
|
input: ×tamppb.Timestamp{Nanos: 1e9},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "FieldMask empty",
|
|
input: &fieldmaskpb.FieldMask{},
|
|
want: `""`,
|
|
}, {
|
|
desc: "FieldMask",
|
|
input: &fieldmaskpb.FieldMask{
|
|
Paths: []string{
|
|
"foo",
|
|
"foo_bar",
|
|
"foo.bar_qux",
|
|
"_foo",
|
|
},
|
|
},
|
|
want: `"foo,fooBar,foo.barQux,Foo"`,
|
|
}, {
|
|
desc: "FieldMask error 1",
|
|
input: &fieldmaskpb.FieldMask{
|
|
Paths: []string{"foo_"},
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "FieldMask error 2",
|
|
input: &fieldmaskpb.FieldMask{
|
|
Paths: []string{"foo__bar"},
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Any empty",
|
|
input: &anypb.Any{},
|
|
want: `{}`,
|
|
}, {
|
|
desc: "Any with non-custom message",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &pb2.Nested{
|
|
OptString: scalar.String("embedded inside Any"),
|
|
OptNested: &pb2.Nested{
|
|
OptString: scalar.String("inception"),
|
|
},
|
|
}
|
|
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: "foo/pb2.Nested",
|
|
Value: b,
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "foo/pb2.Nested",
|
|
"optString": "embedded inside Any",
|
|
"optNested": {
|
|
"optString": "inception"
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "Any with empty embedded message",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
|
|
},
|
|
input: &anypb.Any{TypeUrl: "foo/pb2.Nested"},
|
|
want: `{
|
|
"@type": "foo/pb2.Nested"
|
|
}`,
|
|
}, {
|
|
desc: "Any without registered type",
|
|
mo: protojson.MarshalOptions{Resolver: preg.NewTypes()},
|
|
input: &anypb.Any{TypeUrl: "foo/pb2.Nested"},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Any with missing required error",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.PartialRequired{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &pb2.PartialRequired{
|
|
OptString: scalar.String("embedded inside Any"),
|
|
}
|
|
b, err := proto.MarshalOptions{
|
|
AllowPartial: true,
|
|
Deterministic: true,
|
|
}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
|
|
Value: b,
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "pb2.PartialRequired",
|
|
"optString": "embedded inside Any"
|
|
}`,
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Any with partial required and AllowPartial",
|
|
mo: protojson.MarshalOptions{
|
|
AllowPartial: true,
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.PartialRequired{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &pb2.PartialRequired{
|
|
OptString: scalar.String("embedded inside Any"),
|
|
}
|
|
b, err := proto.MarshalOptions{
|
|
AllowPartial: true,
|
|
Deterministic: true,
|
|
}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
|
|
Value: b,
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "pb2.PartialRequired",
|
|
"optString": "embedded inside Any"
|
|
}`,
|
|
}, {
|
|
desc: "Any with invalid UTF8",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &pb2.Nested{
|
|
OptString: scalar.String("abc\xff"),
|
|
}
|
|
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: "foo/pb2.Nested",
|
|
Value: b,
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "foo/pb2.Nested",
|
|
"optString": "` + "abc\xff" + `"
|
|
}`,
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Any with invalid value",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&pb2.Nested{})),
|
|
},
|
|
input: &anypb.Any{
|
|
TypeUrl: "foo/pb2.Nested",
|
|
Value: dhex("80"),
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Any with BoolValue",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&wrapperspb.BoolValue{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &wrapperspb.BoolValue{Value: true}
|
|
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: "type.googleapis.com/google.protobuf.BoolValue",
|
|
Value: b,
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "type.googleapis.com/google.protobuf.BoolValue",
|
|
"value": true
|
|
}`,
|
|
}, {
|
|
desc: "Any with Empty",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&emptypb.Empty{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &emptypb.Empty{}
|
|
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: "type.googleapis.com/google.protobuf.Empty",
|
|
Value: b,
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "type.googleapis.com/google.protobuf.Empty",
|
|
"value": {}
|
|
}`,
|
|
}, {
|
|
desc: "Any with StringValue containing invalid UTF8",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&wrapperspb.StringValue{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &wrapperspb.StringValue{Value: "abcd"}
|
|
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: "google.protobuf.StringValue",
|
|
Value: bytes.Replace(b, []byte("abcd"), []byte("abc\xff"), -1),
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "google.protobuf.StringValue",
|
|
"value": "` + "abc\xff" + `"
|
|
}`,
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Any with Int64Value",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&wrapperspb.Int64Value{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &wrapperspb.Int64Value{Value: 42}
|
|
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: "google.protobuf.Int64Value",
|
|
Value: b,
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "google.protobuf.Int64Value",
|
|
"value": "42"
|
|
}`,
|
|
}, {
|
|
desc: "Any with Duration",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&durationpb.Duration{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &durationpb.Duration{}
|
|
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: "type.googleapis.com/google.protobuf.Duration",
|
|
Value: b,
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "type.googleapis.com/google.protobuf.Duration",
|
|
"value": "0s"
|
|
}`,
|
|
}, {
|
|
desc: "Any with empty Value",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&structpb.Value{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &structpb.Value{}
|
|
b, err := proto.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: "type.googleapis.com/google.protobuf.Value",
|
|
Value: b,
|
|
}
|
|
}(),
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Any with Value of StringValue",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&structpb.Value{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &structpb.Value{Kind: &structpb.Value_StringValue{"abcd"}}
|
|
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: "type.googleapis.com/google.protobuf.Value",
|
|
Value: bytes.Replace(b, []byte("abcd"), []byte("abc\xff"), -1),
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "type.googleapis.com/google.protobuf.Value",
|
|
"value": "` + "abc\xff" + `"
|
|
}`,
|
|
wantErr: true,
|
|
}, {
|
|
desc: "Any with Value of NullValue",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&structpb.Value{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &structpb.Value{Kind: &structpb.Value_NullValue{}}
|
|
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: "type.googleapis.com/google.protobuf.Value",
|
|
Value: b,
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "type.googleapis.com/google.protobuf.Value",
|
|
"value": null
|
|
}`,
|
|
}, {
|
|
desc: "Any with Struct",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(
|
|
pimpl.Export{}.MessageTypeOf(&structpb.Struct{}),
|
|
pimpl.Export{}.MessageTypeOf(&structpb.Value{}),
|
|
pimpl.Export{}.MessageTypeOf(&wrapperspb.BoolValue{}),
|
|
pimpl.Export{}.EnumTypeOf(structpb.NullValue_NULL_VALUE),
|
|
pimpl.Export{}.MessageTypeOf(&wrapperspb.StringValue{}),
|
|
),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"bool": {Kind: &structpb.Value_BoolValue{true}},
|
|
"null": {Kind: &structpb.Value_NullValue{}},
|
|
"string": {Kind: &structpb.Value_StringValue{"hello"}},
|
|
"struct": {
|
|
Kind: &structpb.Value_StructValue{
|
|
&structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"string": {Kind: &structpb.Value_StringValue{"world"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
TypeUrl: "google.protobuf.Struct",
|
|
Value: b,
|
|
}
|
|
}(),
|
|
want: `{
|
|
"@type": "google.protobuf.Struct",
|
|
"value": {
|
|
"bool": true,
|
|
"null": null,
|
|
"string": "hello",
|
|
"struct": {
|
|
"string": "world"
|
|
}
|
|
}
|
|
}`,
|
|
}, {
|
|
desc: "Any with missing type_url",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&wrapperspb.BoolValue{})),
|
|
},
|
|
input: func() proto.Message {
|
|
m := &wrapperspb.BoolValue{Value: true}
|
|
b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("error in binary marshaling message for Any.value: %v", err)
|
|
}
|
|
return &anypb.Any{
|
|
Value: b,
|
|
}
|
|
}(),
|
|
wantErr: true,
|
|
}, {
|
|
desc: "well known types as field values",
|
|
mo: protojson.MarshalOptions{
|
|
Resolver: preg.NewTypes(pimpl.Export{}.MessageTypeOf(&emptypb.Empty{})),
|
|
},
|
|
input: &pb2.KnownTypes{
|
|
OptBool: &wrapperspb.BoolValue{Value: false},
|
|
OptInt32: &wrapperspb.Int32Value{Value: 42},
|
|
OptInt64: &wrapperspb.Int64Value{Value: 42},
|
|
OptUint32: &wrapperspb.UInt32Value{Value: 42},
|
|
OptUint64: &wrapperspb.UInt64Value{Value: 42},
|
|
OptFloat: &wrapperspb.FloatValue{Value: 1.23},
|
|
OptDouble: &wrapperspb.DoubleValue{Value: 3.1415},
|
|
OptString: &wrapperspb.StringValue{Value: "hello"},
|
|
OptBytes: &wrapperspb.BytesValue{Value: []byte("hello")},
|
|
OptDuration: &durationpb.Duration{Seconds: 123},
|
|
OptTimestamp: ×tamppb.Timestamp{Seconds: 1553036601},
|
|
OptStruct: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"string": {Kind: &structpb.Value_StringValue{"hello"}},
|
|
},
|
|
},
|
|
OptList: &structpb.ListValue{
|
|
Values: []*structpb.Value{
|
|
{Kind: &structpb.Value_NullValue{}},
|
|
{Kind: &structpb.Value_StringValue{}},
|
|
{Kind: &structpb.Value_StructValue{}},
|
|
{Kind: &structpb.Value_ListValue{}},
|
|
},
|
|
},
|
|
OptValue: &structpb.Value{
|
|
Kind: &structpb.Value_StringValue{"world"},
|
|
},
|
|
OptEmpty: &emptypb.Empty{},
|
|
OptAny: &anypb.Any{
|
|
TypeUrl: "google.protobuf.Empty",
|
|
},
|
|
OptFieldmask: &fieldmaskpb.FieldMask{
|
|
Paths: []string{"foo_bar", "bar_foo"},
|
|
},
|
|
},
|
|
want: `{
|
|
"optBool": false,
|
|
"optInt32": 42,
|
|
"optInt64": "42",
|
|
"optUint32": 42,
|
|
"optUint64": "42",
|
|
"optFloat": 1.23,
|
|
"optDouble": 3.1415,
|
|
"optString": "hello",
|
|
"optBytes": "aGVsbG8=",
|
|
"optDuration": "123s",
|
|
"optTimestamp": "2019-03-19T23:03:21Z",
|
|
"optStruct": {
|
|
"string": "hello"
|
|
},
|
|
"optList": [
|
|
null,
|
|
"",
|
|
{},
|
|
[]
|
|
],
|
|
"optValue": "world",
|
|
"optEmpty": {},
|
|
"optAny": {
|
|
"@type": "google.protobuf.Empty",
|
|
"value": {}
|
|
},
|
|
"optFieldmask": "fooBar,barFoo"
|
|
}`,
|
|
}}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
// Use 2-space indentation on all MarshalOptions.
|
|
tt.mo.Indent = " "
|
|
b, err := tt.mo.Marshal(tt.input)
|
|
if err != nil && !tt.wantErr {
|
|
t.Errorf("Marshal() returned error: %v\n", err)
|
|
}
|
|
if err == nil && tt.wantErr {
|
|
t.Errorf("Marshal() got nil error, want error\n")
|
|
}
|
|
got := string(b)
|
|
if got != tt.want {
|
|
t.Errorf("Marshal()\n<got>\n%v\n<want>\n%v\n", got, tt.want)
|
|
if diff := cmp.Diff(tt.want, got, splitLines); diff != "" {
|
|
t.Errorf("Marshal() diff -want +got\n%v\n", diff)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|