encoding/jsonpb: add support for unmarshaling wrapper and struct types

Also, fixed unmarshaling of map messages where non-fatal errors were not
propagated up.

Change-Id: I06415b4a4ccd12135f0fdfaa38ccda54866139e7
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/168997
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/encoding/jsonpb/decode_test.go b/encoding/jsonpb/decode_test.go
index 2552275..725c5a8 100644
--- a/encoding/jsonpb/decode_test.go
+++ b/encoding/jsonpb/decode_test.go
@@ -16,6 +16,8 @@
 	"github.com/golang/protobuf/v2/proto"
 	preg "github.com/golang/protobuf/v2/reflect/protoregistry"
 	"github.com/golang/protobuf/v2/runtime/protoiface"
+
+	knownpb "github.com/golang/protobuf/v2/types/known"
 )
 
 func init() {
@@ -754,12 +756,28 @@
 			},
 		},
 	}, {
-		desc:         "repeated scalars containing invalid type",
+		desc:         "repeated string contains invalid UTF8",
+		inputMessage: &pb2.Repeats{},
+		inputText:    `{"rptString": ["` + "abc\xff" + `"]}`,
+		wantMessage: &pb2.Repeats{
+			RptString: []string{"abc\xff"},
+		},
+		wantErr: true,
+	}, {
+		desc:         "repeated messages contain invalid UTF8",
+		inputMessage: &pb2.Nests{},
+		inputText:    `{"rptNested": [{"optString": "` + "abc\xff" + `"}]}`,
+		wantMessage: &pb2.Nests{
+			RptNested: []*pb2.Nested{{OptString: scalar.String("abc\xff")}},
+		},
+		wantErr: true,
+	}, {
+		desc:         "repeated scalars contain invalid type",
 		inputMessage: &pb2.Repeats{},
 		inputText:    `{"rptString": ["hello", null, "world"]}`,
 		wantErr:      true,
 	}, {
-		desc:         "repeated messages containing invalid type",
+		desc:         "repeated messages contain invalid type",
 		inputMessage: &pb2.Nests{},
 		inputText:    `{"rptNested": [{}, null]}`,
 		wantErr:      true,
@@ -938,6 +956,36 @@
 }`,
 		wantErr: true,
 	}, {
+		desc:         "map contains contains message value with invalid UTF8",
+		inputMessage: &pb3.Maps{},
+		inputText: `{
+  "strToNested": {
+    "hello": {
+      "sString": "` + "abc\xff" + `"
+	}
+  }
+}`,
+		wantMessage: &pb3.Maps{
+			StrToNested: map[string]*pb3.Nested{
+				"hello": {SString: "abc\xff"},
+			},
+		},
+		wantErr: true,
+	}, {
+		desc:         "map key contains invalid UTF8",
+		inputMessage: &pb3.Maps{},
+		inputText: `{
+  "strToNested": {
+    "` + "abc\xff" + `": {}
+  }
+}`,
+		wantMessage: &pb3.Maps{
+			StrToNested: map[string]*pb3.Nested{
+				"abc\xff": {},
+			},
+		},
+		wantErr: true,
+	}, {
 		desc:         "extensions of non-repeated fields",
 		inputMessage: &pb2.Extensions{},
 		inputText: `{
@@ -947,8 +995,8 @@
   "[pb2.opt_ext_bool]": true,
   "[pb2.opt_ext_nested]": {
     "optString": "nested in an extension",
-    "opt_nested": {
-      "opt_string": "another nested in an extension"
+    "optNested": {
+      "optString": "another nested in an extension"
     }
   },
   "[pb2.opt_ext_string]": "extension field",
@@ -1146,6 +1194,374 @@
 			})
 			return m
 		}(),
