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

Fix node id marshalling for custom string types #124

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
24 changes: 22 additions & 2 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,9 +542,29 @@ func fullNode(n *Node, included *map[string]*Node) *Node {
// assign will take the value specified and assign it to the field; if
// field is expecting a ptr assign will assign a ptr.
func assign(field, value reflect.Value) {
if field.Kind() == reflect.Ptr {
if value.Type().AssignableTo(field.Type()) {
field.Set(value)
return
}

indirectValue := reflect.Indirect(value)
if indirectValue.Type().AssignableTo(field.Type()) {
field.Set(indirectValue)
return
}

// Conversion required.

if field.Kind() == reflect.Ptr {
if value.Kind() == reflect.Ptr {
field.Set(value.Convert(field.Type()))
} else {
// Because field is zero value, we cannot simply field.Elem().Set().
v := reflect.New(field.Type().Elem())
v.Elem().Set(value.Convert(field.Type().Elem()))
field.Set(v)
}
} else {
field.Set(reflect.Indirect(value))
field.Set(reflect.Indirect(value).Convert(field.Type()))
}
}
106 changes: 106 additions & 0 deletions request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,112 @@ func TestManyPayload_withLinks(t *testing.T) {
}
}

func TestUnmarshalPayloadIDTypeOfString(t *testing.T) {
t.Run("Unmarshal string to value type", func(t *testing.T) {
data := map[string]interface{}{
"data": map[string]interface{}{
"type": "books",
"id": "978-3-16-148410-0",
"attributes": map[string]interface{}{
"title": "Gesammelte Werke in deutscher Sprache",
},
},
}
b, err := json.Marshal(data)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

type (
IBSN string

Book struct {
ID IBSN `jsonapi:"primary,books"`
Title string `jsonapi:"attr,title"`
}
)

book := &Book{}
if err := UnmarshalPayload(bytes.NewReader(b), book); err != nil {
t.Fatalf("Unexpected error: %v", err)
}

expected := IBSN("978-3-16-148410-0")
if book.ID != expected {
t.Fatalf("Expected book id to be %v but got %v", expected, book.ID)
}
})

t.Run("Unmarshal string to ptr type", func(t *testing.T) {
data := map[string]interface{}{
"data": map[string]interface{}{
"type": "books",
"id": "978-3-16-148410-0",
"attributes": map[string]interface{}{
"title": "Gesammelte Werke in deutscher Sprache",
},
},
}
b, err := json.Marshal(data)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

type (
IBSN string

Book struct {
ID *IBSN `jsonapi:"primary,books"`
Title string `jsonapi:"attr,title"`
}
)

book := &Book{}
if err := UnmarshalPayload(bytes.NewReader(b), book); err != nil {
t.Fatalf("Unexpected error: %v", err)
}

expected := IBSN("978-3-16-148410-0")
if !reflect.DeepEqual(book.ID, &expected) {
t.Fatalf("Expected book id to be %v but got %v", &expected, book.ID)
}
})

t.Run("Unmarshal nil to ptr type", func(t *testing.T) {
data := map[string]interface{}{
"data": map[string]interface{}{
"type": "books",
"id": nil,
"attributes": map[string]interface{}{
"title": "Gesammelte Werke in deutscher Sprache",
},
},
}
b, err := json.Marshal(data)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

type (
IBSN string

Book struct {
ID *IBSN `jsonapi:"primary,books"`
Title string `jsonapi:"attr,title"`
}
)

book := &Book{}
if err := UnmarshalPayload(bytes.NewReader(b), book); err != nil {
t.Fatalf("Unexpected error: %v", err)
}

if book.ID != nil {
t.Fatalf("Expected book id to be %v but got %v", nil, book.ID)
}
})
}

func samplePayloadWithoutIncluded() map[string]interface{} {
return map[string]interface{}{
"data": map[string]interface{}{
Expand Down
2 changes: 1 addition & 1 deletion response.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ func visitModelNode(model interface{}, included *map[string]*Node,
// Handle allowed types
switch kind {
case reflect.String:
node.ID = v.Interface().(string)
node.ID = v.String()
case reflect.Int:
node.ID = strconv.FormatInt(int64(v.Interface().(int)), 10)
case reflect.Int8:
Expand Down
32 changes: 32 additions & 0 deletions response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,38 @@ func TestMarshalIDPtr(t *testing.T) {
}
}

func TestMarshalIDTypeOfString(t *testing.T) {
type (
IBSN string

Book struct {
ID IBSN `jsonapi:"primary,books"`
}
)

book := &Book{ID: IBSN("978-3-16-148410-0")}

out := &bytes.Buffer{}
if err := MarshalPayload(out, book); err != nil {
t.Fatal(err)
}

var payload map[string]interface{}
if err := json.Unmarshal(out.Bytes(), &payload); err != nil {
t.Fatal(err)
}

data := payload["data"].(map[string]interface{})
id, ok := data["id"]
if !ok {
t.Fatal("Was expecting the data.id value to exist")
}

if id != "978-3-16-148410-0" {
t.Fatalf("Was expecting the data.id value to be %s but got %s", "978-3-16-148410-0", id)
}
}

func TestMarshalOnePayload_omitIDString(t *testing.T) {
type Foo struct {
ID string `jsonapi:"primary,foo"`
Expand Down