Skip to content

Commit

Permalink
module/chi: add go-chi engine support (#6)
Browse files Browse the repository at this point in the history
add go-chi engine support
  • Loading branch information
alimy authored Mar 20, 2019
2 parents 575d595 + 7aca551 commit 0e44409
Show file tree
Hide file tree
Showing 12 changed files with 523 additions and 3 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ build: fmt bindata

.PHONY: test
test:
hack/test.sh ./ module/echo module/gin module/httprouter module/iris module/mux
hack/test.sh ./ module/echo module/gin module/chi module/httprouter module/iris module/mux

.PHONY: fmt
fmt:
Expand All @@ -32,7 +32,7 @@ fmt-check:

.PHONY: vet
vet:
hack/vet.sh ./ module/echo module/gin module/httprouter module/iris module/mux module/example
hack/vet.sh ./ module/echo module/gin module/chi module/httprouter module/iris module/mux module/example

.PHONY: lint
lint:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![GoDoc](https://godoc.org/github.com/alimy/mir?status.svg)](https://godoc.org/github.com/alimy/mir)
[![Release](https://img.shields.io/github/release/alimy/mir.svg?style=flat-square)](https://github.com/alimy/mir/releases)

Mir is used for register handler to http router(eg: [Gin](https://github.com/gin-gonic/gin), [Echo](https://github.com/labstack/echo), [Iris](https://github.com/kataras/iris), [Macaron](https://github.com/go-macaron/macaron), [Mux](https://github.com/gorilla/mux), [httprouter](https://github.com/julienschmidt/httprouter))
Mir is used for register handler to http router(eg: [Gin](https://github.com/gin-gonic/gin), [Chi](https://github.com/go-chi/chi), [Echo](https://github.com/labstack/echo), [Iris](https://github.com/kataras/iris), [Macaron](https://github.com/go-macaron/macaron), [Mux](https://github.com/gorilla/mux), [httprouter](https://github.com/julienschmidt/httprouter))
depends on struct tag string info that defined in logic object's struct type field.

### Usage (eg: gin backend)
Expand Down
1 change: 1 addition & 0 deletions module/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Submodules of Mir

* [Mir.Gin](gin): Provide mir.Engine implement backend by [Gin](https://github.com/gin-gonic/gin).
* [Mir.Chi](chi): Provide mir.Engine implement backend by [Chi](https://github.com/go-chi/chi).
* [Mir.Echo](echo): Provide mir.Engine implement backend by [Echo](https://github.com/labstack/echo).
* [Mir.Iris](iris): Provide mir.Engine implement backend by [Iris](https://github.com/kataras/iris).
* [Mir.Macaron](macaron): Provide mir.Engine implement backend by [Macaron](https://github.com/go-macaron/macaron).
Expand Down
52 changes: 52 additions & 0 deletions module/chi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Mir.Chi
Mir.Chi module provide mir.Engine implement backend by [Chi](https://github.com/go-chi/chi).

### Usage
```go
package main

import(
"github.com/alimy/mir"
"github.com/go-chi/chi"
"log"
"net/http"

mirE "github.com/alimy/mir/module/chi"
)

type site struct {
count uint32

Group mir.Group `mir:"v1"`
index mir.Get `mir:"/index/"`
articles mir.Get `mir:"/articles/{category}/{id:[0-9]+}#GetArticles"`
}

// Index handler of the index field that in site struct, the struct tag indicate
// this handler will register to path "/index/" and method is http.MethodGet.
func (h *site) Index(rw http.ResponseWriter, r *http.Request) {
h.count++
rw.Write([]byte("Index"))
}

// GetArticles handler of articles indicator that contains Host/Path/Queries/Handler info.
// Path info is the second or first(if no host info) segment start with '/'(eg: /articles/{category}/{id:[0-9]+}/
// Handler info is forth info start with '#' that indicate real handler method name(eg: GetArticles).
// if no handler info will use field name capital first char as default handler name(eg: if articles had
// no #GetArticles then the handler name will is Articles)
func (h *site) GetArticles(rw http.ResponseWriter, r *http.Request) {
rw.Write([]byte("GetArticles"))
}

func main() {
// Create a new mux router instance
r := chi.NewRouter()

// Instance a mir engine to register handler for mux router by mir
mirE.Register(r, &site{})

// Bind to a port and pass our router in
log.Fatal(http.ListenAndServe(":8013", r))
}

```
22 changes: 22 additions & 0 deletions module/chi/chi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2019 Michael Li <alimy@gility.net>. All rights reserved.
// Use of this source code is governed by Apache License 2.0 that
// can be found in the LICENSE file.

package chi

import (
"github.com/alimy/mir"
"github.com/go-chi/chi"
)

// Mir return mir.Engine interface implements instance.
// Used to register routes to Chi router with struct tag string's information.
func Mir(r chi.Router) mir.Engine {
return &mirEngine{engine: r}
}

// Register use entries's info to register handler to Chi router.
func Register(r chi.Router, entries ...interface{}) error {
mirE := Mir(r)
return mir.Register(mirE, entries...)
}
17 changes: 17 additions & 0 deletions module/chi/chi_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2019 Michael Li <alimy@gility.net>. All rights reserved.
// Use of this source code is governed by Apache License 2.0 that
// can be found in the LICENSE file.

package chi_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestMux(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Chi Suite")
}
174 changes: 174 additions & 0 deletions module/chi/chi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 2019 Michael Li <alimy@gility.net>. All rights reserved.
// Use of this source code is governed by Apache License 2.0 that
// can be found in the LICENSE file.

package chi_test

import (
"bytes"
"github.com/alimy/mir"
"github.com/go-chi/chi"
"net/http/httptest"

. "github.com/alimy/mir/module/chi"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("Core", func() {
var (
router chi.Router
w *httptest.ResponseRecorder
err error
)

JustBeforeEach(func() {
w = httptest.NewRecorder()
})

Context("check Mir function", func() {
BeforeEach(func() {
router = chi.NewRouter()
mirE := Mir(router)
err = mir.Register(mirE, &entry{Chain: mirChain()})
})

It("no error", func() {
Expect(err).Should(BeNil())
})

It("no nil", func() {
Expect(router).ShouldNot(BeNil())
})

It("handle add", func() {
body := bytes.NewReader([]byte("hello"))
r := httptest.NewRequest(mir.MethodPost, "/v1/add/10086/", body)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("Add:10086:hello"))
})

It("handler index", func() {
r := httptest.NewRequest(mir.MethodGet, "/v1/index/", nil)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("Index"))
})

It("handle articles", func() {
r := httptest.NewRequest(mir.MethodGet, "/v1/articles/golang/10086", nil)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("GetArticles:golang:10086"))
})
})

Context("check Register function", func() {
BeforeEach(func() {
router = chi.NewRouter()
err = Register(router, &entry{Group: "/v2", Chain: mirChain()})
})

It("no error", func() {
Expect(err).Should(BeNil())
})

It("no nil", func() {
Expect(router).ShouldNot(BeNil())
})

It("handle add", func() {
body := bytes.NewReader([]byte("hello"))
r := httptest.NewRequest(mir.MethodPost, "/v2/add/10086/", body)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("Add:10086:hello"))
})

It("handler index", func() {
r := httptest.NewRequest(mir.MethodGet, "/v2/index/", nil)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("Index"))
})

It("handle articles", func() {
r := httptest.NewRequest(mir.MethodGet, "/v2/articles/golang/10086", nil)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("GetArticles:golang:10086"))
})
})

Context("check Register entries", func() {
BeforeEach(func() {
router = chi.NewRouter()
err = Register(router, &entry{}, &entry{Group: "v2", Chain: mirChain()})
})

It("no error", func() {
Expect(err).Should(BeNil())
})

It("no nil", func() {
Expect(router).ShouldNot(BeNil())
})

It("handle v1 add", func() {
body := bytes.NewReader([]byte("hello"))
r := httptest.NewRequest(mir.MethodPost, "/v1/add/10086/", body)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("Add:10086:hello"))
})

It("handler v1 index", func() {
r := httptest.NewRequest(mir.MethodGet, "/v1/index/", nil)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("Index"))
})

It("handle v1 articles", func() {
r := httptest.NewRequest(mir.MethodGet, "/v1/articles/golang/10086", nil)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("GetArticles:golang:10086"))
})

It("handle v2 add", func() {
body := bytes.NewReader([]byte("hello"))
r := httptest.NewRequest(mir.MethodPost, "/v2/add/10086/", body)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("Add:10086:hello"))
})

It("handler v2 index", func() {
r := httptest.NewRequest(mir.MethodGet, "/v2/index/", nil)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("Index"))
})

It("handle v2 articles", func() {
r := httptest.NewRequest(mir.MethodGet, "/v2/articles/golang/10086", nil)
router.ServeHTTP(w, r)

Expect(w.Code).To(Equal(200))
Expect(w.Body.String()).To(Equal("GetArticles:golang:10086"))
})
})
})
83 changes: 83 additions & 0 deletions module/chi/core.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2019 Michael Li <alimy@gility.net>. All rights reserved.
// Use of this source code is governed by Apache License 2.0 that
// can be found in the LICENSE file.

package chi

import (
"fmt"
"github.com/alimy/mir"
"github.com/go-chi/chi"
"net/http"
"strings"
)

var _ mir.Engine = &mirEngine{}

// mirEngine used to implements mir.Engine interface
type mirEngine struct {
engine chi.Router
}

// Register register entries to chi engine
func (e *mirEngine) Register(entries []*mir.TagMir) error {
for _, entry := range entries {
var router chi.Router
if entry.Group == "" || entry.Group == "/" {
router = e.engine
} else {
pathPrefix := entry.Group
if !strings.HasPrefix(entry.Group, "/") {
pathPrefix = "/" + entry.Group
}
router = chi.NewRouter()
e.engine.Mount(pathPrefix, router)
}
if err := handlerChainTo(router, entry.Chain); err != nil {
return err
}
// Notice just return if catch a error or continue next entry register
if err := registerWith(router, entry.Fields); err != nil {
return err
}
}
return nil
}

// registerWith register fields to give router
func registerWith(router chi.Router, fields []*mir.TagField) error {
for _, field := range fields {
if handlerFunc, ok := field.Handler.(func(http.ResponseWriter, *http.Request)); ok {
if field.Method == mir.MethodAny {
router.Connect(field.Path, handlerFunc)
router.Delete(field.Path, handlerFunc)
router.Get(field.Path, handlerFunc)
router.Head(field.Path, handlerFunc)
router.Options(field.Path, handlerFunc)
router.Patch(field.Path, handlerFunc)
router.Post(field.Path, handlerFunc)
router.Put(field.Path, handlerFunc)
router.Trace(field.Path, handlerFunc)
} else {
router.MethodFunc(field.Method, field.Path, handlerFunc)
}
} else {
return fmt.Errorf("handler not func(http.ResponseWriter, *http.Request) function")
}
}
return nil
}

// handlerChainTo setup handlers to router that grouped
func handlerChainTo(router chi.Router, chain mir.Chain) error {
// just return if empty chain
if chain == nil {
return nil
}
if middlewares, ok := chain.(chi.Middlewares); ok {
router.Use(middlewares...)
} else {
return fmt.Errorf("chain type not chi.Middlewares")
}
return nil
}
Loading

0 comments on commit 0e44409

Please sign in to comment.