Skip to content

Commit 1352d24

Browse files
committed
Merge pull request #182 from manyminds/router_replacement
Extract router to be an interface
2 parents 590b9e0 + 26e054e commit 1352d24

File tree

12 files changed

+343
-181
lines changed

12 files changed

+343
-181
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ go get github.com/manyminds/api2go/jsonapi
3232
- [Ignoring fields](#ignoring-fields)
3333
- [Manual marshaling / unmarshaling](#manual-marshaling--unmarshaling)
3434
- [SQL Null-Types](#sql-null-types)
35+
- [Using api2go with the gin framework](#api2go-with-gin)
3536
- [Building a REST API](#building-a-rest-api)
3637
- [Query Params](#query-params)
3738
- [Using Pagination](#using-pagination)
@@ -318,6 +319,47 @@ these values, it is required to implement the `json.Marshaller` and `json.Unmars
318319
But you dont have to do this by yourself! There already is a library that did the work for you. We recommend that you use the types
319320
of this library: http://gopkg.in/guregu/null.v2/zero
320321

322+
## Using api2go with the gin framework
323+
324+
If you want to use api2go with [gin](https://github.com/gin-gonic/gin) you need to use a different router than the default one.
325+
Get the according adapter using:
326+
327+
```go get github.com/manyminds/api2go-adapter/gingonic```
328+
329+
After that you can bootstrap api2go the following way:
330+
```go
331+
import (
332+
"github.com/gin-gonic/gin"
333+
"github.com/manyminds/api2go"
334+
"github.com/manyminds/api2go-adapter/gingonic"
335+
"github.com/manyminds/api2go/examples/model"
336+
"github.com/manyminds/api2go/examples/resource"
337+
"github.com/manyminds/api2go/examples/storage"
338+
)
339+
340+
func main() {
341+
r := gin.Default()
342+
api := api2go.NewAPIWithRouting(
343+
"api",
344+
api2go.NewStaticResolver("/"),
345+
api2go.DefaultContentMarshalers,
346+
gingonic.New(r),
347+
)
348+
349+
userStorage := storage.NewUserStorage()
350+
chocStorage := storage.NewChocolateStorage()
351+
api.AddResource(model.User{}, resource.UserResource{ChocStorage: chocStorage, UserStorage: userStorage})
352+
api.AddResource(model.Chocolate{}, resource.ChocolateResource{ChocStorage: chocStorage, UserStorage: userStorage})
353+
354+
r.GET("/ping", func(c *gin.Context) {
355+
c.String(200, "pong")
356+
})
357+
r.Run(":8080")
358+
}
359+
```
360+
361+
Keep in mind that you absolutely should map api2go under its own namespace to not get conflicts with your normal routes.
362+
321363
## Building a REST API
322364

323365
First, write an implementation of `api2go.CRUD`. You have to implement at least these 4 methods:

api.go

Lines changed: 68 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import (
1212
"strings"
1313

1414
"github.com/golang/gddo/httputil"
15-
"github.com/julienschmidt/httprouter"
1615
"github.com/manyminds/api2go/jsonapi"
16+
"github.com/manyminds/api2go/routing"
1717
)
1818

1919
const defaultContentTypHeader = "application/vnd.api+json"
@@ -235,19 +235,25 @@ func (api *API) addResource(prototype jsonapi.MarshalIdentifier, source CRUD, ma
235235
marshalers: marshalers,
236236
}
237237

238-
requestInfo := func(r *http.Request) *information {
238+
requestInfo := func(r *http.Request, api *API) *information {
239239
var info *information
240240
if resolver, ok := api.info.resolver.(RequestAwareURLResolver); ok {
241241
resolver.SetRequest(*r)
242-
info = &information{prefix: api.prefix, resolver: resolver}
242+
info = &information{prefix: api.info.prefix, resolver: resolver}
243243
} else {
244244
info = &api.info
245245
}
246246

247247
return info
248248
}
249249

250-
api.router.Handle("OPTIONS", api.prefix+name, func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
250+
prefix := strings.Trim(api.info.prefix, "/")
251+
baseURL := "/" + name
252+
if prefix != "" {
253+
baseURL = "/" + prefix + baseURL
254+
}
255+
256+
api.router.Handle("OPTIONS", baseURL, func(w http.ResponseWriter, r *http.Request, _ map[string]string) {
251257
c := api.contextPool.Get().(APIContexter)
252258
c.Reset()
253259
api.middlewareChain(c, w, r)
@@ -256,7 +262,7 @@ func (api *API) addResource(prototype jsonapi.MarshalIdentifier, source CRUD, ma
256262
api.contextPool.Put(c)
257263
})
258264

259-
api.router.Handle("OPTIONS", api.prefix+name+"/:id", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
265+
api.router.Handle("OPTIONS", baseURL+"/:id", func(w http.ResponseWriter, r *http.Request, _ map[string]string) {
260266
c := api.contextPool.Get().(APIContexter)
261267
c.Reset()
262268
api.middlewareChain(c, w, r)
@@ -265,8 +271,8 @@ func (api *API) addResource(prototype jsonapi.MarshalIdentifier, source CRUD, ma
265271
api.contextPool.Put(c)
266272
})
267273

268-
api.router.GET(api.prefix+name, func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
269-
info := requestInfo(r)
274+
api.router.Handle("GET", baseURL, func(w http.ResponseWriter, r *http.Request, _ map[string]string) {
275+
info := requestInfo(r, api)
270276
c := api.contextPool.Get().(APIContexter)
271277
c.Reset()
272278
api.middlewareChain(c, w, r)
@@ -278,12 +284,12 @@ func (api *API) addResource(prototype jsonapi.MarshalIdentifier, source CRUD, ma
278284
}
279285
})
280286

281-
api.router.GET(api.prefix+name+"/:id", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
282-
info := requestInfo(r)
287+
api.router.Handle("GET", baseURL+"/:id", func(w http.ResponseWriter, r *http.Request, params map[string]string) {
288+
info := requestInfo(r, api)
283289
c := api.contextPool.Get().(APIContexter)
284290
c.Reset()
285291
api.middlewareChain(c, w, r)
286-
err := res.handleRead(c, w, r, ps, *info)
292+
err := res.handleRead(c, w, r, params, *info)
287293
api.contextPool.Put(c)
288294
if err != nil {
289295
handleError(err, w, r, marshalers)
@@ -295,40 +301,40 @@ func (api *API) addResource(prototype jsonapi.MarshalIdentifier, source CRUD, ma
295301
if ok {
296302
relations := casted.GetReferences()
297303
for _, relation := range relations {
298-
api.router.GET(api.prefix+name+"/:id/relationships/"+relation.Name, func(relation jsonapi.Reference) httprouter.Handle {
299-
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
300-
info := requestInfo(r)
304+
api.router.Handle("GET", baseURL+"/:id/relationships/"+relation.Name, func(relation jsonapi.Reference) routing.HandlerFunc {
305+
return func(w http.ResponseWriter, r *http.Request, params map[string]string) {
306+
info := requestInfo(r, api)
301307
c := api.contextPool.Get().(APIContexter)
302308
c.Reset()
303309
api.middlewareChain(c, w, r)
304-
err := res.handleReadRelation(c, w, r, ps, *info, relation)
310+
err := res.handleReadRelation(c, w, r, params, *info, relation)
305311
api.contextPool.Put(c)
306312
if err != nil {
307313
handleError(err, w, r, marshalers)
308314
}
309315
}
310316
}(relation))
311317

312-
api.router.GET(api.prefix+name+"/:id/"+relation.Name, func(relation jsonapi.Reference) httprouter.Handle {
313-
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
314-
info := requestInfo(r)
318+
api.router.Handle("GET", baseURL+"/:id/"+relation.Name, func(relation jsonapi.Reference) routing.HandlerFunc {
319+
return func(w http.ResponseWriter, r *http.Request, params map[string]string) {
320+
info := requestInfo(r, api)
315321
c := api.contextPool.Get().(APIContexter)
316322
c.Reset()
317323
api.middlewareChain(c, w, r)
318-
err := res.handleLinked(c, api, w, r, ps, relation, *info)
324+
err := res.handleLinked(c, api, w, r, params, relation, *info)
319325
api.contextPool.Put(c)
320326
if err != nil {
321327
handleError(err, w, r, marshalers)
322328
}
323329
}
324330
}(relation))
325331

326-
api.router.PATCH(api.prefix+name+"/:id/relationships/"+relation.Name, func(relation jsonapi.Reference) httprouter.Handle {
327-
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
332+
api.router.Handle("PATCH", baseURL+"/:id/relationships/"+relation.Name, func(relation jsonapi.Reference) routing.HandlerFunc {
333+
return func(w http.ResponseWriter, r *http.Request, params map[string]string) {
328334
c := api.contextPool.Get().(APIContexter)
329335
c.Reset()
330336
api.middlewareChain(c, w, r)
331-
err := res.handleReplaceRelation(c, w, r, ps, relation)
337+
err := res.handleReplaceRelation(c, w, r, params, relation)
332338
api.contextPool.Put(c)
333339
if err != nil {
334340
handleError(err, w, r, marshalers)
@@ -338,25 +344,25 @@ func (api *API) addResource(prototype jsonapi.MarshalIdentifier, source CRUD, ma
338344

339345
if _, ok := ptrPrototype.(jsonapi.EditToManyRelations); ok && relation.Name == jsonapi.Pluralize(relation.Name) {
340346
// generate additional routes to manipulate to-many relationships
341-
api.router.POST(api.prefix+name+"/:id/relationships/"+relation.Name, func(relation jsonapi.Reference) httprouter.Handle {
342-
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
347+
api.router.Handle("POST", baseURL+"/:id/relationships/"+relation.Name, func(relation jsonapi.Reference) routing.HandlerFunc {
348+
return func(w http.ResponseWriter, r *http.Request, params map[string]string) {
343349
c := api.contextPool.Get().(APIContexter)
344350
c.Reset()
345351
api.middlewareChain(c, w, r)
346-
err := res.handleAddToManyRelation(c, w, r, ps, relation)
352+
err := res.handleAddToManyRelation(c, w, r, params, relation)
347353
api.contextPool.Put(c)
348354
if err != nil {
349355
handleError(err, w, r, marshalers)
350356
}
351357
}
352358
}(relation))
353359

354-
api.router.DELETE(api.prefix+name+"/:id/relationships/"+relation.Name, func(relation jsonapi.Reference) httprouter.Handle {
355-
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
360+
api.router.Handle("DELETE", baseURL+"/:id/relationships/"+relation.Name, func(relation jsonapi.Reference) routing.HandlerFunc {
361+
return func(w http.ResponseWriter, r *http.Request, params map[string]string) {
356362
c := api.contextPool.Get().(APIContexter)
357363
c.Reset()
358364
api.middlewareChain(c, w, r)
359-
err := res.handleDeleteToManyRelation(c, w, r, ps, relation)
365+
err := res.handleDeleteToManyRelation(c, w, r, params, relation)
360366
api.contextPool.Put(c)
361367
if err != nil {
362368
handleError(err, w, r, marshalers)
@@ -367,34 +373,34 @@ func (api *API) addResource(prototype jsonapi.MarshalIdentifier, source CRUD, ma
367373
}
368374
}
369375

370-
api.router.POST(api.prefix+name, func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
371-
info := requestInfo(r)
376+
api.router.Handle("POST", baseURL, func(w http.ResponseWriter, r *http.Request, params map[string]string) {
377+
info := requestInfo(r, api)
372378
c := api.contextPool.Get().(APIContexter)
373379
c.Reset()
374380
api.middlewareChain(c, w, r)
375-
err := res.handleCreate(c, w, r, api.prefix, *info)
381+
err := res.handleCreate(c, w, r, info.prefix, *info)
376382
api.contextPool.Put(c)
377383
if err != nil {
378384
handleError(err, w, r, marshalers)
379385
}
380386
})
381387

382-
api.router.DELETE(api.prefix+name+"/:id", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
388+
api.router.Handle("DELETE", baseURL+"/:id", func(w http.ResponseWriter, r *http.Request, params map[string]string) {
383389
c := api.contextPool.Get().(APIContexter)
384390
c.Reset()
385391
api.middlewareChain(c, w, r)
386-
err := res.handleDelete(c, w, r, ps)
392+
err := res.handleDelete(c, w, r, params)
387393
api.contextPool.Put(c)
388394
if err != nil {
389395
handleError(err, w, r, marshalers)
390396
}
391397
})
392398

393-
api.router.PATCH(api.prefix+name+"/:id", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
399+
api.router.Handle("PATCH", baseURL+"/:id", func(w http.ResponseWriter, r *http.Request, params map[string]string) {
394400
c := api.contextPool.Get().(APIContexter)
395401
c.Reset()
396402
api.middlewareChain(c, w, r)
397-
err := res.handleUpdate(c, w, r, ps)
403+
err := res.handleUpdate(c, w, r, params)
398404
api.contextPool.Put(c)
399405
if err != nil {
400406
handleError(err, w, r, marshalers)
@@ -451,8 +457,8 @@ func (res *resource) handleIndex(c APIContexter, w http.ResponseWriter, r *http.
451457
return respondWith(response, info, http.StatusOK, w, r, res.marshalers)
452458
}
453459

454-
func (res *resource) handleRead(c APIContexter, w http.ResponseWriter, r *http.Request, ps httprouter.Params, info information) error {
455-
id := ps.ByName("id")
460+
func (res *resource) handleRead(c APIContexter, w http.ResponseWriter, r *http.Request, params map[string]string, info information) error {
461+
id := params["id"]
456462

457463
response, err := res.source.FindOne(id, buildRequest(c, r))
458464

@@ -463,8 +469,8 @@ func (res *resource) handleRead(c APIContexter, w http.ResponseWriter, r *http.R
463469
return respondWith(response, info, http.StatusOK, w, r, res.marshalers)
464470
}
465471

466-
func (res *resource) handleReadRelation(c APIContexter, w http.ResponseWriter, r *http.Request, ps httprouter.Params, info information, relation jsonapi.Reference) error {
467-
id := ps.ByName("id")
472+
func (res *resource) handleReadRelation(c APIContexter, w http.ResponseWriter, r *http.Request, params map[string]string, info information, relation jsonapi.Reference) error {
473+
id := params["id"]
468474

469475
obj, err := res.source.FindOne(id, buildRequest(c, r))
470476
if err != nil {
@@ -519,8 +525,8 @@ func (res *resource) handleReadRelation(c APIContexter, w http.ResponseWriter, r
519525
}
520526

521527
// try to find the referenced resource and call the findAll Method with referencing resource id as param
522-
func (res *resource) handleLinked(c APIContexter, api *API, w http.ResponseWriter, r *http.Request, ps httprouter.Params, linked jsonapi.Reference, info information) error {
523-
id := ps.ByName("id")
528+
func (res *resource) handleLinked(c APIContexter, api *API, w http.ResponseWriter, r *http.Request, params map[string]string, linked jsonapi.Reference, info information) error {
529+
id := params["id"]
524530
for _, resource := range api.resources {
525531
if resource.name == linked.Type {
526532
request := buildRequest(c, r)
@@ -607,7 +613,8 @@ func (res *resource) handleCreate(c APIContexter, w http.ResponseWriter, r *http
607613
if !ok {
608614
return fmt.Errorf("Expected one newly created object by resource %s", res.name)
609615
}
610-
w.Header().Set("Location", prefix+res.name+"/"+result.GetID())
616+
617+
w.Header().Set("Location", "/"+prefix+"/"+res.name+"/"+result.GetID())
611618

612619
// handle 200 status codes
613620
switch response.StatusCode() {
@@ -624,8 +631,9 @@ func (res *resource) handleCreate(c APIContexter, w http.ResponseWriter, r *http
624631
}
625632
}
626633

627-
func (res *resource) handleUpdate(c APIContexter, w http.ResponseWriter, r *http.Request, ps httprouter.Params) error {
628-
obj, err := res.source.FindOne(ps.ByName("id"), buildRequest(c, r))
634+
func (res *resource) handleUpdate(c APIContexter, w http.ResponseWriter, r *http.Request, params map[string]string) error {
635+
id := params["id"]
636+
obj, err := res.source.FindOne(id, buildRequest(c, r))
629637
if err != nil {
630638
return err
631639
}
@@ -698,7 +706,7 @@ func (res *resource) handleUpdate(c APIContexter, w http.ResponseWriter, r *http
698706
case http.StatusOK:
699707
updated := response.Result()
700708
if updated == nil {
701-
internalResponse, err := res.source.FindOne(ps.ByName("id"), buildRequest(c, r))
709+
internalResponse, err := res.source.FindOne(id, buildRequest(c, r))
702710
if err != nil {
703711
return err
704712
}
@@ -722,13 +730,15 @@ func (res *resource) handleUpdate(c APIContexter, w http.ResponseWriter, r *http
722730
}
723731
}
724732

725-
func (res *resource) handleReplaceRelation(c APIContexter, w http.ResponseWriter, r *http.Request, ps httprouter.Params, relation jsonapi.Reference) error {
733+
func (res *resource) handleReplaceRelation(c APIContexter, w http.ResponseWriter, r *http.Request, params map[string]string, relation jsonapi.Reference) error {
726734
var (
727735
err error
728736
editObj interface{}
729737
)
730738

731-
response, err := res.source.FindOne(ps.ByName("id"), buildRequest(c, r))
739+
id := params["id"]
740+
741+
response, err := res.source.FindOne(id, buildRequest(c, r))
732742
if err != nil {
733743
return err
734744
}
@@ -765,13 +775,15 @@ func (res *resource) handleReplaceRelation(c APIContexter, w http.ResponseWriter
765775
return err
766776
}
767777

768-
func (res *resource) handleAddToManyRelation(c APIContexter, w http.ResponseWriter, r *http.Request, ps httprouter.Params, relation jsonapi.Reference) error {
778+
func (res *resource) handleAddToManyRelation(c APIContexter, w http.ResponseWriter, r *http.Request, params map[string]string, relation jsonapi.Reference) error {
769779
var (
770780
err error
771781
editObj interface{}
772782
)
773783

774-
response, err := res.source.FindOne(ps.ByName("id"), buildRequest(c, r))
784+
id := params["id"]
785+
786+
response, err := res.source.FindOne(id, buildRequest(c, r))
775787
if err != nil {
776788
return err
777789
}
@@ -830,12 +842,15 @@ func (res *resource) handleAddToManyRelation(c APIContexter, w http.ResponseWrit
830842
return err
831843
}
832844

833-
func (res *resource) handleDeleteToManyRelation(c APIContexter, w http.ResponseWriter, r *http.Request, ps httprouter.Params, relation jsonapi.Reference) error {
845+
func (res *resource) handleDeleteToManyRelation(c APIContexter, w http.ResponseWriter, r *http.Request, params map[string]string, relation jsonapi.Reference) error {
834846
var (
835847
err error
836848
editObj interface{}
837849
)
838-
response, err := res.source.FindOne(ps.ByName("id"), buildRequest(c, r))
850+
851+
id := params["id"]
852+
853+
response, err := res.source.FindOne(id, buildRequest(c, r))
839854
if err != nil {
840855
return err
841856
}
@@ -902,8 +917,9 @@ func getPointerToStruct(oldObj interface{}) interface{} {
902917
return ptr.Interface()
903918
}
904919

905-
func (res *resource) handleDelete(c APIContexter, w http.ResponseWriter, r *http.Request, ps httprouter.Params) error {
906-
response, err := res.source.Delete(ps.ByName("id"), buildRequest(c, r))
920+
func (res *resource) handleDelete(c APIContexter, w http.ResponseWriter, r *http.Request, params map[string]string) error {
921+
id := params["id"]
922+
response, err := res.source.Delete(id, buildRequest(c, r))
907923
if err != nil {
908924
return err
909925
}

0 commit comments

Comments
 (0)