diff --git a/request.go b/parse.go similarity index 64% rename from request.go rename to parse.go index 32329cf..fa86c74 100644 --- a/request.go +++ b/parse.go @@ -2,6 +2,7 @@ package japi import ( "encoding/json" + "fmt" "io" "io/ioutil" ) @@ -21,16 +22,19 @@ func ParseObject(reader io.ReadCloser) (*Object, error) { return nil, err } - request := struct { - Object *Object `json:"data"` + data := struct { + Object Object `json:"data"` }{} - err = json.Unmarshal(byteData, request) + err = json.Unmarshal(byteData, &data) if err != nil { - return nil, err + return nil, fmt.Errorf("Unable to parse json: \n%s\nError:%s", + string(byteData), + err.Error(), + ) } - return request.Object, nil + return &data.Object, nil } // ParseList returns a JSON List for a given io.ReadCloser containing @@ -43,14 +47,17 @@ func ParseList(reader io.ReadCloser) ([]*Object, error) { return nil, err } - request := struct { + data := struct { List []*Object `json:"data"` - }{} + }{List: []*Object{}} - err = json.Unmarshal(byteData, request) + err = json.Unmarshal(byteData, &data) if err != nil { - return nil, err + return nil, fmt.Errorf("Unable to parse json: \n%s\nError:%s", + string(byteData), + err.Error(), + ) } - return request.List, nil + return data.List, nil } diff --git a/parse_test.go b/parse_test.go new file mode 100644 index 0000000..401d46a --- /dev/null +++ b/parse_test.go @@ -0,0 +1,53 @@ +package japi + +import ( + "bytes" + "io" + "io/ioutil" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestRequest(t *testing.T) { + + Convey("Request Tests", t, func() { + + Convey("->ParseObject()", func() { + objectJSON := `{"data": {"type": "user", "id": "sweetID123", "attributes": {"ID":"123"}}}` + + closer := createIOCloser([]byte(objectJSON)) + + object, err := ParseObject(closer) + So(err, ShouldBeNil) + So(object, ShouldNotBeEmpty) + So(object.Type, ShouldEqual, "user") + So(object.ID, ShouldEqual, "sweetID123") + So(object.Attributes, ShouldResemble, map[string]interface{}{"ID": "123"}) + }) + + Convey("->ParseList()", func() { + listJSON := + `{"data": [ + {"type": "user", "id": "sweetID123", "attributes": {"ID": "123"}}, + {"type": "user", "id": "sweetID456", "attributes": {"ID": "456"}} +]}` + + closer := createIOCloser([]byte(listJSON)) + + list, err := ParseList(closer) + So(err, ShouldBeNil) + So(len(list), ShouldEqual, 2) + + object := list[1] + So(object.Type, ShouldEqual, "user") + So(object.ID, ShouldEqual, "sweetID456") + So(object.Attributes, ShouldResemble, map[string]interface{}{"ID": "456"}) + }) + }) +} + +func createIOCloser(data []byte) io.ReadCloser { + reader := bytes.NewReader(data) + return ioutil.NopCloser(reader) +} diff --git a/request_test.go b/request_test.go index 307d699..e6db758 100644 --- a/request_test.go +++ b/request_test.go @@ -9,23 +9,41 @@ import ( . "github.com/smartystreets/goconvey/convey" ) -func TestRequest(t *testing.T) { +func TestParsing(t *testing.T) { - Convey("Request Tests", t, func() { + Convey("Parse Tests", t, func() { Convey("->ParseObject()", func() { - jsonStr := `{"data": {"type": "user", "id": "sweetID123", "attributes": {"ID":"123"}}}` + objectJSON := `{"data": {"type": "user", "id": "sweetID123", "attributes": {"ID":"123"}}}` - closer := createIOCloser([]byte(jsonStr)) + closer := createIOCloser([]byte(objectJSON)) object, err := ParseObject(closer) So(err, ShouldBeNil) So(object, ShouldNotBeEmpty) - So(err, ShouldBeNil) So(object.Type, ShouldEqual, "user") So(object.ID, ShouldEqual, "sweetID123") So(object.Attributes, ShouldResemble, map[string]interface{}{"ID": "123"}) }) + + Convey("->ParseList()", func() { + listJSON := + `{"data": [ + {"type": "user", "id": "sweetID123", "attributes": {"ID": "123"}}, + {"type": "user", "id": "sweetID456", "attributes": {"ID": "456"}} +]}` + + closer := createIOCloser([]byte(listJSON)) + + list, err := ParseList(closer) + So(err, ShouldBeNil) + So(len(list), ShouldEqual, 2) + + object := list[1] + So(object.Type, ShouldEqual, "user") + So(object.ID, ShouldEqual, "sweetID456") + So(object.Attributes, ShouldResemble, map[string]interface{}{"ID": "456"}) + }) }) } diff --git a/response.go b/response.go deleted file mode 100644 index b8beba0..0000000 --- a/response.go +++ /dev/null @@ -1,84 +0,0 @@ -package japi - -import ( - "encoding/json" - "fmt" - "net/http" - "strconv" -) - -type DataResponse struct { - Data interface{} `json:"data"` -} - -// ErrorResponse for API requests -type ErrorResponse struct { - Errors []*Error `json:"errors"` -} - -// RespondWithObject sends a single data object as a JSON response -func RespondWithObject(w http.ResponseWriter, status int, object *Object) error { - payload := struct { - Data *Object `json:"data"` - }{object} - - return respond(w, status, payload) -} - -// RespondWithList sends a list of data objects as a JSON response -func RespondWithList(w http.ResponseWriter, status int, list []*Object) error { - payload := struct { - Data []*Object `json:"data"` - }{list} - return respond(w, status, payload) -} - -// RespondWithError is a convenience function that puts an error into an array -// and then calls RespondWithErrors which is the correct error response format -func RespondWithError(w http.ResponseWriter, err *Error) error { - errors := []*Error{err} - return RespondWithErrors(w, errors) -} - -// RespondWithErrors sends the expected error response format for a -// request that cannot be fulfilled in someway. Allows the user -// to compile multiple errors that can be sent back to a user. Uses -// the first error status as the HTTP Status to return. -func RespondWithErrors(w http.ResponseWriter, errors []*Error) error { - - if len(errors) == 0 { - return fmt.Errorf("No errors provided for attempted error response.") - } - - // use the first error to set the error status - status := errors[0].Status - payload := ErrorResponse{errors} - return respond(w, status, payload) -} - -// Respond formats a JSON response with the appropriate headers. -func respond(w http.ResponseWriter, status int, payload interface{}) error { - content, err := json.MarshalIndent(payload, "", " ") - if err != nil { - return err - } - - w.Header().Add("Content-Type", ContentType) - w.Header().Set("Content-Length", strconv.Itoa(len(content))) - w.WriteHeader(status) - w.Write(content) - - return nil -} - -func prepareError(error *Error) *ErrorResponse { - return &ErrorResponse{} -} - -func prepareObject(object *Object) *DataResponse { - return &DataResponse{object} -} - -func prepareList(list []*Object) *DataResponse { - return &DataResponse{list} -} diff --git a/send.go b/send.go new file mode 100644 index 0000000..c4df366 --- /dev/null +++ b/send.go @@ -0,0 +1,81 @@ +package japi + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" +) + +// Data represents the top level json format of incoming requests +// and outgoing responses +type Data struct { + Data interface{} `json:"data"` +} + +// ErrorResponse for API requests +type ErrorResponse struct { + Errors []*Error `json:"errors"` +} + +// SendObject sends a single data object as a JSON response +func SendObject(w http.ResponseWriter, status int, object *Object) error { + return Send(w, status, prepareObject(object)) +} + +// SendList sends a list of data objects as a JSON response +func SendList(w http.ResponseWriter, status int, list []*Object) error { + return Send(w, status, prepareList(list)) +} + +// SendError is a convenience function that puts an error into an array +// and then calls SendErrors which is the correct error response format +func SendError(w http.ResponseWriter, err *Error) error { + return SendErrors(w, prepareError(err)) +} + +// SendErrors sends the expected error response format for a +// request that cannot be fulfilled in someway. Allows the user +// to compile multiple errors that can be sent back to a user. Uses +// the first error status as the HTTP Status to return. +func SendErrors(w http.ResponseWriter, errors []*Error) error { + + if len(errors) == 0 { + return fmt.Errorf("No errors provided for attempted error response.") + } + + // use the first error to set the error status + status := errors[0].Status + return Send(w, status, prepareErrorList(errors)) +} + +// Send formats a JSON response with the appropriate headers. +func Send(w http.ResponseWriter, status int, payload interface{}) error { + content, err := json.MarshalIndent(payload, "", " ") + if err != nil { + return err + } + + w.Header().Add("Content-Type", ContentType) + w.Header().Set("Content-Length", strconv.Itoa(len(content))) + w.WriteHeader(status) + w.Write(content) + + return nil +} + +func prepareError(err *Error) []*Error { + return []*Error{err} +} + +func prepareErrorList(errors []*Error) *ErrorResponse { + return &ErrorResponse{Errors: errors} +} + +func prepareObject(object *Object) *Data { + return &Data{Data: object} +} + +func prepareList(list []*Object) *Data { + return &Data{Data: list} +} diff --git a/send_test.go b/send_test.go new file mode 100644 index 0000000..b295974 --- /dev/null +++ b/send_test.go @@ -0,0 +1,24 @@ +package japi + +import ( + "net/http/httptest" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestSend(t *testing.T) { + + Convey("Send Tests", t, func() { + + response := httptest.NewRecorder() + + Convey("->SendObject()", func() { + + }) + + Convey("->SendList()", func() { + + }) + }) +}