Skip to content

Commit

Permalink
Marshalling: improve error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
blgm committed May 4, 2020
1 parent 338f7c5 commit e6ca9a8
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 12 deletions.
30 changes: 30 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package jsonry

import (
"fmt"
"reflect"
"strings"
)

type context []reflect.StructField

func (ctx context) String() string {
t := ctx.tail()
return fmt.Sprintf("field '%s' of type '%s' at path '%s'", t.Name, t.Type, ctx.path())
}

func (ctx context) withField(f reflect.StructField) context {
return append(ctx, f)
}

func (ctx context) tail() reflect.StructField {
return ctx[len(ctx)-1]
}

func (ctx context) path() string {
var path []string
for _, e := range ctx {
path = append(path, e.Name)
}
return strings.Join(path, ".")
}
10 changes: 7 additions & 3 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ import (

type UnsupportedType struct {
TypeName string
Context context
}

func NewUnsupportedTypeError(t reflect.Type) *UnsupportedType {
return &UnsupportedType{TypeName: t.String()}
func NewUnsupportedTypeError(ctx context, t reflect.Type) *UnsupportedType {
return &UnsupportedType{
Context: ctx,
TypeName: t.String(),
}
}

func (u *UnsupportedType) Error() string {
return fmt.Sprintf("unsupported type: %s", u.TypeName)
return fmt.Sprintf("unsupported type '%s' at %s", u.TypeName, u.Context)
}
17 changes: 10 additions & 7 deletions marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,38 @@ func Marshal(input interface{}) ([]byte, error) {
return nil, errors.New("the input must be a struct")
}

m, err := marshal(i)
m, err := marshal(context{}, i)
if err != nil {
return nil, err
}

return json.Marshal(m)
}

func marshal(in reflect.Value) (map[string]interface{}, error) {
func marshal(ctx context, in reflect.Value) (map[string]interface{}, error) {
out := make(map[string]interface{})

t := in.Type()
for i := 0; i < t.NumField(); i++ {
v := in.Field(i)
f := t.Field(i)
switch actual(in.Field(i)) {
ctx = ctx.withField(f)

switch actualMetakind(v) {
case metakindBasic:
out[f.Name] = in.Field(i).Interface()
out[f.Name] = v.Interface()
default:
return nil, NewUnsupportedTypeError(actualType(in.Field(i)))
return nil, NewUnsupportedTypeError(ctx, actualType(v))
}
}

return out, nil
}

func actual(v reflect.Value) metakind {
func actualMetakind(v reflect.Value) metakind {
switch v.Kind() {
case reflect.Interface:
return actual(v.Elem())
return actualMetakind(v.Elem())
case reflect.String, reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
Expand Down
6 changes: 4 additions & 2 deletions marshal_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package jsonry_test

import (
"fmt"

"code.cloudfoundry.org/jsonry"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
Expand Down Expand Up @@ -29,9 +31,9 @@ var _ = Describe("Marshal", func() {
"unsupported types",
func(input c, typeName string) {
_, err := jsonry.Marshal(input)
Expect(err).To(MatchError(ContainSubstring("unsupported type: %s", typeName)))
Expect(err).To(MatchError(fmt.Sprintf("unsupported type '%s' at field 'V' of type 'interface {}' at path 'V'", typeName)), err.Error())
},
Entry("complex", c{V: complex(1, 2)}, "complex"),
Entry("complex", c{V: complex(1, 2)}, "complex128"),
Entry("slice", c{V: []string{"hello"}}, "[]string"),
Entry("array", c{V: [1]string{"hello"}}, "[1]string"),
Entry("map", c{V: make(map[string]interface{})}, "map[string]interface {}"),
Expand Down

0 comments on commit e6ca9a8

Please sign in to comment.