Skip to content

Commit

Permalink
add Convert unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
bughou committed Jul 10, 2020
1 parent c95d3bd commit a0dd81a
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 46 deletions.
5 changes: 3 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ a golang http router with regexp support, inspired by `httprouter` and `gin`.

## default middlewares
- logging with error alarm support
- processing list of requests in processing
- list of requests in processing
- CORS check

## attentions
- static route is always matched before regexp route.
- routes will not be looked back if no match is found for better performance.
- call `c.Next()` in middleware to pass control to the next midlleware or route,
if you don't call `c.Next()` no remaining midlleware or route will be executed.
- generally don't use midlleware after routes,
Expand All @@ -36,7 +37,7 @@ import (

func main() {
router := goa.New()
// logger should be the first, to handle panic and log all requests
// logger should comes first, to handle panic and log all requests
router.Use(middlewares.NewLogger(logger.New(os.Stdout)).Record)
router.Use(middlewares.NewCORS(allowOrigin).Check)
utilroutes.Setup(router)
Expand Down
6 changes: 3 additions & 3 deletions convert-req.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ func getReqFieldsConvertFuncs(typ reflect.Type, path string) (funcs []convertFun
funcs = append(funcs, newParamConvertFunc(f.Type, f.Index, path))
case "Query":
funcs = append(funcs, newQueryConvertFunc(f.Type, f.Index))
case "Body":
funcs = append(funcs, newBodyConvertFunc(f.Type, f.Index))
case "Header":
funcs = append(funcs, newHeaderConvertFunc(f.Type, f.Index))
case "Body":
funcs = append(funcs, newBodyConvertFunc(f.Type, f.Index))
case "Session":
funcs = append(funcs, newSessionConvertFunc(f.Type, f.Index))
case "Ctx":
Expand Down Expand Up @@ -90,7 +90,7 @@ func newBodyConvertFunc(typ reflect.Type, index []int) convertFunc {
if err != nil {
return err
}
return json.Unmarshal(body, req.FieldByIndex(index).Interface())
return json.Unmarshal(body, req.FieldByIndex(index).Addr().Interface())
}
}

Expand Down
41 changes: 23 additions & 18 deletions convert-resp.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,42 @@ func newRespWriteFunc(typ reflect.Type) (reflect.Type, func(*Context, reflect.Va
if typ.Kind() != reflect.Struct {
log.Panic("resp parameter of handler func must be a struct pointer.")
}

for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
switch f.Name {
case "Data":
// data can be of any type
case "Error":
if !f.Type.Implements(errorType) {
log.Panicf(`resp.Error must be of "error" type.`)
}
case "Header":
converters.ValidateRespHeader(typ)
default:
log.Panicf("Unknow field: resp.%s.", f.Name)
}
}
validateRespFields(typ)
return typ, func(ctx *Context, resp reflect.Value) {
var data interface{}
var err error

for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
switch f.Name {
case "Error":
if e := resp.FieldByIndex(f.Index).Interface(); e != nil {
err = e.(error)
}
case "Data":
data = resp.FieldByIndex(f.Index).Interface()
case "Error":
err = resp.FieldByIndex(f.Index).Interface().(error)
case "Header":
converters.WriteRespHeader(resp.FieldByIndex(f.Index), ctx.ResponseWriter.Header())
}
}
ctx.Data(data, err)
}
}

func validateRespFields(typ reflect.Type) {
for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
switch f.Name {
case "Data":
// data can be of any type
case "Error":
if !f.Type.Implements(errorType) {
log.Panicf(`resp.Error must be of "error" type.`)
}
case "Header":
converters.ValidateRespHeader(f.Type)
default:
log.Panicf("Unknow field: resp.%s.", f.Name)
}
}
}
19 changes: 12 additions & 7 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ package goa
import (
"log"
"reflect"

"github.com/lovego/errs"
)

