Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show type for ErrUnsupportedPtrType #2

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,24 @@ var (
// (numeric) but the Struct field was a non numeric type (i.e. not int, uint,
// float, etc)
ErrUnknownFieldNumberType = errors.New("The struct field was not of a known number type")
// ErrUnsupportedPtrType is returned when the Struct field was a pointer but
// the JSON value was of a different type
ErrUnsupportedPtrType = errors.New("Pointer type in struct is not supported")
// ErrInvalidType is returned when the given type is incompatible with the expected type.
ErrInvalidType = errors.New("Invalid type provided") // I wish we used punctuation.
)

// ErrUnsupportedPtrType is returned when the Struct field was a pointer but
// the JSON value was of a different type
func ErrUnsupportedPtrType(rf reflect.Value, t reflect.Type, structField reflect.StructField) error {
typeName := t.Elem().Name()
kind := t.Elem().Kind()
if kind.String() != "" && kind.String() != typeName {
typeName = fmt.Sprintf("%s (%s)", typeName, kind.String())
}
return fmt.Errorf(
"jsonapi: Can't unmarshal %+v (%s) to struct field `%s`, which is a pointer to `%s`",
rf, rf.Type().Kind(), structField.Name, typeName,
)
}

// UnmarshalPayload converts an io into a struct instance using jsonapi tags on
// struct fields. This method supports single request payloads only, at the
// moment. Bulk creates and updates are not supported yet.
Expand Down Expand Up @@ -256,7 +267,8 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
continue
}

value, err := unmarshalAttribute(attribute, args, fieldType.Type, fieldValue)
structField := fieldType
value, err := unmarshalAttribute(attribute, args, structField, fieldValue)
if err != nil {
er = err
break
Expand Down Expand Up @@ -363,9 +375,10 @@ func assign(field, value reflect.Value) {
}
}

func unmarshalAttribute(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value) (value reflect.Value, err error) {
func unmarshalAttribute(attribute interface{}, args []string, structField reflect.StructField, fieldValue reflect.Value) (value reflect.Value, err error) {

value = reflect.ValueOf(attribute)
fieldType := structField.Type

// Handle field of type []string
if fieldValue.Type() == reflect.TypeOf([]string{}) {
Expand Down Expand Up @@ -399,7 +412,7 @@ func unmarshalAttribute(attribute interface{}, args []string, fieldType reflect.

// Field was a Pointer type
if fieldValue.Kind() == reflect.Ptr {
value, err = handlePointer(attribute, args, fieldType, fieldValue)
value, err = handlePointer(attribute, args, fieldType, fieldValue, structField)
return
}

Expand Down Expand Up @@ -527,7 +540,7 @@ func handleNumeric(attribute interface{}, args []string, fieldType reflect.Type,
return numericValue, nil
}

func handlePointer(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value) (reflect.Value, error) {
func handlePointer(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value, structField reflect.StructField) (reflect.Value, error) {
t := fieldValue.Type()
var concreteVal reflect.Value

Expand All @@ -543,11 +556,11 @@ func handlePointer(attribute interface{}, args []string, fieldType reflect.Type,
case uintptr:
concreteVal = reflect.ValueOf(&cVal)
default:
return reflect.Value{}, ErrUnsupportedPtrType
return reflect.Value{}, ErrUnsupportedPtrType(reflect.ValueOf(attribute), fieldType, structField)
}

if t != concreteVal.Type() {
return reflect.Value{}, ErrUnsupportedPtrType
return reflect.Value{}, ErrUnsupportedPtrType(reflect.ValueOf(attribute), fieldType, structField)
}

return concreteVal, nil
Expand Down
57 changes: 55 additions & 2 deletions request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,65 @@ func TestUnmarshalPayloadWithPointerAttr_AbsentVal(t *testing.T) {
}
}

func TestUnmarshalToStructWithPointerAttr_BadType(t *testing.T) {
func TestUnmarshalToStructWithPointerAttr_BadType_bool(t *testing.T) {
out := new(WithPointer)
in := map[string]interface{}{
"name": true, // This is the wrong type.
}
expectedErrorMessage := ErrUnsupportedPtrType.Error()
expectedErrorMessage := "jsonapi: Can't unmarshal true (bool) to struct field `Name`, which is a pointer to `string`"

err := UnmarshalPayload(sampleWithPointerPayload(in), out)

if err == nil {
t.Fatalf("Expected error due to invalid type.")
}
if err.Error() != expectedErrorMessage {
t.Fatalf("Unexpected error message: %s", err.Error())
}
}

func TestUnmarshalToStructWithPointerAttr_BadType_MapPtr(t *testing.T) {
out := new(WithPointer)
in := map[string]interface{}{
"name": &map[string]interface{}{"a": 5}, // This is the wrong type.
}
expectedErrorMessage := "jsonapi: Can't unmarshal map[a:5] (map) to struct field `Name`, which is a pointer to `string`"

err := UnmarshalPayload(sampleWithPointerPayload(in), out)

if err == nil {
t.Fatalf("Expected error due to invalid type.")
}
if err.Error() != expectedErrorMessage {
t.Fatalf("Unexpected error message: %s", err.Error())
}
}

func TestUnmarshalToStructWithPointerAttr_BadType_Struct(t *testing.T) {
out := new(WithPointer)
type FooStruct struct{ A int }
in := map[string]interface{}{
"name": FooStruct{A: 5}, // This is the wrong type.
}
expectedErrorMessage := "jsonapi: Can't unmarshal map[A:5] (map) to struct field `Name`, which is a pointer to `string`"

err := UnmarshalPayload(sampleWithPointerPayload(in), out)

if err == nil {
t.Fatalf("Expected error due to invalid type.")
}
if err.Error() != expectedErrorMessage {
t.Fatalf("Unexpected error message: %s", err.Error())
}
}

func TestUnmarshalToStructWithPointerAttr_BadType_IntSlice(t *testing.T) {
out := new(WithPointer)
type FooStruct struct{ A, B int }
in := map[string]interface{}{
"name": []int{4, 5}, // This is the wrong type.
}
expectedErrorMessage := "jsonapi: Can't unmarshal [4 5] (slice) to struct field `Name`, which is a pointer to `string`"

err := UnmarshalPayload(sampleWithPointerPayload(in), out)

Expand Down