diff --git a/request.go b/request.go index 88bf2ba..44a946f 100644 --- a/request.go +++ b/request.go @@ -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. @@ -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 @@ -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{}) { @@ -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 } @@ -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 @@ -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 diff --git a/request_test.go b/request_test.go index b0054aa..2d57136 100644 --- a/request_test.go +++ b/request_test.go @@ -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)