Skip to content

Commit 42d8fe0

Browse files
author
Michal Witkowski
committed
Add nicejsonpb error handling (against dev)
1 parent d3afd40 commit 42d8fe0

File tree

4 files changed

+178
-16
lines changed

4 files changed

+178
-16
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@ _obj
1313
_test
1414
_testmain.go
1515

16+
# Ignore Gogland stuff
17+
.idea/
18+
1619
# Conformance test output and transient files.
1720
conformance/failing_tests.txt

jsonpb/errors.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Go support for Protocol Buffers - Google's data interchange format
2+
//
3+
// Copyright 2015 The Go Authors. All rights reserved.
4+
// https://github.com/golang/protobuf
5+
//
6+
// Redistribution and use in source and binary forms, with or without
7+
// modification, are permitted provided that the following conditions are
8+
// met:
9+
//
10+
// * Redistributions of source code must retain the above copyright
11+
// notice, this list of conditions and the following disclaimer.
12+
// * Redistributions in binary form must reproduce the above
13+
// copyright notice, this list of conditions and the following disclaimer
14+
// in the documentation and/or other materials provided with the
15+
// distribution.
16+
// * Neither the name of Google Inc. nor the names of its
17+
// contributors may be used to endorse or promote products derived from
18+
// this software without specific prior written permission.
19+
//
20+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
32+
package jsonpb
33+
34+
import (
35+
"encoding/json"
36+
"fmt"
37+
"reflect"
38+
"strings"
39+
40+
"github.com/golang/protobuf/proto"
41+
)
42+
43+
type fieldError struct {
44+
fieldStack []string
45+
nestedErr error
46+
}
47+
48+
func (f *fieldError) Error() string {
49+
return "unparsable field " + strings.Join(f.fieldStack, ".") + ": " + f.nestedErr.Error()
50+
}
51+
52+
func (f *fieldError) FieldStack() []string {
53+
return f.fieldStack
54+
}
55+
56+
func (f *fieldError) UnderlyingError() error {
57+
return f.nestedErr
58+
}
59+
60+
// newFieldError wraps a given error providing a message call stack.
61+
func newFieldError(fieldName string, err error) error {
62+
if fErr, ok := err.(*fieldError); ok {
63+
fErr.fieldStack = append([]string{fieldName}, fErr.fieldStack...)
64+
return err
65+
}
66+
return &fieldError{
67+
fieldStack: []string{fieldName},
68+
nestedErr: err,
69+
}
70+
}
71+
72+
// correctJsonType gets rid of the dredded json.RawMessage errors and casts them to the right type.
73+
func correctJsonType(err error, realType reflect.Type) error {
74+
if uErr, ok := err.(*json.UnmarshalTypeError); ok {
75+
uErr.Type = realType
76+
return uErr
77+
}
78+
return err
79+
}
80+
81+
func getFieldMismatchError(remainingFields map[string]json.RawMessage, structProps *proto.StructProperties) error {
82+
remaining := []string{}
83+
for k, _ := range remainingFields {
84+
remaining = append(remaining, k)
85+
}
86+
known := []string{}
87+
for _, prop := range structProps.Prop {
88+
jsonNames := acceptedJSONFieldNames(prop)
89+
if strings.HasPrefix(jsonNames.camel, "XXX_") {
90+
continue
91+
}
92+
known = append(known, jsonNames.camel)
93+
}
94+
return fmt.Errorf("fields %v do not exist in set of known fields %v", remaining, known)
95+
}

