forked from Mirrors/jsonapi
Added tests for the REST endpoints exposed in our example. Added a script for running the example app. Split the example into multiple files.
This commit is contained in:
parent
4898d2ff2a
commit
6958c3a8be
224
examples/app.go
224
examples/app.go
|
@ -8,98 +8,11 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/google/jsonapi"
|
||||
)
|
||||
|
||||
func createBlog(w http.ResponseWriter, r *http.Request) {
|
||||
jsonapiRuntime := jsonapi.NewRuntime().Instrument("blogs.create")
|
||||
|
||||
blog := new(Blog)
|
||||
|
||||
if err := jsonapiRuntime.UnmarshalPayload(r.Body, blog); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// ...do stuff with your blog...
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Set("Content-Type", jsonapi.MediaType)
|
||||
|
||||
if err := jsonapiRuntime.MarshalOnePayload(w, blog); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func listBlogs(w http.ResponseWriter, r *http.Request) {
|
||||
jsonapiRuntime := jsonapi.NewRuntime().Instrument("blogs.list")
|
||||
// ...fetch your blogs, filter, offset, limit, etc...
|
||||
|
||||
// but, for now
|
||||
blogs := testBlogsForList()
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", jsonapi.MediaType)
|
||||
if err := jsonapiRuntime.MarshalManyPayload(w, blogs); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func showBlog(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.FormValue("id")
|
||||
|
||||
// ...fetch your blog...
|
||||
|
||||
intID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
jsonapiRuntime := jsonapi.NewRuntime().Instrument("blogs.show")
|
||||
|
||||
// but, for now
|
||||
blog := testBlogForCreate(intID)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
w.Header().Set("Content-Type", jsonapi.MediaType)
|
||||
if err := jsonapiRuntime.MarshalOnePayload(w, blog); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func echoBlogs(w http.ResponseWriter, r *http.Request) {
|
||||
jsonapiRuntime := jsonapi.NewRuntime().Instrument("blogs.echo")
|
||||
|
||||
// Fetch the blogs from the HTTP request body
|
||||
data, err := jsonapiRuntime.UnmarshalManyPayload(r.Body, reflect.TypeOf(new(Blog)))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// Type assert the []interface{} to []*Blog
|
||||
blogs := []*Blog{}
|
||||
for _, b := range data {
|
||||
blog, ok := b.(*Blog)
|
||||
if !ok {
|
||||
http.Error(w, "Unexpected type", http.StatusInternalServerError)
|
||||
}
|
||||
blogs = append(blogs, blog)
|
||||
}
|
||||
|
||||
// Echo the blogs to the response body
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", jsonapi.MediaType)
|
||||
if err := jsonapiRuntime.MarshalManyPayload(w, blogs); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
jsonapi.Instrumentation = func(r *jsonapi.Runtime, eventType jsonapi.Event, callGUID string, dur time.Duration) {
|
||||
metricPrefix := r.Value("instrument").(string)
|
||||
|
@ -121,91 +34,11 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
http.HandleFunc("/blogs", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !regexp.MustCompile(`application/vnd\.api\+json`).Match([]byte(r.Header.Get("Accept"))) {
|
||||
http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
createBlog(w, r)
|
||||
} else if r.Method == http.MethodPut {
|
||||
echoBlogs(w, r)
|
||||
} else if r.FormValue("id") != "" {
|
||||
showBlog(w, r)
|
||||
} else {
|
||||
listBlogs(w, r)
|
||||
}
|
||||
})
|
||||
|
||||
exampleHandler := &ExampleHandler{}
|
||||
http.HandleFunc("/blogs", exampleHandler.ServeHTTP)
|
||||
exerciseHandler()
|
||||
}
|
||||
|
||||
func testBlogForCreate(i int) *Blog {
|
||||
return &Blog{
|
||||
ID: 1 * i,
|
||||
Title: "Title 1",
|
||||
CreatedAt: time.Now(),
|
||||
Posts: []*Post{
|
||||
&Post{
|
||||
ID: 1 * i,
|
||||
Title: "Foo",
|
||||
Body: "Bar",
|
||||
Comments: []*Comment{
|
||||
&Comment{
|
||||
ID: 1 * i,
|
||||
Body: "foo",
|
||||
},
|
||||
&Comment{
|
||||
ID: 2 * i,
|
||||
Body: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
&Post{
|
||||
ID: 2 * i,
|
||||
Title: "Fuubar",
|
||||
Body: "Bas",
|
||||
Comments: []*Comment{
|
||||
&Comment{
|
||||
ID: 1 * i,
|
||||
Body: "foo",
|
||||
},
|
||||
&Comment{
|
||||
ID: 3 * i,
|
||||
Body: "bas",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
CurrentPost: &Post{
|
||||
ID: 1 * i,
|
||||
Title: "Foo",
|
||||
Body: "Bar",
|
||||
Comments: []*Comment{
|
||||
&Comment{
|
||||
ID: 1 * i,
|
||||
Body: "foo",
|
||||
},
|
||||
&Comment{
|
||||
ID: 2 * i,
|
||||
Body: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testBlogsForList() []interface{} {
|
||||
blogs := make([]interface{}, 0, 10)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
blogs = append(blogs, testBlogForCreate(i))
|
||||
}
|
||||
|
||||
return blogs
|
||||
}
|
||||
|
||||
func exerciseHandler() {
|
||||
// list
|
||||
req, _ := http.NewRequest(http.MethodGet, "/blogs", nil)
|
||||
|
@ -242,7 +75,7 @@ func exerciseHandler() {
|
|||
fmt.Println("============== end raw jsonapi from show =============")
|
||||
|
||||
// create
|
||||
blog := testBlogForCreate(1)
|
||||
blog := fixtureBlogCreate(1)
|
||||
in := bytes.NewBuffer(nil)
|
||||
jsonapi.MarshalOnePayloadEmbedded(in, blog)
|
||||
|
||||
|
@ -265,9 +98,9 @@ func exerciseHandler() {
|
|||
|
||||
// echo
|
||||
blogs := []interface{}{
|
||||
testBlogForCreate(1),
|
||||
testBlogForCreate(2),
|
||||
testBlogForCreate(3),
|
||||
fixtureBlogCreate(1),
|
||||
fixtureBlogCreate(2),
|
||||
fixtureBlogCreate(3),
|
||||
}
|
||||
in = bytes.NewBuffer(nil)
|
||||
jsonapi.MarshalManyPayload(in, blogs)
|
||||
|
@ -300,48 +133,3 @@ func exerciseHandler() {
|
|||
fmt.Println(string(out.Bytes()))
|
||||
fmt.Println("================ end marshal materialized Blog struct =================")
|
||||
}
|
||||
|
||||
type Blog struct {
|
||||
ID int `jsonapi:"primary,blogs"`
|
||||
Title string `jsonapi:"attr,title"`
|
||||
Posts []*Post `jsonapi:"relation,posts"`
|
||||
CurrentPost *Post `jsonapi:"relation,current_post"`
|
||||
CurrentPostID int `jsonapi:"attr,current_post_id"`
|
||||
CreatedAt time.Time `jsonapi:"attr,created_at"`
|
||||
ViewCount int `jsonapi:"attr,view_count"`
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
ID int `jsonapi:"primary,posts"`
|
||||
BlogID int `jsonapi:"attr,blog_id"`
|
||||
Title string `jsonapi:"attr,title"`
|
||||
Body string `jsonapi:"attr,body"`
|
||||
Comments []*Comment `jsonapi:"relation,comments"`
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
ID int `jsonapi:"primary,comments"`
|
||||
PostID int `jsonapi:"attr,post_id"`
|
||||
Body string `jsonapi:"attr,body"`
|
||||
}
|
||||
|
||||
// Blog Links
|
||||
func (blog Blog) JSONAPILinks() *map[string]interface{} {
|
||||
return &map[string]interface{}{
|
||||
"self": fmt.Sprintf("https://example.com/blogs/%d", blog.ID),
|
||||
}
|
||||
}
|
||||
|
||||
func (blog Blog) JSONAPIRelationshipLinks(relation string) *map[string]interface{} {
|
||||
if relation == "posts" {
|
||||
return &map[string]interface{}{
|
||||
"related": fmt.Sprintf("https://example.com/blogs/%d/posts", blog.ID),
|
||||
}
|
||||
}
|
||||
if relation == "current_post" {
|
||||
return &map[string]interface{}{
|
||||
"related": fmt.Sprintf("https://example.com/blogs/%d/current_post", blog.ID),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
func fixtureBlogCreate(i int) *Blog {
|
||||
return &Blog{
|
||||
ID: 1 * i,
|
||||
Title: "Title 1",
|
||||
CreatedAt: time.Now(),
|
||||
Posts: []*Post{
|
||||
&Post{
|
||||
ID: 1 * i,
|
||||
Title: "Foo",
|
||||
Body: "Bar",
|
||||
Comments: []*Comment{
|
||||
&Comment{
|
||||
ID: 1 * i,
|
||||
Body: "foo",
|
||||
},
|
||||
&Comment{
|
||||
ID: 2 * i,
|
||||
Body: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
&Post{
|
||||
ID: 2 * i,
|
||||
Title: "Fuubar",
|
||||
Body: "Bas",
|
||||
Comments: []*Comment{
|
||||
&Comment{
|
||||
ID: 1 * i,
|
||||
Body: "foo",
|
||||
},
|
||||
&Comment{
|
||||
ID: 3 * i,
|
||||
Body: "bas",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
CurrentPost: &Post{
|
||||
ID: 1 * i,
|
||||
Title: "Foo",
|
||||
Body: "Bar",
|
||||
Comments: []*Comment{
|
||||
&Comment{
|
||||
ID: 1 * i,
|
||||
Body: "foo",
|
||||
},
|
||||
&Comment{
|
||||
ID: 2 * i,
|
||||
Body: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func fixtureBlogsList() (blogs []interface{}) {
|
||||
for i := 0; i < 10; i++ {
|
||||
blogs = append(blogs, fixtureBlogCreate(i))
|
||||
}
|
||||
|
||||
return blogs
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/jsonapi"
|
||||
)
|
||||
|
||||
const (
|
||||
headerAccept = "Accept"
|
||||
headerContentType = "Content-Type"
|
||||
)
|
||||
|
||||
// ExampleHandler is the handler we are using to demonstrate building an HTTP
|
||||
// server with the jsonapi library.
|
||||
type ExampleHandler struct{}
|
||||
|
||||
func (h *ExampleHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get(headerAccept) != jsonapi.MediaType {
|
||||
http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
|
||||
}
|
||||
|
||||
var methodHandler http.HandlerFunc
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
methodHandler = h.createBlog
|
||||
case http.MethodPut:
|
||||
methodHandler = h.echoBlogs
|
||||
case http.MethodGet:
|
||||
if r.FormValue("id") != "" {
|
||||
methodHandler = h.showBlog
|
||||
} else {
|
||||
methodHandler = h.listBlogs
|
||||
}
|
||||
default:
|
||||
http.Error(w, "Not Found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
methodHandler(w, r)
|
||||
}
|
||||
|
||||
func (h *ExampleHandler) createBlog(w http.ResponseWriter, r *http.Request) {
|
||||
jsonapiRuntime := jsonapi.NewRuntime().Instrument("blogs.create")
|
||||
|
||||
blog := new(Blog)
|
||||
|
||||
if err := jsonapiRuntime.UnmarshalPayload(r.Body, blog); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// ...do stuff with your blog...
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Set(headerContentType, jsonapi.MediaType)
|
||||
|
||||
if err := jsonapiRuntime.MarshalOnePayload(w, blog); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ExampleHandler) echoBlogs(w http.ResponseWriter, r *http.Request) {
|
||||
jsonapiRuntime := jsonapi.NewRuntime().Instrument("blogs.list")
|
||||
// ...fetch your blogs, filter, offset, limit, etc...
|
||||
|
||||
// but, for now
|
||||
blogs := fixtureBlogsList()
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set(headerContentType, jsonapi.MediaType)
|
||||
if err := jsonapiRuntime.MarshalManyPayload(w, blogs); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ExampleHandler) showBlog(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.FormValue("id")
|
||||
|
||||
// ...fetch your blog...
|
||||
|
||||
intID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
jsonapiRuntime := jsonapi.NewRuntime().Instrument("blogs.show")
|
||||
|
||||
// but, for now
|
||||
blog := fixtureBlogCreate(intID)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
w.Header().Set(headerContentType, jsonapi.MediaType)
|
||||
if err := jsonapiRuntime.MarshalOnePayload(w, blog); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ExampleHandler) listBlogs(w http.ResponseWriter, r *http.Request) {
|
||||
jsonapiRuntime := jsonapi.NewRuntime().Instrument("blogs.list")
|
||||
// ...fetch your blogs, filter, offset, limit, etc...
|
||||
|
||||
// but, for now
|
||||
blogs := fixtureBlogsList()
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", jsonapi.MediaType)
|
||||
if err := jsonapiRuntime.MarshalManyPayload(w, blogs); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/google/jsonapi"
|
||||
)
|
||||
|
||||
func TestExampleHandler_post(t *testing.T) {
|
||||
blog := fixtureBlogCreate(1)
|
||||
requestBody := bytes.NewBuffer(nil)
|
||||
jsonapi.MarshalOnePayloadEmbedded(requestBody, blog)
|
||||
|
||||
r, err := http.NewRequest(http.MethodPost, "/blogs?id=1", requestBody)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r.Header.Set(headerAccept, jsonapi.MediaType)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := &ExampleHandler{}
|
||||
handler.ServeHTTP(rr, r)
|
||||
|
||||
if e, a := http.StatusCreated, rr.Code; e != a {
|
||||
t.Fatalf("Expected a status of %d, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExampleHandler_put(t *testing.T) {
|
||||
blogs := []interface{}{
|
||||
fixtureBlogCreate(1),
|
||||
fixtureBlogCreate(2),
|
||||
fixtureBlogCreate(3),
|
||||
}
|
||||
requestBody := bytes.NewBuffer(nil)
|
||||
jsonapi.MarshalManyPayload(requestBody, blogs)
|
||||
|
||||
r, err := http.NewRequest(http.MethodPut, "/blogs", requestBody)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r.Header.Set(headerAccept, jsonapi.MediaType)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := &ExampleHandler{}
|
||||
handler.ServeHTTP(rr, r)
|
||||
|
||||
if e, a := http.StatusOK, rr.Code; e != a {
|
||||
t.Fatalf("Expected a status of %d, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExampleHandler_get_show(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "/blogs?id=1", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r.Header.Set(headerAccept, jsonapi.MediaType)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := &ExampleHandler{}
|
||||
handler.ServeHTTP(rr, r)
|
||||
|
||||
if e, a := http.StatusOK, rr.Code; e != a {
|
||||
t.Fatalf("Expected a status of %d, got %d", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExampleHandler_get_list(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "/blogs", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r.Header.Set(headerAccept, jsonapi.MediaType)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := &ExampleHandler{}
|
||||
handler.ServeHTTP(rr, r)
|
||||
|
||||
if e, a := http.StatusOK, rr.Code; e != a {
|
||||
t.Fatalf("Expected a status of %d, got %d", e, a)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Blog struct {
|
||||
ID int `jsonapi:"primary,blogs"`
|
||||
Title string `jsonapi:"attr,title"`
|
||||
Posts []*Post `jsonapi:"relation,posts"`
|
||||
CurrentPost *Post `jsonapi:"relation,current_post"`
|
||||
CurrentPostID int `jsonapi:"attr,current_post_id"`
|
||||
CreatedAt time.Time `jsonapi:"attr,created_at"`
|
||||
ViewCount int `jsonapi:"attr,view_count"`
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
ID int `jsonapi:"primary,posts"`
|
||||
BlogID int `jsonapi:"attr,blog_id"`
|
||||
Title string `jsonapi:"attr,title"`
|
||||
Body string `jsonapi:"attr,body"`
|
||||
Comments []*Comment `jsonapi:"relation,comments"`
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
ID int `jsonapi:"primary,comments"`
|
||||
PostID int `jsonapi:"attr,post_id"`
|
||||
Body string `jsonapi:"attr,body"`
|
||||
}
|
||||
|
||||
// Blog Links
|
||||
func (blog Blog) JSONAPILinks() *map[string]interface{} {
|
||||
return &map[string]interface{}{
|
||||
"self": fmt.Sprintf("https://example.com/blogs/%d", blog.ID),
|
||||
}
|
||||
}
|
||||
|
||||
func (blog Blog) JSONAPIRelationshipLinks(relation string) *map[string]interface{} {
|
||||
if relation == "posts" {
|
||||
return &map[string]interface{}{
|
||||
"related": fmt.Sprintf("https://example.com/blogs/%d/posts", blog.ID),
|
||||
}
|
||||
}
|
||||
if relation == "current_post" {
|
||||
return &map[string]interface{}{
|
||||
"related": fmt.Sprintf("https://example.com/blogs/%d/current_post", blog.ID),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
go run examples/app.go examples/handler.go examples/fixtures.go examples/models.go
|
Loading…
Reference in New Issue