func convertHandler(h interface{}, path string) HandlerFunc {
if handler, ok := h.(func(*Context)); ok {
return handler
}
if handler, ok := h.(HandlerFunc); ok {
return handler
}
Expand All @@ -29,13 +34,13 @@ func convertHandler(h interface{}, path string) HandlerFunc {
return func(ctx *Context) {
req, err := reqConvertFunc(ctx)
if err != nil {
ctx.Data(nil, err)
ctx.Data(nil, errs.New("args-err", err.Error()))
return
}
resp := reflect.New(respTyp)
val.Call([]reflect.Value{req, resp})
if respWriteFunc != nil {
respWriteFunc(ctx, resp)
respWriteFunc(ctx, resp.Elem())
}
}
}
Expand All @@ -51,23 +56,23 @@ func handlerExample(req *struct {
Id int64
Page int64
}
Header struct {
Cookie string
}
Body struct {
Id int64
Name string
}
Header struct {
Cookie string
}
Session struct {
UserId int64
}
Ctx *Context
}, resp *struct {
Data struct {
Error error
Data struct {
Id int64
Name string
}
Error error
Header struct {
SetCookie string
}
Expand Down
76 changes: 76 additions & 0 deletions convert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package goa

import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
)

func ExampleConvertReq() {
router := New()
type T struct {
Type string
Id int
Flag bool
}

router.Get(`/(?P<type>\w+)/(?P<id>\d+)/(?P<flag>(true|false))`, func(req struct {
Param T
Query struct {
Page int
T
}
Header struct {
Cookie string
}
Body struct {
Name string
T
}
}, resp *struct {
Error error
Data interface{}
Header struct {
SetCookie string `header:"Set-Cookie"`
}
}) {
fmt.Printf("req.Param: %+v\n", req.Param)
fmt.Printf("req.Query: %+v\n", req.Query)
fmt.Printf("req.Header: %+v\n", req.Header)
fmt.Printf("req.Body: %+v\n", req.Body)

resp.Data = []int{1, 2, 3}
resp.Header.SetCookie = "c=d"
})

req, err := http.NewRequest(
"GET",
"http://localhost/users/123/true?page=3&type=users&Id=123&flag=true",
strings.NewReader(`{"name":"张三", "type":"users", "id": 123, "flag": true}`),
)
if err != nil {
panic(err)
}
req.Header.Set("Cookie", "a=b")

rw := httptest.NewRecorder()
router.ServeHTTP(rw, req)

resp := rw.Result()
fmt.Printf("resp.Status: %v\n", resp.Status)
fmt.Printf("resp.Header: %v\n", resp.Header)
body, err := ioutil.ReadAll(resp.Body)
fmt.Printf("resp.Body: %v %v\n", string(body), err)

// Output:
// req.Param: {Type:users Id:123 Flag:true}
// req.Query: {Page:3 T:{Type:users Id:123 Flag:true}}
// req.Header: {Cookie:a=b}
// req.Body: {Name:张三 T:{Type:users Id:123 Flag:true}}
// resp.Status: 200 OK
// resp.Header: map[Content-Type:[application/json; charset=utf-8] Set-Cookie:[c=d]]
// resp.Body: {"code":"ok","data":[1,2,3],"message":"success"}
// <nil>
}
4 changes: 2 additions & 2 deletions converters/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func ConvertHeader(value reflect.Value, map2strs map[string][]string) (err error
if len(values) > 0 && values[0] != "" {
err = Set(v, values[0])
}
return err != nil // if err != nil, stop Traverse
return err == nil // if err == nil, go on Traverse
})
return
}
Expand All @@ -43,7 +43,7 @@ func ValidateRespHeader(typ reflect.Type) {
}
structs.Traverse(reflect.New(typ).Elem(), false, func(_ reflect.Value, f reflect.StructField) bool {
if f.Type.Kind() != reflect.String {
log.Panicf("req.Header.%s: type must be string.", f.Name)
log.Panicf("resp.Header.%s: type must be string.", f.Name)
}
return false
})
Expand Down
2 changes: 1 addition & 1 deletion converters/param.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func ForParam(typ reflect.Type, path string) ParamConverter {
if isSupportedType(f.Type) {
fields = append(fields, ParamField{ParamIndex: i - 1, StructField: f})
} else {
log.Panic("req.Param.%s: type must be string, number or bool.", f.Name)
log.Panicf("req.Param.%s: type must be string, number or bool.", f.Name)
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions converters/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ func ValidateQuery(typ reflect.Type) {

func ConvertQuery(value reflect.Value, map2strs map[string][]string) (err error) {
structs.Traverse(value, true, func(v reflect.Value, f reflect.StructField) bool {
values := map2strs[lowercaseFirstLetter(f.Name)]
values := map2strs[f.Name]
if len(values) == 0 {
values = map2strs[lowercaseFirstLetter(f.Name)]
}
if len(values) > 0 && values[0] != "" {
err = Set(v, values[0])
}
return err != nil // if err != nil, stop Traverse
return err == nil // if err == nil, go on Traverse
})
return
}
4 changes: 2 additions & 2 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
func ExampleRouter() {
router := New()

router.Get("/", func(c *Context) {
router.Get("/", HandlerFunc(func(c *Context) {
fmt.Println("root")
})
}))
users := router.Group("/users")

users.Get("/", func(c *Context) {
Expand Down
18 changes: 9 additions & 9 deletions routergroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,32 @@ func (g *RouterGroup) Use(handlers ...HandlerFunc) {
g.handlers = append(g.handlers, handlers...)
}

func (g *RouterGroup) Get(path string, handler HandlerFunc) *RouterGroup {
func (g *RouterGroup) Get(path string, handler interface{}) *RouterGroup {
return g.Add("GET", path, handler)
}

func (g *RouterGroup) Post(path string, handler HandlerFunc) *RouterGroup {
func (g *RouterGroup) Post(path string, handler interface{}) *RouterGroup {
return g.Add("POST", path, handler)
}

func (g *RouterGroup) GetPost(path string, handler HandlerFunc) *RouterGroup {
func (g *RouterGroup) GetPost(path string, handler interface{}) *RouterGroup {
g.Add("GET", path, handler)
return g.Add("POST", path, handler)
}

func (g *RouterGroup) Put(path string, handler HandlerFunc) *RouterGroup {
func (g *RouterGroup) Put(path string, handler interface{}) *RouterGroup {
return g.Add("PUT", path, handler)
}

func (g *RouterGroup) Patch(path string, handler HandlerFunc) *RouterGroup {
func (g *RouterGroup) Patch(path string, handler interface{}) *RouterGroup {
return g.Add("PATCH", path, handler)
}

func (g *RouterGroup) Delete(path string, handler HandlerFunc) *RouterGroup {
func (g *RouterGroup) Delete(path string, handler interface{}) *RouterGroup {
return g.Add("DELETE", path, handler)
}

func (g *RouterGroup) Add(method, path string, handler HandlerFunc) *RouterGroup {
func (g *RouterGroup) Add(method, path string, handler interface{}) *RouterGroup {
method = strings.ToUpper(method)
path = g.concatPath(quotePath(path))
// remove trailing slash
Expand All @@ -63,8 +63,8 @@ func (g *RouterGroup) Add(method, path string, handler HandlerFunc) *RouterGroup
if handler == nil {
return g
}
// handlerFunc = convertHandler(handler, path)
handlers := g.concatHandlers(handler)
handlerFunc := convertHandler(handler, path)
handlers := g.concatHandlers(handlerFunc)

rootNode := g.routes[method]
if rootNode == nil {
Expand Down

0 comments on commit a0dd81a

Please sign in to comment.