jsonpb/jsonpb.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,7 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
886886
if targetType.Kind() == reflect.Struct {
887887
var jsonFields map[string]json.RawMessage
888888
if err := json.Unmarshal(inputValue, &jsonFields); err != nil {
889-
return err
889+
return correctJsonType(err, targetType)
890890
}
891891

892892
consumeField := func(prop *proto.Properties) (json.RawMessage, bool) {
@@ -924,7 +924,7 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
924924
}
925925

926926
if err := u.unmarshalValue(target.Field(i), valueForField, sprops.Prop[i]); err != nil {
927-
return err
927+
return newFieldError(sprops.Prop[i].Name, err)
928928
}
929929
}
930930
// Check for any oneof fields.
@@ -937,7 +937,7 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
937937
nv := reflect.New(oop.Type.Elem())
938938
target.Field(oop.Field).Set(nv)
939939
if err := u.unmarshalValue(nv.Elem().Field(0), raw, oop.Prop); err != nil {
940-
return err
940+
return newFieldError(oop.Prop.Name, err)
941941
}
942942
}
943943
}
@@ -953,22 +953,16 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
953953
delete(jsonFields, name)
954954
nv := reflect.New(reflect.TypeOf(ext.ExtensionType).Elem())
955955
if err := u.unmarshalValue(nv.Elem(), raw, nil); err != nil {
956-
return err
956+
return newFieldError(name, err)
957957
}
958958
if err := proto.SetExtension(ep, ext, nv.Interface()); err != nil {
959-
return err
959+
return newFieldError(name, err)
960960
}
961961
}
962962
}
963963
}
964964
if !u.AllowUnknownFields && len(jsonFields) > 0 {
965-
// Pick any field to be the scapegoat.
966-
var f string
967-
for fname := range jsonFields {
968-
f = fname
969-
break
970-
}
971-
return fmt.Errorf("unknown field %q in %v", f, targetType)
965+
return getFieldMismatchError(jsonFields, sprops)
972966
}
973967
return nil
974968
}
@@ -977,14 +971,14 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
977971
if targetType.Kind() == reflect.Slice && targetType.Elem().Kind() != reflect.Uint8 {
978972
var slc []json.RawMessage
979973
if err := json.Unmarshal(inputValue, &slc); err != nil {
980-
return err
974+
return correctJsonType(err, targetType)
981975
}
982976
if slc != nil {
983977
l := len(slc)
984978
target.Set(reflect.MakeSlice(targetType, l, l))
985979
for i := 0; i < l; i++ {
986980
if err := u.unmarshalValue(target.Index(i), slc[i], prop); err != nil {
987-
return err
981+
return newFieldError(fmt.Sprintf("[%d]", i), err)
988982
}
989983
}
990984
}
@@ -999,6 +993,12 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
999993
}
1000994
if mp != nil {
1001995
target.Set(reflect.MakeMap(targetType))
996+
if prop != nil {
997+
// These could still be nil if the protobuf metadata is broken somehow.
998+
// TODO: This won't work because the fields are unexported.
999+
// We should probably just reparse them.
1000+
//keyprop, valprop = prop.mkeyprop, prop.mvalprop
1001+
}
10021002
for ks, raw := range mp {
10031003
// Unmarshal map key. The core json library already decoded the key into a
10041004
// string, so we handle that specially. Other types were quoted post-serialization.
@@ -1009,15 +1009,15 @@ func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMe
10091009
k = reflect.New(targetType.Key()).Elem()
10101010
// TODO: pass the correct Properties if needed.
10111011
if err := u.unmarshalValue(k, json.RawMessage(ks), nil); err != nil {
1012-
return err
1012+
return newFieldError(fmt.Sprintf("['%s']", ks), newFieldError("key", err))
10131013
}
10141014
}
10151015

10161016
// Unmarshal map value.
10171017
v := reflect.New(targetType.Elem()).Elem()
10181018
// TODO: pass the correct Properties if needed.
10191019
if err := u.unmarshalValue(v, raw, nil); err != nil {
1020-
return err
1020+
return newFieldError(fmt.Sprintf("['%s']", ks), newFieldError("value", err))
10211021
}
10221022
target.SetMapIndex(k, v)
10231023
}

jsonpb/jsonpb_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,3 +1148,67 @@ func TestUnmarshalUnsetRequiredFields(t *testing.T) {
11481148
}
11491149
}
11501150
}
1151+
1152+
1153+
func TestUnmarshalReturnsUsefulErrors(t *testing.T) {
1154+
for _, entry := range []struct {
1155+
name string
1156+
input string
1157+
msg proto.Message
1158+
expectedError string
1159+
}{
1160+
{
1161+
name: "FindsErrorsInNested",
1162+
input: `{"simple": {"oString": 3.1}}`,
1163+
msg: &pb.Widget{},
1164+
expectedError: `unparsable field Simple.OString: json: cannot unmarshal number into Go value of type string`,
1165+
},
1166+
{
1167+
name: "FindsErrorsInArrays",
1168+
input: `{"rInt32": [2,5,1,"sdas",2.1]}`,
1169+
msg: &pb.Repeats{},
1170+
expectedError: `unparsable field RInt32.[3]: json: cannot unmarshal string into Go value of type int32`,
1171+
},
1172+
{
1173+
name: "FindErrorsInMapKeys",
1174+
input: `{"mInt64Str": {"1": "something", "badKey": "foo"}}`,
1175+
msg: &pb.Maps{},
1176+
expectedError: `unparsable field MInt64Str.['badKey'].key: invalid character 'b' looking for beginning of value`,
1177+
},
1178+
{
1179+
name: "FindErrorsInMapValues",
1180+
input: `{"mInt64Str": {"1": "something", "2": 4.2}}`,
1181+
msg: &pb.Maps{},
1182+
expectedError: `unparsable field MInt64Str.['2'].value: json: cannot unmarshal number into Go value of type string`,
1183+
},
1184+
{
1185+
name: "HandlesGoodFormattingOfInt64AsString",
1186+
input: `{"oInt64": "should_be_int"}`,
1187+
msg: &pb.Simple{},
1188+
expectedError: `unparsable field OInt64: invalid character 's' looking for beginning of value`,
1189+
},
1190+
{
1191+
name: "RemapsRawMessageToRealArrayType",
1192+
input: `{"rInt32": "not_an_array"}`,
1193+
msg: &pb.Repeats{},
1194+
expectedError: `unparsable field RInt32: json: cannot unmarshal string into Go value of type []int32`,
1195+
},
1196+
{
1197+
name: "UnknownFieldErrors",
1198+
input: `{"simple": {"oString": "something", "unknownOne": 1, "unknownTwo": 21}}`,
1199+
msg: &pb.Widget{},
1200+
expectedError: `unparsable field Simple: fields [unknownOne unknownTwo] do not exist in set of known fields [oBool oInt32 oInt64 oUint32 oUint64 oSint32 oSint64 oFloat oDouble oString oBytes]`,
1201+
},
1202+
} {
1203+
u := Unmarshaler{AllowUnknownFields: false}
1204+
err := u.Unmarshal(strings.NewReader(entry.input), entry.msg)
1205+
if err == nil {
1206+
t.Errorf("in %v there should have been an error", entry.name)
1207+
continue
1208+
}
1209+
if err.Error() != entry.expectedError {
1210+
t.Errorf("in %v expected error \n'%v'\n but got \n'%v'", entry.name, entry.expectedError, err)
1211+
continue
1212+
}
1213+
}
1214+
}

0 commit comments

Comments
 (0)