From 980176b258942d7730607c82a6690ce38c537084 Mon Sep 17 00:00:00 2001 From: Peter Grant Date: Fri, 19 Feb 2016 10:51:40 -0800 Subject: [PATCH 1/3] Test cases for empty vs nil list handling --- list_test.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/list_test.go b/list_test.go index 49c018b..c1fe07e 100644 --- a/list_test.go +++ b/list_test.go @@ -51,6 +51,41 @@ func TestList(t *testing.T) { }) }) + Convey("->Send(empty list)", func() { + + Convey("should send a properly formatted empty List response", func() { + + writer := httptest.NewRecorder() + err := Send(writer, req, List([]*Object{})) + So(err, ShouldBeNil) + So(writer.Code, ShouldEqual, http.StatusOK) + + contentLength, convErr := strconv.Atoi(writer.HeaderMap.Get("Content-Length")) + So(convErr, ShouldBeNil) + So(contentLength, ShouldBeGreaterThan, 0) + So(writer.HeaderMap.Get("Content-Type"), ShouldEqual, ContentType) + + req, reqErr := testRequest(writer.Body.Bytes()) + So(reqErr, ShouldBeNil) + + responseList, parseErr := ParseList(req) + So(parseErr, ShouldBeNil) + So(len(responseList), ShouldEqual, 0) + }) + }) + + Convey("->Send(nil list)", func() { + + Convey("should fail with a 500 ISE", func() { + + writer := httptest.NewRecorder() + var list []*Object + err := Send(writer, req, List(list)) + So(err, ShouldNotBeNil) + So(writer.Code, ShouldEqual, http.StatusInternalServerError) + }) + }) + Convey("->UnmarshalJSON()", func() { Convey("should handle a data object", func() { From f87157aecd947c2b9f8e71641bf5bc1d42603352 Mon Sep 17 00:00:00 2001 From: Peter Grant Date: Fri, 19 Feb 2016 10:32:33 -0800 Subject: [PATCH 2/3] Serialize empty data as empty array --- document.go | 6 +++--- list.go | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/document.go b/document.go index fbaa21f..642a451 100644 --- a/document.go +++ b/document.go @@ -12,7 +12,7 @@ Refer to the JSON API Specification for a full descriptor of each attribute: http://jsonapi.org/format/#document-structure */ type Document struct { - Data List `json:"data,omitempty"` + Data List `json:"data"` Errors ErrorList `json:"errors,omitempty"` Links *Link `json:"links,omitempty"` Included []*Object `json:"included,omitempty"` @@ -89,10 +89,10 @@ func (d *Document) Validate(r *http.Request, response bool) *Error { return nil } - if !d.HasErrors() && !d.HasData() { + if !d.HasErrors() && d.Data == nil { return ISE("Both `errors` and `data` cannot be blank for a JSON response") } - if d.HasErrors() && d.HasData() { + if d.HasErrors() && d.Data != nil { return ISE("Both `errors` and `data` cannot be set for a JSON response") } if !d.HasData() && d.Included != nil { diff --git a/list.go b/list.go index 2070ba4..4938d57 100644 --- a/list.go +++ b/list.go @@ -55,15 +55,23 @@ func (list *List) UnmarshalJSON(rawData []byte) error { MarshalJSON returns a top level object for the "data" attribute if a single object. In all other cases returns a JSON encoded list for "data". */ -func (list List) MarshalJSON() ([]byte, error) { +func (list *List) MarshalJSON() ([]byte, error) { // avoid stack overflow by using this subtype for marshaling type MarshalList List - marshalList := MarshalList(list) - count := len(marshalList) + marshalList := (*MarshalList)(list) + isnil := marshalList == nil + count := 0 + if !isnil { + count = len(*marshalList) + } switch { + case isnil: + return nil, nil + case count == 0: + return []byte("[]"), nil case count == 1: - return json.Marshal(marshalList[0]) + return json.Marshal((*marshalList)[0]) default: return json.Marshal(marshalList) } From 25be454751bd62cd7f6d4369b605e6164cd23bae Mon Sep 17 00:00:00 2001 From: Peter Grant Date: Mon, 22 Feb 2016 11:29:03 -0800 Subject: [PATCH 3/3] Address code review comments --- list.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/list.go b/list.go index 4938d57..3d72bea 100644 --- a/list.go +++ b/list.go @@ -53,25 +53,25 @@ func (list *List) UnmarshalJSON(rawData []byte) error { /* MarshalJSON returns a top level object for the "data" attribute if a single object. In -all other cases returns a JSON encoded list for "data". +all other cases returns a JSON encoded list for "data". We use a pointer receiver here +so we are able to distinguish between nil (don't serialize) and empty (serialize as []). */ func (list *List) MarshalJSON() ([]byte, error) { // avoid stack overflow by using this subtype for marshaling type MarshalList List - marshalList := (*MarshalList)(list) - isnil := marshalList == nil - count := 0 - if !isnil { - count = len(*marshalList) + + if list == nil { + return nil, nil } + marshalList := MarshalList(*list) + count := len(marshalList) + switch { - case isnil: - return nil, nil case count == 0: return []byte("[]"), nil case count == 1: - return json.Marshal((*marshalList)[0]) + return json.Marshal(marshalList[0]) default: return json.Marshal(marshalList) }