+	}, {
+		desc:         "Empty",
+		inputMessage: &knownpb.Empty{},
+		inputText:    `{}`,
+		wantMessage:  &knownpb.Empty{},
+	}, {
+		desc:         "Empty contains unknown",
+		inputMessage: &knownpb.Empty{},
+		inputText:    `{"unknown": null}`,
+		wantErr:      true,
+	}, {
+		desc:         "BoolValue false",
+		inputMessage: &knownpb.BoolValue{},
+		inputText:    `false`,
+		wantMessage:  &knownpb.BoolValue{},
+	}, {
+		desc:         "BoolValue true",
+		inputMessage: &knownpb.BoolValue{},
+		inputText:    `true`,
+		wantMessage:  &knownpb.BoolValue{Value: true},
+	}, {
+		desc:         "BoolValue invalid value",
+		inputMessage: &knownpb.BoolValue{},
+		inputText:    `{}`,
+		wantErr:      true,
+	}, {
+		desc:         "Int32Value",
+		inputMessage: &knownpb.Int32Value{},
+		inputText:    `42`,
+		wantMessage:  &knownpb.Int32Value{Value: 42},
+	}, {
+		desc:         "Int32Value in JSON string",
+		inputMessage: &knownpb.Int32Value{},
+		inputText:    `"1.23e3"`,
+		wantMessage:  &knownpb.Int32Value{Value: 1230},
+	}, {
+		desc:         "Int64Value",
+		inputMessage: &knownpb.Int64Value{},
+		inputText:    `"42"`,
+		wantMessage:  &knownpb.Int64Value{Value: 42},
+	}, {
+		desc:         "UInt32Value",
+		inputMessage: &knownpb.UInt32Value{},
+		inputText:    `42`,
+		wantMessage:  &knownpb.UInt32Value{Value: 42},
+	}, {
+		desc:         "UInt64Value",
+		inputMessage: &knownpb.UInt64Value{},
+		inputText:    `"42"`,
+		wantMessage:  &knownpb.UInt64Value{Value: 42},
+	}, {
+		desc:         "FloatValue",
+		inputMessage: &knownpb.FloatValue{},
+		inputText:    `1.02`,
+		wantMessage:  &knownpb.FloatValue{Value: 1.02},
+	}, {
+		desc:         "FloatValue exceeds max limit",
+		inputMessage: &knownpb.FloatValue{},
+		inputText:    `1.23+40`,
+		wantErr:      true,
+	}, {
+		desc:         "FloatValue Infinity",
+		inputMessage: &knownpb.FloatValue{},
+		inputText:    `"-Infinity"`,
+		wantMessage:  &knownpb.FloatValue{Value: float32(math.Inf(-1))},
+	}, {
+		desc:         "DoubleValue",
+		inputMessage: &knownpb.DoubleValue{},
+		inputText:    `1.02`,
+		wantMessage:  &knownpb.DoubleValue{Value: 1.02},
+	}, {
+		desc:         "DoubleValue Infinity",
+		inputMessage: &knownpb.DoubleValue{},
+		inputText:    `"Infinity"`,
+		wantMessage:  &knownpb.DoubleValue{Value: math.Inf(+1)},
+	}, {
+		desc:         "StringValue empty",
+		inputMessage: &knownpb.StringValue{},
+		inputText:    `""`,
+		wantMessage:  &knownpb.StringValue{},
+	}, {
+		desc:         "StringValue",
+		inputMessage: &knownpb.StringValue{},
+		inputText:    `"谷歌"`,
+		wantMessage:  &knownpb.StringValue{Value: "谷歌"},
+	}, {
+		desc:         "StringValue with invalid UTF8 error",
+		inputMessage: &knownpb.StringValue{},
+		inputText:    "\"abc\xff\"",
+		wantMessage:  &knownpb.StringValue{Value: "abc\xff"},
+		wantErr:      true,
+	}, {
+		desc:         "StringValue field with invalid UTF8 error",
+		inputMessage: &pb2.KnownTypes{},
+		inputText:    "{\n  \"optString\": \"abc\xff\"\n}",
+		wantMessage: &pb2.KnownTypes{
+			OptString: &knownpb.StringValue{Value: "abc\xff"},
+		},
+		wantErr: true,
+	}, {
+		desc:         "BytesValue",
+		inputMessage: &knownpb.BytesValue{},
+		inputText:    `"aGVsbG8="`,
+		wantMessage:  &knownpb.BytesValue{Value: []byte("hello")},
+	}, {
+		desc:         "Value null",
+		inputMessage: &knownpb.Value{},
+		inputText:    `null`,
+		wantMessage:  &knownpb.Value{Kind: &knownpb.Value_NullValue{}},
+	}, {
+		desc:         "Value field null",
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `{
+  "optValue": null
+}`,
+		wantMessage: &pb2.KnownTypes{
+			OptValue: &knownpb.Value{Kind: &knownpb.Value_NullValue{}},
+		},
+	}, {
+		desc:         "Value bool",
+		inputMessage: &knownpb.Value{},
+		inputText:    `false`,
+		wantMessage:  &knownpb.Value{Kind: &knownpb.Value_BoolValue{}},
+	}, {
+		desc:         "Value field bool",
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `{
+  "optValue": true
+}`,
+		wantMessage: &pb2.KnownTypes{
+			OptValue: &knownpb.Value{Kind: &knownpb.Value_BoolValue{true}},
+		},
+	}, {
+		desc:         "Value number",
+		inputMessage: &knownpb.Value{},
+		inputText:    `1.02`,
+		wantMessage:  &knownpb.Value{Kind: &knownpb.Value_NumberValue{1.02}},
+	}, {
+		desc:         "Value field number",
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `{
+  "optValue": 1.02
+}`,
+		wantMessage: &pb2.KnownTypes{
+			OptValue: &knownpb.Value{Kind: &knownpb.Value_NumberValue{1.02}},
+		},
+	}, {
+		desc:         "Value string",
+		inputMessage: &knownpb.Value{},
+		inputText:    `"hello"`,
+		wantMessage:  &knownpb.Value{Kind: &knownpb.Value_StringValue{"hello"}},
+	}, {
+		desc:         "Value string with invalid UTF8",
+		inputMessage: &knownpb.Value{},
+		inputText:    "\"\xff\"",
+		wantMessage:  &knownpb.Value{Kind: &knownpb.Value_StringValue{"\xff"}},
+		wantErr:      true,
+	}, {
+		desc:         "Value field string",
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `{
+  "optValue": "NaN"
+}`,
+		wantMessage: &pb2.KnownTypes{
+			OptValue: &knownpb.Value{Kind: &knownpb.Value_StringValue{"NaN"}},
+		},
+	}, {
+		desc:         "Value field string with invalid UTF8",
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `{
+  "optValue": "` + "\xff" + `"
+}`,
+		wantMessage: &pb2.KnownTypes{
+			OptValue: &knownpb.Value{Kind: &knownpb.Value_StringValue{"\xff"}},
+		},
+		wantErr: true,
+	}, {
+		desc:         "Value empty struct",
+		inputMessage: &knownpb.Value{},
+		inputText:    `{}`,
+		wantMessage: &knownpb.Value{
+			Kind: &knownpb.Value_StructValue{
+				&knownpb.Struct{Fields: map[string]*knownpb.Value{}},
+			},
+		},
+	}, {
+		desc:         "Value struct",
+		inputMessage: &knownpb.Value{},
+		inputText: `{
+  "string": "hello",
+  "number": 123,
+  "null": null,
+  "bool": false,
+  "struct": {
+    "string": "world"
+  },
+  "list": []
+}`,
+		wantMessage: &knownpb.Value{
+			Kind: &knownpb.Value_StructValue{
+				&knownpb.Struct{
+					Fields: map[string]*knownpb.Value{
+						"string": {Kind: &knownpb.Value_StringValue{"hello"}},
+						"number": {Kind: &knownpb.Value_NumberValue{123}},
+						"null":   {Kind: &knownpb.Value_NullValue{}},
+						"bool":   {Kind: &knownpb.Value_BoolValue{false}},
+						"struct": {
+							Kind: &knownpb.Value_StructValue{
+								&knownpb.Struct{
+									Fields: map[string]*knownpb.Value{
+										"string": {Kind: &knownpb.Value_StringValue{"world"}},
+									},
+								},
+							},
+						},
+						"list": {
+							Kind: &knownpb.Value_ListValue{&knownpb.ListValue{}},
+						},
+					},
+				},
+			},
+		},
+	}, {
+		desc:         "Value struct with invalid UTF8 string",
+		inputMessage: &knownpb.Value{},
+		inputText:    "{\"string\": \"abc\xff\"}",
+		wantMessage: &knownpb.Value{
+			Kind: &knownpb.Value_StructValue{
+				&knownpb.Struct{
+					Fields: map[string]*knownpb.Value{
+						"string": {Kind: &knownpb.Value_StringValue{"abc\xff"}},
+					},
+				},
+			},
+		},
+		wantErr: true,
+	}, {
+		desc:         "Value field struct",
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `{
+  "optValue": {
+    "string": "hello"
+  }
+}`,
+		wantMessage: &pb2.KnownTypes{
+			OptValue: &knownpb.Value{
+				Kind: &knownpb.Value_StructValue{
+					&knownpb.Struct{
+						Fields: map[string]*knownpb.Value{
+							"string": {Kind: &knownpb.Value_StringValue{"hello"}},
+						},
+					},
+				},
+			},
+		},
+	}, {
+		desc:         "Value empty list",
+		inputMessage: &knownpb.Value{},
+		inputText:    `[]`,
+		wantMessage: &knownpb.Value{
+			Kind: &knownpb.Value_ListValue{
+				&knownpb.ListValue{Values: []*knownpb.Value{}},
+			},
+		},
+	}, {
+		desc:         "Value list",
+		inputMessage: &knownpb.Value{},
+		inputText: `[
+  "string",
+  123,
+  null,
+  true,
+  {},
+  [
+    "string",
+	1.23,
+	null,
+	false
+  ]
+]`,
+		wantMessage: &knownpb.Value{
+			Kind: &knownpb.Value_ListValue{
+				&knownpb.ListValue{
+					Values: []*knownpb.Value{
+						{Kind: &knownpb.Value_StringValue{"string"}},
+						{Kind: &knownpb.Value_NumberValue{123}},
+						{Kind: &knownpb.Value_NullValue{}},
+						{Kind: &knownpb.Value_BoolValue{true}},
+						{Kind: &knownpb.Value_StructValue{&knownpb.Struct{}}},
+						{
+							Kind: &knownpb.Value_ListValue{
+								&knownpb.ListValue{
+									Values: []*knownpb.Value{
+										{Kind: &knownpb.Value_StringValue{"string"}},
+										{Kind: &knownpb.Value_NumberValue{1.23}},
+										{Kind: &knownpb.Value_NullValue{}},
+										{Kind: &knownpb.Value_BoolValue{false}},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}, {
+		desc:         "Value list with invalid UTF8 string",
+		inputMessage: &knownpb.Value{},
+		inputText:    "[\"abc\xff\"]",
+		wantMessage: &knownpb.Value{
+			Kind: &knownpb.Value_ListValue{
+				&knownpb.ListValue{
+					Values: []*knownpb.Value{
+						{Kind: &knownpb.Value_StringValue{"abc\xff"}},
+					},
+				},
+			},
+		},
+		wantErr: true,
+	}, {
+		desc:         "Value field list with invalid UTF8 string",
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `{
+  "optValue": [ "` + "abc\xff" + `"]
+}`,
+		wantMessage: &pb2.KnownTypes{
+			OptValue: &knownpb.Value{
+				Kind: &knownpb.Value_ListValue{
+					&knownpb.ListValue{
+						Values: []*knownpb.Value{
+							{Kind: &knownpb.Value_StringValue{"abc\xff"}},
+						},
+					},
+				},
+			},
+		},
+		wantErr: true,
+	}, {
+		desc:         "FieldMask empty",
+		inputMessage: &knownpb.FieldMask{},
+		inputText:    `""`,
+		wantMessage:  &knownpb.FieldMask{Paths: []string{}},
+	}, {
+		desc:         "FieldMask",
+		inputMessage: &knownpb.FieldMask{},
+		inputText:    `"foo,fooBar , foo.barQux ,Foo"`,
+		wantMessage: &knownpb.FieldMask{
+			Paths: []string{
+				"foo",
+				"foo_bar",
+				"foo.bar_qux",
+				"_foo",
+			},
+		},
+	}, {
+		desc:         "FieldMask field",
+		inputMessage: &pb2.KnownTypes{},
+		inputText: `{
+  "optFieldmask": "foo, qux.fooBar"
+}`,
+		wantMessage: &pb2.KnownTypes{
+			OptFieldmask: &knownpb.FieldMask{
+				Paths: []string{
+					"foo",
+					"qux.foo_bar",
+				},
+			},
+		},
 	}}
 
 	for _, tt := range tests {