Skip to content

Commit aa388db

Browse files
author
Derek Dowling
committed
Nailing down initial types, requests, responses
1 parent 978bf2e commit aa388db

File tree

6 files changed

+200
-0
lines changed

6 files changed

+200
-0
lines changed

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Go JSON API
2+
---
3+
4+
Go API helpers for achieving a [JSON API Specification](http://jsonapi.org/)
5+
compatible backend.
6+
7+
## Mission
8+
9+
To make dealing with JSON APIs in Go simple, consistent, and easy.
10+
11+
## Installation
12+
13+
```
14+
$ go get github.com/derekdowling/go-jsonapi
15+
```

error.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package japi
2+
3+
// Error represents a JSON Spec Error
4+
type Error struct {
5+
Title string `json:"title"`
6+
Detail string `json:"detail"`
7+
Status int `json:"status"`
8+
Source struct {
9+
Pointer string `json:"pointer"`
10+
} `json:"source"`
11+
}

object.go

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package japi
2+
3+
// Object represents the default JSON spec for objects
4+
type Object struct {
5+
Type string `json:"type"`
6+
ID string `json:"id"`
7+
Attributes interface{} `json:"attributes"`
8+
}

request.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package japi
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"io/ioutil"
7+
)
8+
9+
const (
10+
// ContentType is the data encoding of choice for HTTP Request and Response Headers
11+
ContentType = "application/vnd.api+json"
12+
)
13+
14+
// ParseObject returns a JSON object for a given io.ReadCloser containing
15+
// a raw JSON payload
16+
func ParseObject(reader io.ReadCloser) (*Object, error) {
17+
defer reader.Close()
18+
19+
byteData, err := ioutil.ReadAll(reader)
20+
if err != nil {
21+
return nil, err
22+
}
23+
24+
request := struct {
25+
Object *Object `json:"data"`
26+
}{}
27+
28+
err = json.Unmarshal(byteData, request)
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
return request.Object, nil
34+
}
35+
36+
// ParseList returns a JSON List for a given io.ReadCloser containing
37+
// a raw JSON payload
38+
func ParseList(reader io.ReadCloser) ([]*Object, error) {
39+
defer reader.Close()
40+
41+
byteData, err := ioutil.ReadAll(reader)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
request := struct {
47+
List []*Object `json:"data"`
48+
}{}
49+
50+
err = json.Unmarshal(byteData, request)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
return request.List, nil
56+
}

request_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package japi
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"io/ioutil"
7+
"testing"
8+
9+
. "github.com/smartystreets/goconvey/convey"
10+
)
11+
12+
func TestRequest(t *testing.T) {
13+
14+
Convey("Request Tests", t, func() {
15+
16+
Convey("->ParseObject()", func() {
17+
jsonStr := `{"data": {"type": "user", "id": "sweetID123", "attributes": {"ID":"123"}}}`
18+
19+
closer := createIOCloser([]byte(jsonStr))
20+
21+
object, err := ParseObject(closer)
22+
So(err, ShouldBeNil)
23+
So(object, ShouldNotBeEmpty)
24+
So(err, ShouldBeNil)
25+
So(object.Type, ShouldEqual, "user")
26+
So(object.ID, ShouldEqual, "sweetID123")
27+
So(object.Attributes, ShouldResemble, map[string]interface{}{"ID": "123"})
28+
})
29+
})
30+
}
31+
32+
func createIOCloser(data []byte) io.ReadCloser {
33+
reader := bytes.NewReader(data)
34+
return ioutil.NopCloser(reader)
35+
}

response.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package japi
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
)
8+
9+
type DataResponse struct {
10+
Data interface{} `json:"data"`
11+
}
12+
13+
// ErrorResponse for API requests
14+
type ErrorResponse struct {
15+
Errors []*Error `json:"errors"`
16+
}
17+
18+
// RespondWithObject sends a single data object as a JSON response
19+
func RespondWithObject(w http.ResponseWriter, status int, object *Object) error {
20+
payload := struct {
21+
Data *Object `json:"data"`
22+
}{object}
23+
24+
return respond(w, status, payload)
25+
}
26+
27+
// RespondWithList sends a list of data objects as a JSON response
28+
func RespondWithList(w http.ResponseWriter, status int, list []*Object) error {
29+
payload := struct {
30+
Data []*Object `json:"data"`
31+
}{list}
32+
return respond(w, status, payload)
33+
}
34+
35+
// RespondWithError is a convenience function that puts an error into an array
36+
// and then calls RespondWithErrors which is the correct error response format
37+
func RespondWithError(w http.ResponseWriter, err *Error) error {
38+
errors := []*Error{err}
39+
return RespondWithErrors(w, errors)
40+
}
41+
42+
// RespondWithErrors sends the expected error response format for a
43+
// request that cannot be fulfilled in someway. Allows the user
44+
// to compile multiple errors that can be sent back to a user. Uses
45+
// the first error status as the HTTP Status to return.
46+
func RespondWithErrors(w http.ResponseWriter, errors []*Error) error {
47+
48+
if len(errors) == 0 {
49+
return fmt.Errorf("No errors provided for attempted error response.")
50+
}
51+
52+
// use the first error to set the error status
53+
status := errors[0].Status
54+
payload := ErrorResponse{errors}
55+
return respond(w, status, payload)
56+
}
57+
58+
// Respond formats a JSON response with the appropriate headers.
59+
func respond(w http.ResponseWriter, status int, payload interface{}) error {
60+
w.Header().Add("Content-Type", ContentType)
61+
w.WriteHeader(status)
62+
return json.NewEncoder(w).Encode(payload)
63+
}
64+
65+
func prepareError(error *Error) *ErrorResponse {
66+
return &ErrorResponse{}
67+
}
68+
69+
func prepareObject(object *Object) *DataResponse {
70+
return &DataResponse{object}
71+
}
72+
73+
func prepareList(list []*Object) *DataResponse {
74+
return &DataResponse{list}
75+
}

0 commit comments

Comments
 (0)