From 7f3d3f53ae60d447020e89dc73cbb8d9f887fda1 Mon Sep 17 00:00:00 2001 From: Jon Chen Date: Wed, 29 Jun 2016 13:15:21 -0700 Subject: [PATCH] merge jsh-api into go-json-spec-handler --- Godeps/Godeps.json | 5 - README.md | 6 +- .../derekdowling/jsh-api/.gitignore | 1 - .../derekdowling/jsh-api/.travis.yml | 12 - .../derekdowling/jsh-api/Godeps/Godeps.json | 55 ---- .../derekdowling/jsh-api/Godeps/Readme | 5 - client/client_test.go | 4 +- .../derekdowling/jsh-api => jsh-api}/LICENSE | 0 .../jsh-api => jsh-api}/README.md | 10 +- .../derekdowling/jsh-api => jsh-api}/api.go | 0 jsh-api/api_test.go | 53 ++++ .../jsh-api => jsh-api}/jshapi.go | 0 .../jsh-api => jsh-api}/mock_storage.go | 0 .../jsh-api => jsh-api}/relationship.go | 0 .../jsh-api => jsh-api}/resource.go | 8 +- jsh-api/resource_test.go | 245 ++++++++++++++++++ .../jsh-api => jsh-api}/sender.go | 0 .../jsh-api => jsh-api}/store/store.go | 0 .../jsh-api => jsh-api}/utility.go | 0 jsh.go | 2 +- 20 files changed, 315 insertions(+), 91 deletions(-) delete mode 100644 _vendor/github.com/derekdowling/jsh-api/.gitignore delete mode 100644 _vendor/github.com/derekdowling/jsh-api/.travis.yml delete mode 100644 _vendor/github.com/derekdowling/jsh-api/Godeps/Godeps.json delete mode 100644 _vendor/github.com/derekdowling/jsh-api/Godeps/Readme rename {_vendor/github.com/derekdowling/jsh-api => jsh-api}/LICENSE (100%) rename {_vendor/github.com/derekdowling/jsh-api => jsh-api}/README.md (89%) rename {_vendor/github.com/derekdowling/jsh-api => jsh-api}/api.go (100%) create mode 100644 jsh-api/api_test.go rename {_vendor/github.com/derekdowling/jsh-api => jsh-api}/jshapi.go (100%) rename {_vendor/github.com/derekdowling/jsh-api => jsh-api}/mock_storage.go (100%) rename {_vendor/github.com/derekdowling/jsh-api => jsh-api}/relationship.go (100%) rename {_vendor/github.com/derekdowling/jsh-api => jsh-api}/resource.go (97%) create mode 100644 jsh-api/resource_test.go rename {_vendor/github.com/derekdowling/jsh-api => jsh-api}/sender.go (100%) rename {_vendor/github.com/derekdowling/jsh-api => jsh-api}/store/store.go (100%) rename {_vendor/github.com/derekdowling/jsh-api => jsh-api}/utility.go (100%) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 96e0a25..fad0070 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -19,11 +19,6 @@ "Comment": "0.2-1-g148764a", "Rev": "148764a2aea6cdd93a6776e20da13f6dfa38775f" }, - { - "ImportPath": "github.com/derekdowling/jsh-api", - "Comment": "0.5.1", - "Rev": "c3348ad7e3af7734ac9d3085a729125f15568d43" - }, { "ImportPath": "github.com/jtolds/gls", "Rev": "9a4a02dbe491bef4bab3c24fd9f3087d6c4c6690" diff --git a/README.md b/README.md index fa4b3d0..e1478b9 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ sending JSONAPI compatible responses. Not Implementing: * These features aren't handled because they are beyond the scope of what - this module is meant to achieve. See [jshapi](https://github.com/derekdowling/jsh-api) + this module is meant to achieve. See [jshapi](https://github.com/derekdowling/go-json-spec-handler/tree/master/jsh-api) for a full-fledged API solution that solves many of these problems. - Routing @@ -126,10 +126,10 @@ user := &yourUser{} err := object.Unmarshal("users", user) ``` -### [JSH-API](https://github.com/derekdowling/jsh-api) +### [JSH-API](https://github.com/derekdowling/go-json-spec-handler/tree/master/jsh-api) If you're looking for a good place to start with a new API, I've since created -[jshapi](https://github.com/derekdowling/jsh-api) which builds on top of [Goji 2](https://goji.io/) +[jshapi](https://github.com/derekdowling/go-json-spec-handler/tree/master/jsh-api) which builds on top of [Goji 2](https://goji.io/) and `jsh` in order to handle the routing structure that JSON API requires as well as a number of other useful tools for testing and mocking APIs as you develop your own projects. JSHAPI is similar in spirit to this project as it diff --git a/_vendor/github.com/derekdowling/jsh-api/.gitignore b/_vendor/github.com/derekdowling/jsh-api/.gitignore deleted file mode 100644 index 22d0d82..0000000 --- a/_vendor/github.com/derekdowling/jsh-api/.gitignore +++ /dev/null @@ -1 +0,0 @@ -vendor diff --git a/_vendor/github.com/derekdowling/jsh-api/.travis.yml b/_vendor/github.com/derekdowling/jsh-api/.travis.yml deleted file mode 100644 index 347a635..0000000 --- a/_vendor/github.com/derekdowling/jsh-api/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: go -go: - - 1.5 - - tip - -install: - - go get github.com/tools/godep - - go get github.com/smartystreets/goconvey - - godep restore - -script: - - godep go test ./... diff --git a/_vendor/github.com/derekdowling/jsh-api/Godeps/Godeps.json b/_vendor/github.com/derekdowling/jsh-api/Godeps/Godeps.json deleted file mode 100644 index 4e98d09..0000000 --- a/_vendor/github.com/derekdowling/jsh-api/Godeps/Godeps.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "ImportPath": "github.com/derekdowling/jsh-api", - "GoVersion": "go1.5", - "Packages": [ - "./..." - ], - "Deps": [ - { - "ImportPath": "github.com/asaskevich/govalidator", - "Comment": "v2-37-gedd46cd", - "Rev": "edd46cdac249b001c7b7d88c6d43993ea875e8d8" - }, - { - "ImportPath": "github.com/derekdowling/go-json-spec-handler", - "Comment": "0.8.4", - "Rev": "4664564902f42be5e81747745ad12919f9e03d49" - }, - { - "ImportPath": "github.com/derekdowling/go-stdlogger", - "Rev": "cfa80b78b82f8d241ceece0b733b1161bb6a885c" - }, - { - "ImportPath": "github.com/derekdowling/goji2-logger", - "Comment": "0.2-1-g148764a", - "Rev": "148764a2aea6cdd93a6776e20da13f6dfa38775f" - }, - { - "ImportPath": "github.com/jtolds/gls", - "Rev": "9a4a02dbe491bef4bab3c24fd9f3087d6c4c6690" - }, - { - "ImportPath": "github.com/smartystreets/assertions", - "Comment": "1.5.0-409-g5de9043", - "Rev": "5de9043ec1a39cc97edc838ef7236538a55b30a4" - }, - { - "ImportPath": "github.com/smartystreets/goconvey/convey", - "Comment": "1.6.0-10-g995f5b2", - "Rev": "995f5b2e021c69b8b028ba6d0b05c1dd500783db" - }, - { - "ImportPath": "github.com/zenazn/goji/web/mutil", - "Comment": "v0.9.0-34-gbf843a1", - "Rev": "bf843a174a08e846246b8945f8a9a853d84a256a" - }, - { - "ImportPath": "goji.io", - "Rev": "8110ccbc05240c40d43ccb004e0542b3bcbf27da" - }, - { - "ImportPath": "golang.org/x/net/context", - "Rev": "04b9de9b512f58addf28c9853d50ebef61c3953e" - } - ] -} diff --git a/_vendor/github.com/derekdowling/jsh-api/Godeps/Readme b/_vendor/github.com/derekdowling/jsh-api/Godeps/Readme deleted file mode 100644 index 4cdaa53..0000000 --- a/_vendor/github.com/derekdowling/jsh-api/Godeps/Readme +++ /dev/null @@ -1,5 +0,0 @@ -This directory tree is generated automatically by godep. - -Please do not edit. - -See https://github.com/tools/godep for more information. diff --git a/client/client_test.go b/client/client_test.go index 64d9c65..cbe6d40 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -9,7 +9,7 @@ import ( "golang.org/x/net/context" "github.com/derekdowling/go-json-spec-handler" - "github.com/derekdowling/jsh-api" + "github.com/derekdowling/go-json-spec-handler/jsh-api" . "github.com/smartystreets/goconvey/convey" ) @@ -105,7 +105,7 @@ func TestResponseParsing(t *testing.T) { } // not a great for this, would much rather have it in test_util, but it causes an -// import cycle wit jsh-api +// import cycle with jsh-api func testAPI() *jshapi.API { resource := jshapi.NewMockResource("tests", 1, nil) diff --git a/_vendor/github.com/derekdowling/jsh-api/LICENSE b/jsh-api/LICENSE similarity index 100% rename from _vendor/github.com/derekdowling/jsh-api/LICENSE rename to jsh-api/LICENSE diff --git a/_vendor/github.com/derekdowling/jsh-api/README.md b/jsh-api/README.md similarity index 89% rename from _vendor/github.com/derekdowling/jsh-api/README.md rename to jsh-api/README.md index f1de1ed..ef710cf 100644 --- a/_vendor/github.com/derekdowling/jsh-api/README.md +++ b/jsh-api/README.md @@ -1,8 +1,6 @@ # JSH-API -[![GoDoc](https://godoc.org/github.com/derekdowling/go-json-spec-handler?status.png)](https://godoc.org/github.com/derekdowling/jsh-api) -[![Build Status](https://travis-ci.org/derekdowling/jsh-api.svg?branch=master)](https://travis-ci.org/derekdowling/jsh-api) -[![Go Report Card](http://goreportcard.com/badge/manyminds/api2go)](http://goreportcard.com/report/derekdowling/jsh-api) +[![GoDoc](https://godoc.org/github.com/derekdowling/go-json-spec-handler/jsh-api?status.png)](https://godoc.org/github.com/derekdowling/go-json-spec-handler/jsh-api) A [JSON API](http://jsonapi.org) specification micro-service builder created on top of [jsh](http://github.com/derekdowling/go-json-spec-handler), [Goji](http://goji.io), and [context](https://godoc.org/golang.org/x/net/context) to handle the nitty gritty but predictable (un)wrapping, validating, preparing, and logging necessary for any JSON API written in Go. The rest (storage, and business logic) is up to you. @@ -12,7 +10,7 @@ A [JSON API](http://jsonapi.org) specification micro-service builder created on The easiest way to get started is like so: ```go -import github.com/derekdowling/jsh-api +import github.com/derekdowling/go-json-spec-handler/jsh-api // implement jshapi/store.CRUD interface and add resource specific middleware via Goji userStorage := &UserStorage{} @@ -31,7 +29,7 @@ http.ListenAndServe("localhost:8000", api) For a completely custom setup: ```go -import github.com/derekdowling/jsh-api +import github.com/derekdowling/go-json-spec-handler/jsh-api // manually setup your API api := jshapi.New("") @@ -102,7 +100,7 @@ resource.Action("reset", resetAction) ## Working With Storage Interfaces -Below is a basic example of how one might implement parts of a [CRUD Storage](https://godoc.org/github.com/derekdowling/jsh-api/store#CRUD) +Below is a basic example of how one might implement parts of a [CRUD Storage](https://godoc.org/github.com/derekdowling/go-json-spec-handler/tree/master/jsh-api/store#CRUD) interface for a basic user resource using [jsh](https://godoc.org/github.com/derekdowling/go-json-spec-handler) for Save and Update. This should give you a pretty good idea of how easy it is to implement the Storage driver with jsh. diff --git a/_vendor/github.com/derekdowling/jsh-api/api.go b/jsh-api/api.go similarity index 100% rename from _vendor/github.com/derekdowling/jsh-api/api.go rename to jsh-api/api.go diff --git a/jsh-api/api_test.go b/jsh-api/api_test.go new file mode 100644 index 0000000..d460325 --- /dev/null +++ b/jsh-api/api_test.go @@ -0,0 +1,53 @@ +package jshapi + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/derekdowling/go-json-spec-handler" + "github.com/derekdowling/go-json-spec-handler/client" + . "github.com/smartystreets/goconvey/convey" +) + +const testResourceType = "bars" + +func TestAPI(t *testing.T) { + + Convey("API Tests", t, func() { + + api := New("api") + + So(api.prefix, ShouldEqual, "/api") + + testAttrs := map[string]string{ + "foo": "bar", + } + + Convey("->AddResource()", func() { + resource := NewMockResource(testResourceType, 1, testAttrs) + api.Add(resource) + + So(api.Resources[testResourceType], ShouldEqual, resource) + + server := httptest.NewServer(api) + baseURL := server.URL + api.prefix + + Convey("should work with / routes", func() { + _, resp, err := jsc.List(baseURL, testResourceType) + + So(resp.StatusCode, ShouldEqual, http.StatusOK) + So(err, ShouldBeNil) + }) + + Convey("should work with //:id routes", func() { + patchObj, err := jsh.NewObject("1", testResourceType, testAttrs) + So(err, ShouldBeNil) + + _, resp, patchErr := jsc.Patch(baseURL, patchObj) + So(resp.StatusCode, ShouldEqual, http.StatusOK) + So(patchErr, ShouldBeNil) + }) + }) + }) +} diff --git a/_vendor/github.com/derekdowling/jsh-api/jshapi.go b/jsh-api/jshapi.go similarity index 100% rename from _vendor/github.com/derekdowling/jsh-api/jshapi.go rename to jsh-api/jshapi.go diff --git a/_vendor/github.com/derekdowling/jsh-api/mock_storage.go b/jsh-api/mock_storage.go similarity index 100% rename from _vendor/github.com/derekdowling/jsh-api/mock_storage.go rename to jsh-api/mock_storage.go diff --git a/_vendor/github.com/derekdowling/jsh-api/relationship.go b/jsh-api/relationship.go similarity index 100% rename from _vendor/github.com/derekdowling/jsh-api/relationship.go rename to jsh-api/relationship.go diff --git a/_vendor/github.com/derekdowling/jsh-api/resource.go b/jsh-api/resource.go similarity index 97% rename from _vendor/github.com/derekdowling/jsh-api/resource.go rename to jsh-api/resource.go index 27e93f6..b161e06 100644 --- a/_vendor/github.com/derekdowling/jsh-api/resource.go +++ b/jsh-api/resource.go @@ -13,7 +13,7 @@ import ( "golang.org/x/net/context" "github.com/derekdowling/go-json-spec-handler" - "github.com/derekdowling/jsh-api/store" + "github.com/derekdowling/go-json-spec-handler/jsh-api/store" ) const ( @@ -310,6 +310,12 @@ func (res *Resource) patchHandler(ctx context.Context, w http.ResponseWriter, r return } + id := pat.Param(ctx, "id") + if id != parsedObject.ID { + SendHandler(ctx, w, r, jsh.InputError("Request ID does not match URL's", "id")) + return + } + object, err := storage(ctx, parsedObject) if err != nil && reflect.ValueOf(err).IsNil() == false { SendHandler(ctx, w, r, err) diff --git a/jsh-api/resource_test.go b/jsh-api/resource_test.go new file mode 100644 index 0000000..6c47b55 --- /dev/null +++ b/jsh-api/resource_test.go @@ -0,0 +1,245 @@ +package jshapi + +import ( + "log" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/derekdowling/go-json-spec-handler" + "github.com/derekdowling/go-json-spec-handler/client" + . "github.com/smartystreets/goconvey/convey" + "golang.org/x/net/context" +) + +func TestResource(t *testing.T) { + + resource := NewMockResource(testResourceType, 2, testObjAttrs) + + api := New("") + api.Add(resource) + + server := httptest.NewServer(api) + baseURL := server.URL + + routeCount := len(resource.Routes) + if routeCount != 5 { + log.Fatalf("Invalid number of base resource routes: %d", routeCount) + } + + Convey("Resource Tests", t, func() { + + Convey("->NewResource()", func() { + + Convey("should be agnostic to plurality", func() { + resource := NewResource("users") + So(resource.Type, ShouldEqual, "users") + + resource2 := NewResource("user") + So(resource2.Type, ShouldEqual, "user") + }) + }) + + Convey("->Post()", func() { + object := sampleObject("", testResourceType, testObjAttrs) + doc, resp, err := jsc.Post(baseURL, object) + + So(resp.StatusCode, ShouldEqual, http.StatusCreated) + So(err, ShouldBeNil) + So(doc.Data[0].ID, ShouldEqual, "1") + }) + + Convey("->List()", func() { + doc, resp, err := jsc.List(baseURL, testResourceType) + + So(resp.StatusCode, ShouldEqual, http.StatusOK) + So(err, ShouldBeNil) + So(len(doc.Data), ShouldEqual, 2) + So(doc.Data[0].ID, ShouldEqual, "1") + }) + + Convey("->Fetch()", func() { + doc, resp, err := jsc.Fetch(baseURL, testResourceType, "3") + + So(resp.StatusCode, ShouldEqual, http.StatusOK) + So(err, ShouldBeNil) + So(doc.Data[0].ID, ShouldEqual, "3") + }) + + Convey("->Patch()", func() { + + Convey("should reject requests with ID mismatch", func() { + object := sampleObject("1", testResourceType, testObjAttrs) + request, err := jsc.PatchRequest(baseURL, object) + So(err, ShouldBeNil) + // Manually replace resource ID in URL to be invalid + request.URL.Path = strings.Replace(request.URL.Path, "1", "2", 1) + doc, resp, err := jsc.Do(request, jsh.ObjectMode) + + So(resp.StatusCode, ShouldEqual, 422) + So(err, ShouldBeNil) + So(doc, ShouldNotBeNil) + }) + + Convey("should accept patch requests", func() { + object := sampleObject("1", testResourceType, testObjAttrs) + doc, resp, err := jsc.Patch(baseURL, object) + + So(resp.StatusCode, ShouldEqual, http.StatusOK) + So(err, ShouldBeNil) + So(doc.Data[0].ID, ShouldEqual, "1") + }) + }) + + Convey("->Delete()", func() { + resp, err := jsc.Delete(baseURL, testResourceType, "1") + + So(resp.StatusCode, ShouldEqual, http.StatusNoContent) + So(err, ShouldBeNil) + }) + }) +} + +func TestActionHandler(t *testing.T) { + + resource := NewMockResource(testResourceType, 2, testObjAttrs) + + // Add our custom action + handler := func(ctx context.Context, id string) (*jsh.Object, jsh.ErrorType) { + object := sampleObject(id, testResourceType, testObjAttrs) + return object, nil + } + resource.Action("testAction", handler) + + api := New("") + api.Add(resource) + + server := httptest.NewServer(api) + baseURL := server.URL + + Convey("Action Handler Tests", t, func() { + + Convey("Resource State", func() { + So(len(resource.Routes), ShouldEqual, 6) + So(resource.Routes[len(resource.Routes)-1], ShouldEqual, "PATCH - /bars/:id/testAction") + }) + + Convey("->Custom()", func() { + doc, response, err := jsc.Action(baseURL, testResourceType, "1", "testAction") + + So(err, ShouldBeNil) + So(response.StatusCode, ShouldEqual, http.StatusOK) + So(doc.Data, ShouldNotBeEmpty) + }) + }) +} + +func TestToOne(t *testing.T) { + + resource := NewMockResource(testResourceType, 2, testObjAttrs) + + relationshipHandler := func(ctx context.Context, resourceID string) (*jsh.Object, jsh.ErrorType) { + return sampleObject("1", "baz", map[string]string{"baz": "ball"}), nil + } + + subResourceType := "baz" + resource.ToOne(subResourceType, relationshipHandler) + + api := New("") + api.Add(resource) + + server := httptest.NewServer(api) + baseURL := server.URL + + Convey("Relationship ToOne Tests", t, func() { + + Convey("Resource State", func() { + + Convey("should track sub-resources properly", func() { + So(len(resource.Relationships), ShouldEqual, 1) + So(len(resource.Routes), ShouldEqual, 7) + }) + }) + + Convey("->ToOne()", func() { + + Convey("/foo/bars/:id/baz", func() { + doc, resp, err := jsc.Action(baseURL, testResourceType, "1", subResourceType) + + So(resp.StatusCode, ShouldEqual, http.StatusOK) + So(err, ShouldBeNil) + + So(err, ShouldBeNil) + So(doc.Data[0].ID, ShouldEqual, "1") + }) + + Convey("/foo/bars/:id/relationships/baz", func() { + doc, resp, err := jsc.Action(baseURL, testResourceType, "1", "relationships/"+subResourceType) + + So(resp.StatusCode, ShouldEqual, http.StatusOK) + So(err, ShouldBeNil) + + So(err, ShouldBeNil) + So(doc.Data[0].ID, ShouldEqual, "1") + }) + }) + }) +} + +func TestToMany(t *testing.T) { + + resource := NewMockResource(testResourceType, 2, testObjAttrs) + + relationshipHandler := func(ctx context.Context, resourceID string) (jsh.List, jsh.ErrorType) { + return jsh.List{ + sampleObject("1", "baz", map[string]string{"baz": "ball"}), + sampleObject("2", "baz", map[string]string{"baz": "ball2"}), + }, nil + } + + subResourceType := "baz" + resource.ToMany(subResourceType, relationshipHandler) + + api := New("") + api.Add(resource) + + server := httptest.NewServer(api) + baseURL := server.URL + + Convey("Relationship ToMany Tests", t, func() { + + Convey("Resource State", func() { + + Convey("should track sub-resources properly", func() { + So(len(resource.Relationships), ShouldEqual, 1) + So(len(resource.Routes), ShouldEqual, 7) + }) + }) + + Convey("->ToOne()", func() { + + Convey("/foo/bars/:id/bazs", func() { + doc, resp, err := jsc.Action(baseURL, testResourceType, "1", subResourceType+"s") + + So(resp.StatusCode, ShouldEqual, http.StatusOK) + So(err, ShouldBeNil) + + So(err, ShouldBeNil) + So(len(doc.Data), ShouldEqual, 2) + So(doc.Data[0].ID, ShouldEqual, "1") + }) + + Convey("/foo/bars/:id/relationships/bazs", func() { + doc, resp, err := jsc.Action(baseURL, testResourceType, "1", "relationships/"+subResourceType+"s") + + So(resp.StatusCode, ShouldEqual, http.StatusOK) + So(err, ShouldBeNil) + + So(err, ShouldBeNil) + So(len(doc.Data), ShouldEqual, 2) + So(doc.Data[0].ID, ShouldEqual, "1") + }) + }) + }) +} diff --git a/_vendor/github.com/derekdowling/jsh-api/sender.go b/jsh-api/sender.go similarity index 100% rename from _vendor/github.com/derekdowling/jsh-api/sender.go rename to jsh-api/sender.go diff --git a/_vendor/github.com/derekdowling/jsh-api/store/store.go b/jsh-api/store/store.go similarity index 100% rename from _vendor/github.com/derekdowling/jsh-api/store/store.go rename to jsh-api/store/store.go diff --git a/_vendor/github.com/derekdowling/jsh-api/utility.go b/jsh-api/utility.go similarity index 100% rename from _vendor/github.com/derekdowling/jsh-api/utility.go rename to jsh-api/utility.go diff --git a/jsh.go b/jsh.go index 8494736..42bc846 100644 --- a/jsh.go +++ b/jsh.go @@ -4,7 +4,7 @@ // // For a request client, see: jsc: https://godoc.org/github.com/derekdowling/go-json-spec-handler/client // -// For a full http.Handler API builder see jshapi: https://godoc.org/github.com/derekdowling/jsh-api +// For a full http.Handler API builder see jshapi: https://godoc.org/github.com/derekdowling/go-json-spec-handler/jsh-api package jsh const (