From 9aedb4d05a70937533ff8cf657b365a80257406d Mon Sep 17 00:00:00 2001 From: eyalpost Date: Fri, 21 Apr 2017 15:26:41 +0300 Subject: [PATCH 01/21] phase #1 --- controller.go | 2 + param/methodparams.go | 101 ++++++++++++++++++++++++++++++++++++++++++ param/options.go | 15 +++++++ param/parsers.go | 41 +++++++++++++++++ response/renderer.go | 27 +++++++++++ response/responses.go | 44 ++++++++++++++++++ router.go | 42 ++++++++++++++---- 7 files changed, 264 insertions(+), 8 deletions(-) create mode 100644 param/methodparams.go create mode 100644 param/options.go create mode 100644 param/parsers.go create mode 100644 response/renderer.go create mode 100644 response/responses.go diff --git a/controller.go b/controller.go index c2a327b3..02e45738 100644 --- a/controller.go +++ b/controller.go @@ -28,6 +28,7 @@ import ( "strings" "github.com/astaxie/beego/context" + "github.com/astaxie/beego/param" "github.com/astaxie/beego/session" ) @@ -51,6 +52,7 @@ type ControllerComments struct { Router string AllowHTTPMethods []string Params []map[string]string + MethodParams []*param.MethodParam } // Controller defines some basic http request handler operations, such as diff --git a/param/methodparams.go b/param/methodparams.go new file mode 100644 index 00000000..2e6e6840 --- /dev/null +++ b/param/methodparams.go @@ -0,0 +1,101 @@ +package param + +import ( + "fmt" + "reflect" + + beecontext "github.com/astaxie/beego/context" +) + +//Keeps param information to be auto passed to controller methods +type MethodParam struct { + name string + parser paramParser + location paramLocation + required bool + defValue interface{} +} + +type paramLocation byte + +const ( + param paramLocation = iota + body + header +) + +type MethodParamOption func(*MethodParam) + +func Bool(name string, opts ...MethodParamOption) *MethodParam { + return newParam(name, boolParser{}, opts) +} + +func String(name string, opts ...MethodParamOption) *MethodParam { + return newParam(name, stringParser{}, opts) +} + +func Int(name string, opts ...MethodParamOption) *MethodParam { + return newParam(name, intParser{}, opts) +} + +func newParam(name string, parser paramParser, opts []MethodParamOption) (param *MethodParam) { + param = &MethodParam{name: name, parser: parser} + for _, option := range opts { + option(param) + } + return +} + +func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) { + result = make([]reflect.Value, 0, len(methodParams)) + i := 0 + for _, p := range methodParams { + var strValue string + var value interface{} + switch p.location { + case body: + strValue = string(ctx.Input.RequestBody) + case header: + strValue = ctx.Input.Header(p.name) + default: + strValue = ctx.Input.Query(p.name) + } + + if strValue == "" { + if p.required { + ctx.Abort(400, "Missing argument "+p.name) + } else if p.defValue != nil { + value = p.defValue + } else { + value = p.parser.zeroValue() + } + } else { + var err error + value, err = p.parser.parse(strValue) + if err != nil { + //TODO: handle err + } + } + reflectValue, err := safeConvert(reflect.ValueOf(value), methodType.In(i)) + if err != nil { + //TODO: handle err + } + result = append(result, reflectValue) + i++ + } + return +} + +func safeConvert(value reflect.Value, t reflect.Type) (result reflect.Value, err error) { + defer func() { + if r := recover(); r != nil { + var ok bool + err, ok = r.(error) + if !ok { + err = fmt.Errorf("%v", r) + } + } + }() + result = value.Convert(t) + return +} diff --git a/param/options.go b/param/options.go new file mode 100644 index 00000000..91d01ac3 --- /dev/null +++ b/param/options.go @@ -0,0 +1,15 @@ +package param + +var InHeader MethodParamOption = func(p *MethodParam) { + p.location = header +} + +var IsRequired MethodParamOption = func(p *MethodParam) { + p.required = true +} + +func Default(defValue interface{}) MethodParamOption { + return func(p *MethodParam) { + p.defValue = defValue + } +} diff --git a/param/parsers.go b/param/parsers.go new file mode 100644 index 00000000..eec0bb79 --- /dev/null +++ b/param/parsers.go @@ -0,0 +1,41 @@ +package param + +import "strconv" + +type paramParser interface { + parse(value string) (interface{}, error) + zeroValue() interface{} +} + +type boolParser struct { +} + +func (p boolParser) parse(value string) (interface{}, error) { + return strconv.ParseBool(value) +} + +func (p boolParser) zeroValue() interface{} { + return false +} + +type stringParser struct { +} + +func (p stringParser) parse(value string) (interface{}, error) { + return value, nil +} + +func (p stringParser) zeroValue() interface{} { + return "" +} + +type intParser struct { +} + +func (p intParser) parse(value string) (interface{}, error) { + return strconv.Atoi(value) +} + +func (p intParser) zeroValue() interface{} { + return 0 +} diff --git a/response/renderer.go b/response/renderer.go new file mode 100644 index 00000000..ca5936db --- /dev/null +++ b/response/renderer.go @@ -0,0 +1,27 @@ +package response + +import ( + "strconv" + + beecontext "github.com/astaxie/beego/context" +) + +type Renderer interface { + Render(ctx *beecontext.Context) +} + +type rendererFunc func(ctx *beecontext.Context) + +func (f rendererFunc) Render(ctx *beecontext.Context) { + f(ctx) +} + +type StatusCode int + +func (s StatusCode) Error() string { + return strconv.Itoa(int(s)) +} + +func (s StatusCode) Render(ctx *beecontext.Context) { + ctx.Output.SetStatus(int(s)) +} diff --git a/response/responses.go b/response/responses.go new file mode 100644 index 00000000..aa042a35 --- /dev/null +++ b/response/responses.go @@ -0,0 +1,44 @@ +package response + +import ( + beecontext "github.com/astaxie/beego/context" +) + +func Json(value interface{}, encoding ...bool) Renderer { + return rendererFunc(func(ctx *beecontext.Context) { + var ( + hasIndent = true + hasEncoding = false + ) + //TODO: need access to BConfig :( + // if BConfig.RunMode == PROD { + // hasIndent = false + // } + if len(encoding) > 0 && encoding[0] { + hasEncoding = true + } + ctx.Output.JSON(value, hasIndent, hasEncoding) + }) +} + +func Error(err error) Renderer { + return rendererFunc(func(ctx *beecontext.Context) { + ctx.Output.SetStatus(500) + ctx.WriteString(err.Error()) + }) +} + +func RenderMethodResult(result interface{}, ctx *beecontext.Context) { + if result != nil { + renderer, ok := result.(Renderer) + if !ok { + err, ok := result.(error) + if ok { + renderer = Error(err) + } else { + renderer = Json(result) + } + } + renderer.Render(ctx) + } +} diff --git a/router.go b/router.go index 9f573f26..4937354a 100644 --- a/router.go +++ b/router.go @@ -29,6 +29,8 @@ import ( beecontext "github.com/astaxie/beego/context" "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/param" + "github.com/astaxie/beego/response" "github.com/astaxie/beego/toolbox" "github.com/astaxie/beego/utils" ) @@ -116,6 +118,7 @@ type controllerInfo struct { handler http.Handler runFunction FilterFunc routerType int + methodParams []*param.MethodParam } // ControllerRegister containers registered router rules, controller handlers and filters. @@ -151,6 +154,10 @@ func NewControllerRegister() *ControllerRegister { // Add("/api",&RestController{},"get,post:ApiFunc" // Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc") func (p *ControllerRegister) Add(pattern string, c ControllerInterface, mappingMethods ...string) { + p.addWithMethodParams(pattern, c, nil, mappingMethods...) +} + +func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInterface, methodParams []*param.MethodParam, mappingMethods ...string) { reflectVal := reflect.ValueOf(c) t := reflect.Indirect(reflectVal).Type() methods := make(map[string]string) @@ -181,6 +188,7 @@ func (p *ControllerRegister) Add(pattern string, c ControllerInterface, mappingM route.methods = methods route.routerType = routerTypeBeego route.controllerType = t + route.methodParams = methodParams if len(methods) == 0 { for _, m := range HTTPMETHOD { p.addToRouter(m, pattern, route) @@ -247,7 +255,7 @@ func (p *ControllerRegister) Include(cList ...ControllerInterface) { key := t.PkgPath() + ":" + t.Name() if comm, ok := GlobalControllerRouter[key]; ok { for _, a := range comm { - p.Add(a.Router, c, strings.Join(a.AllowHTTPMethods, ",")+":"+a.Method) + p.addWithMethodParams(a.Router, c, a.MethodParams, strings.Join(a.AllowHTTPMethods, ",")+":"+a.Method) } } } @@ -626,11 +634,12 @@ func (p *ControllerRegister) execFilter(context *beecontext.Context, urlPath str func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) { startTime := time.Now() var ( - runRouter reflect.Type - findRouter bool - runMethod string - routerInfo *controllerInfo - isRunnable bool + runRouter reflect.Type + findRouter bool + runMethod string + methodParams []*param.MethodParam + routerInfo *controllerInfo + isRunnable bool ) context := p.pool.Get().(*beecontext.Context) context.Reset(rw, r) @@ -742,6 +751,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) routerInfo.handler.ServeHTTP(rw, r) } else { runRouter = routerInfo.controllerType + methodParams = routerInfo.methodParams method := r.Method if r.Method == "POST" && context.Input.Query("_method") == "PUT" { method = "PUT" @@ -804,9 +814,14 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) execController.Options() default: if !execController.HandlerFunc(runMethod) { - var in []reflect.Value method := vc.MethodByName(runMethod) - method.Call(in) + var in []reflect.Value = param.ConvertParams(methodParams, method.Type(), context) + out := method.Call(in) + + //For backward compatibility we only handle response if we had incoming methodParams + if methodParams != nil { + p.handleParamResponse(context, execController, out) + } } } @@ -886,6 +901,17 @@ Admin: } } +func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, execController ControllerInterface, results []reflect.Value) { + //looping in reverse order for the case when both error and value are returned and error sets the response status code + for i := len(results) - 1; i >= 0; i-- { + result := results[i] + if !result.IsNil() { + resultValue := result.Interface() + response.RenderMethodResult(resultValue, context) + } + } +} + // FindRouter Find Router info for URL func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo *controllerInfo, isFind bool) { var urlPath = context.Input.URL() From 89e01d125c274d636f4c7eeb545be38bdefef139 Mon Sep 17 00:00:00 2001 From: eyalpost Date: Sun, 23 Apr 2017 01:33:50 +0300 Subject: [PATCH 02/21] all types implemented --- param/methodparams.go | 82 ++++++++++++++++++++++++++----------------- param/options.go | 18 +++++++--- param/parsers.go | 58 +++++++++++++++++++++--------- response/renderer.go | 9 +++++ response/responses.go | 10 ++++-- 5 files changed, 123 insertions(+), 54 deletions(-) diff --git a/param/methodparams.go b/param/methodparams.go index 2e6e6840..9cd2b1d6 100644 --- a/param/methodparams.go +++ b/param/methodparams.go @@ -5,6 +5,7 @@ import ( "reflect" beecontext "github.com/astaxie/beego/context" + "github.com/astaxie/beego/logs" ) //Keeps param information to be auto passed to controller methods @@ -13,7 +14,7 @@ type MethodParam struct { parser paramParser location paramLocation required bool - defValue interface{} + defValue string } type paramLocation byte @@ -38,6 +39,18 @@ func Int(name string, opts ...MethodParamOption) *MethodParam { return newParam(name, intParser{}, opts) } +func Float(name string, opts ...MethodParamOption) *MethodParam { + return newParam(name, floatParser{}, opts) +} + +func Time(name string, opts ...MethodParamOption) *MethodParam { + return newParam(name, timeParser{}, opts) +} + +func Json(name string, opts ...MethodParamOption) *MethodParam { + return newParam(name, jsonParser{}, opts) +} + func newParam(name string, parser paramParser, opts []MethodParamOption) (param *MethodParam) { param = &MethodParam{name: name, parser: parser} for _, option := range opts { @@ -46,42 +59,47 @@ func newParam(name string, parser paramParser, opts []MethodParamOption) (param return } -func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) { - result = make([]reflect.Value, 0, len(methodParams)) - i := 0 - for _, p := range methodParams { - var strValue string - var value interface{} - switch p.location { - case body: - strValue = string(ctx.Input.RequestBody) - case header: - strValue = ctx.Input.Header(p.name) - default: - strValue = ctx.Input.Query(p.name) +func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Context) (result reflect.Value) { + var strValue string + var reflectValue reflect.Value + switch param.location { + case body: + strValue = string(ctx.Input.RequestBody) + case header: + strValue = ctx.Input.Header(param.name) + default: + strValue = ctx.Input.Query(param.name) + } + + if strValue == "" { + if param.required { + ctx.Abort(400, fmt.Sprintf("Missing parameter %s", param.name)) + } else { + strValue = param.defValue + } + } + if strValue == "" { + reflectValue = reflect.Zero(paramType) + } else { + value, err := param.parser.parse(strValue, paramType) + if err != nil { + logs.Debug(fmt.Sprintf("Error converting param %s to type %s. Value: %s, Parser: %s, Error: %s", param.name, paramType.Name(), strValue, reflect.TypeOf(param.parser).Name(), err)) + ctx.Abort(400, fmt.Sprintf("Invalid parameter %s. Can not convert %s to type %s", param.name, strValue, paramType.Name())) } - if strValue == "" { - if p.required { - ctx.Abort(400, "Missing argument "+p.name) - } else if p.defValue != nil { - value = p.defValue - } else { - value = p.parser.zeroValue() - } - } else { - var err error - value, err = p.parser.parse(strValue) - if err != nil { - //TODO: handle err - } - } - reflectValue, err := safeConvert(reflect.ValueOf(value), methodType.In(i)) + reflectValue, err = safeConvert(reflect.ValueOf(value), paramType) if err != nil { - //TODO: handle err + panic(err) } + } + return reflectValue +} + +func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) { + result = make([]reflect.Value, 0, len(methodParams)) + for i := 0; i < len(methodParams); i++ { + reflectValue := convertParam(methodParams[i], methodType.In(i), ctx) result = append(result, reflectValue) - i++ } return } diff --git a/param/options.go b/param/options.go index 91d01ac3..6fe10108 100644 --- a/param/options.go +++ b/param/options.go @@ -1,15 +1,25 @@ package param -var InHeader MethodParamOption = func(p *MethodParam) { - p.location = header -} +import ( + "fmt" +) var IsRequired MethodParamOption = func(p *MethodParam) { p.required = true } +var InHeader MethodParamOption = func(p *MethodParam) { + p.location = header +} + +var InBody MethodParamOption = func(p *MethodParam) { + p.location = body +} + func Default(defValue interface{}) MethodParamOption { return func(p *MethodParam) { - p.defValue = defValue + if defValue != nil { + p.defValue = fmt.Sprintf("%v", defValue) + } } } diff --git a/param/parsers.go b/param/parsers.go index eec0bb79..e3032e20 100644 --- a/param/parsers.go +++ b/param/parsers.go @@ -1,41 +1,67 @@ package param -import "strconv" +import ( + "encoding/json" + "reflect" + "strconv" + "time" +) type paramParser interface { - parse(value string) (interface{}, error) - zeroValue() interface{} + parse(value string, toType reflect.Type) (interface{}, error) } type boolParser struct { } -func (p boolParser) parse(value string) (interface{}, error) { +func (p boolParser) parse(value string, toType reflect.Type) (interface{}, error) { return strconv.ParseBool(value) } -func (p boolParser) zeroValue() interface{} { - return false -} - type stringParser struct { } -func (p stringParser) parse(value string) (interface{}, error) { +func (p stringParser) parse(value string, toType reflect.Type) (interface{}, error) { return value, nil } -func (p stringParser) zeroValue() interface{} { - return "" -} - type intParser struct { } -func (p intParser) parse(value string) (interface{}, error) { +func (p intParser) parse(value string, toType reflect.Type) (interface{}, error) { return strconv.Atoi(value) } -func (p intParser) zeroValue() interface{} { - return 0 +type floatParser struct { +} + +func (p floatParser) parse(value string, toType reflect.Type) (interface{}, error) { + if toType.Kind() == reflect.Float32 { + return strconv.ParseFloat(value, 32) + } + return strconv.ParseFloat(value, 64) +} + +type timeParser struct { +} + +func (p timeParser) parse(value string, toType reflect.Type) (result interface{}, err error) { + result, err = time.Parse(time.RFC3339, value) + if err != nil { + result, err = time.Parse("2006-01-02", value) + } + return +} + +type jsonParser struct { +} + +func (p jsonParser) parse(value string, toType reflect.Type) (interface{}, error) { + pResult := reflect.New(toType) + v := pResult.Interface() + err := json.Unmarshal([]byte(value), v) + if err != nil { + return nil, err + } + return pResult.Elem().Interface(), nil } diff --git a/response/renderer.go b/response/renderer.go index ca5936db..f5f9a52d 100644 --- a/response/renderer.go +++ b/response/renderer.go @@ -25,3 +25,12 @@ func (s StatusCode) Error() string { func (s StatusCode) Render(ctx *beecontext.Context) { ctx.Output.SetStatus(int(s)) } + +type statusCodeWithRender struct { + statusCode int + rendererFunc +} + +func (s statusCodeWithRender) Error() string { + return strconv.Itoa(s.statusCode) +} diff --git a/response/responses.go b/response/responses.go index aa042a35..5fbe4be1 100644 --- a/response/responses.go +++ b/response/responses.go @@ -21,20 +21,26 @@ func Json(value interface{}, encoding ...bool) Renderer { }) } -func Error(err error) Renderer { +func errorRenderer(err error) Renderer { return rendererFunc(func(ctx *beecontext.Context) { ctx.Output.SetStatus(500) ctx.WriteString(err.Error()) }) } +func Redirect(localurl string) statusCodeWithRender { + return statusCodeWithRender{302, func(ctx *beecontext.Context) { + ctx.Redirect(302, localurl) + }} +} + func RenderMethodResult(result interface{}, ctx *beecontext.Context) { if result != nil { renderer, ok := result.(Renderer) if !ok { err, ok := result.(error) if ok { - renderer = Error(err) + renderer = errorRenderer(err) } else { renderer = Json(result) } From 19f4a6ac0b51cc685fc966b7e8ca12fd89e5bc43 Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Sun, 23 Apr 2017 21:37:09 +0300 Subject: [PATCH 03/21] slice support --- param/conv.go | 68 ++++++++++++++++++++++++++++++++++++++++ param/methodparams.go | 72 +++---------------------------------------- param/parsers.go | 29 ++++++++++++++++- router.go | 2 +- 4 files changed, 102 insertions(+), 69 deletions(-) create mode 100644 param/conv.go diff --git a/param/conv.go b/param/conv.go new file mode 100644 index 00000000..e909c4ea --- /dev/null +++ b/param/conv.go @@ -0,0 +1,68 @@ +package param + +import ( + "fmt" + "reflect" + + beecontext "github.com/astaxie/beego/context" + "github.com/astaxie/beego/logs" +) + +func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Context) (result reflect.Value) { + var strValue string + var reflectValue reflect.Value + switch param.location { + case body: + strValue = string(ctx.Input.RequestBody) + case header: + strValue = ctx.Input.Header(param.name) + default: + strValue = ctx.Input.Query(param.name) + } + + if strValue == "" { + if param.required { + ctx.Abort(400, fmt.Sprintf("Missing parameter %s", param.name)) + } else { + strValue = param.defValue + } + } + if strValue == "" { + reflectValue = reflect.Zero(paramType) + } else { + value, err := param.parser.parse(strValue, paramType) + if err != nil { + logs.Debug(fmt.Sprintf("Error converting param %s to type %s. Value: %s, Error: %s", param.name, paramType, strValue, err)) + ctx.Abort(400, fmt.Sprintf("Invalid parameter %s. Can not convert %s to type %s", param.name, strValue, paramType)) + } + + reflectValue, err = safeConvert(reflect.ValueOf(value), paramType) + if err != nil { + panic(err) + } + } + return reflectValue +} + +func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) { + result = make([]reflect.Value, 0, len(methodParams)) + for i := 0; i < len(methodParams); i++ { + reflectValue := convertParam(methodParams[i], methodType.In(i), ctx) + result = append(result, reflectValue) + } + return +} + +func safeConvert(value reflect.Value, t reflect.Type) (result reflect.Value, err error) { + defer func() { + if r := recover(); r != nil { + var ok bool + err, ok = r.(error) + if !ok { + err = fmt.Errorf("%v", r) + } + } + }() + result = value.Convert(t) + return +} diff --git a/param/methodparams.go b/param/methodparams.go index 9cd2b1d6..a3e25a55 100644 --- a/param/methodparams.go +++ b/param/methodparams.go @@ -1,13 +1,5 @@ package param -import ( - "fmt" - "reflect" - - beecontext "github.com/astaxie/beego/context" - "github.com/astaxie/beego/logs" -) - //Keeps param information to be auto passed to controller methods type MethodParam struct { name string @@ -51,6 +43,11 @@ func Json(name string, opts ...MethodParamOption) *MethodParam { return newParam(name, jsonParser{}, opts) } +func AsSlice(param *MethodParam) *MethodParam { + param.parser = sliceParser(param.parser) + return param +} + func newParam(name string, parser paramParser, opts []MethodParamOption) (param *MethodParam) { param = &MethodParam{name: name, parser: parser} for _, option := range opts { @@ -58,62 +55,3 @@ func newParam(name string, parser paramParser, opts []MethodParamOption) (param } return } - -func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Context) (result reflect.Value) { - var strValue string - var reflectValue reflect.Value - switch param.location { - case body: - strValue = string(ctx.Input.RequestBody) - case header: - strValue = ctx.Input.Header(param.name) - default: - strValue = ctx.Input.Query(param.name) - } - - if strValue == "" { - if param.required { - ctx.Abort(400, fmt.Sprintf("Missing parameter %s", param.name)) - } else { - strValue = param.defValue - } - } - if strValue == "" { - reflectValue = reflect.Zero(paramType) - } else { - value, err := param.parser.parse(strValue, paramType) - if err != nil { - logs.Debug(fmt.Sprintf("Error converting param %s to type %s. Value: %s, Parser: %s, Error: %s", param.name, paramType.Name(), strValue, reflect.TypeOf(param.parser).Name(), err)) - ctx.Abort(400, fmt.Sprintf("Invalid parameter %s. Can not convert %s to type %s", param.name, strValue, paramType.Name())) - } - - reflectValue, err = safeConvert(reflect.ValueOf(value), paramType) - if err != nil { - panic(err) - } - } - return reflectValue -} - -func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) { - result = make([]reflect.Value, 0, len(methodParams)) - for i := 0; i < len(methodParams); i++ { - reflectValue := convertParam(methodParams[i], methodType.In(i), ctx) - result = append(result, reflectValue) - } - return -} - -func safeConvert(value reflect.Value, t reflect.Type) (result reflect.Value, err error) { - defer func() { - if r := recover(); r != nil { - var ok bool - err, ok = r.(error) - if !ok { - err = fmt.Errorf("%v", r) - } - } - }() - result = value.Convert(t) - return -} diff --git a/param/parsers.go b/param/parsers.go index e3032e20..cfa1a981 100644 --- a/param/parsers.go +++ b/param/parsers.go @@ -4,6 +4,7 @@ import ( "encoding/json" "reflect" "strconv" + "strings" "time" ) @@ -11,6 +12,12 @@ type paramParser interface { parse(value string, toType reflect.Type) (interface{}, error) } +type parserFunc func(value string, toType reflect.Type) (interface{}, error) + +func (f parserFunc) parse(value string, toType reflect.Type) (interface{}, error) { + return f(value, toType) +} + type boolParser struct { } @@ -37,7 +44,11 @@ type floatParser struct { func (p floatParser) parse(value string, toType reflect.Type) (interface{}, error) { if toType.Kind() == reflect.Float32 { - return strconv.ParseFloat(value, 32) + res, err := strconv.ParseFloat(value, 32) + if err != nil { + return nil, err + } + return float32(res), nil } return strconv.ParseFloat(value, 64) } @@ -65,3 +76,19 @@ func (p jsonParser) parse(value string, toType reflect.Type) (interface{}, error } return pResult.Elem().Interface(), nil } + +func sliceParser(elemParser paramParser) paramParser { + return parserFunc(func(value string, toType reflect.Type) (interface{}, error) { + values := strings.Split(value, ",") + result := reflect.MakeSlice(toType, 0, len(values)) + elemType := toType.Elem() + for _, v := range values { + parsedValue, err := elemParser.parse(v, elemType) + if err != nil { + return nil, err + } + result = reflect.Append(result, reflect.ValueOf(parsedValue)) + } + return result.Interface(), nil + }) +} diff --git a/router.go b/router.go index 4937354a..be69419c 100644 --- a/router.go +++ b/router.go @@ -905,7 +905,7 @@ func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, ex //looping in reverse order for the case when both error and value are returned and error sets the response status code for i := len(results) - 1; i >= 0; i-- { result := results[i] - if !result.IsNil() { + if result.Kind() != reflect.Interface || !result.IsNil() { resultValue := result.Interface() response.RenderMethodResult(resultValue, context) } From 864693d2f840374460950e3f393ee48e5b8e868f Mon Sep 17 00:00:00 2001 From: eyalpost Date: Mon, 24 Apr 2017 02:35:04 +0300 Subject: [PATCH 04/21] mall fixes --- cache/conv.go | 2 +- param/options.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cache/conv.go b/cache/conv.go index dbdff1c7..87800586 100644 --- a/cache/conv.go +++ b/cache/conv.go @@ -28,7 +28,7 @@ func GetString(v interface{}) string { return string(result) default: if v != nil { - return fmt.Sprintf("%v", result) + return fmt.Sprint(result) } } return "" diff --git a/param/options.go b/param/options.go index 6fe10108..0692f9d1 100644 --- a/param/options.go +++ b/param/options.go @@ -19,7 +19,7 @@ var InBody MethodParamOption = func(p *MethodParam) { func Default(defValue interface{}) MethodParamOption { return func(p *MethodParam) { if defValue != nil { - p.defValue = fmt.Sprintf("%v", defValue) + p.defValue = fmt.Sprint(defValue) } } } From 9b79437778cf2b78f00d71c963cfc4732a01b39e Mon Sep 17 00:00:00 2001 From: eyalpost Date: Tue, 25 Apr 2017 16:00:49 +0300 Subject: [PATCH 05/21] all types working + controller comments generation --- param/conv.go | 58 +++++++----- param/methodparams.go | 72 ++++++++------- param/options.go | 6 ++ param/parsers.go | 57 ++++++++++++ parser.go | 209 +++++++++++++++++++++++++++++++++++------- router.go | 3 + 6 files changed, 317 insertions(+), 88 deletions(-) diff --git a/param/conv.go b/param/conv.go index e909c4ea..4938f45f 100644 --- a/param/conv.go +++ b/param/conv.go @@ -9,39 +9,51 @@ import ( ) func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Context) (result reflect.Value) { - var strValue string - var reflectValue reflect.Value - switch param.location { - case body: - strValue = string(ctx.Input.RequestBody) - case header: - strValue = ctx.Input.Header(param.name) - default: - strValue = ctx.Input.Query(param.name) - } - - if strValue == "" { + paramValue := getParamValue(param, ctx) + if paramValue == "" { if param.required { ctx.Abort(400, fmt.Sprintf("Missing parameter %s", param.name)) } else { - strValue = param.defValue + paramValue = param.defValue } } - if strValue == "" { - reflectValue = reflect.Zero(paramType) + + reflectValue, err := parseValue(paramValue, paramType) + if err != nil { + logs.Debug(fmt.Sprintf("Error converting param %s to type %s. Value: %v, Error: %s", param.name, paramType, paramValue, err)) + ctx.Abort(400, fmt.Sprintf("Invalid parameter %s. Can not convert %v to type %s", param.name, paramValue, paramType)) + } + + return reflectValue +} + +func getParamValue(param *MethodParam, ctx *beecontext.Context) string { + switch param.location { + case body: + return string(ctx.Input.RequestBody) + case header: + return ctx.Input.Header(param.name) + // if strValue == "" && strings.Contains(param.name, "_") { //magically handle X-Headers? + // strValue = ctx.Input.Header(strings.Replace(param.name, "_", "-", -1)) + // } + case path: + return ctx.Input.Query(":" + param.name) + default: + return ctx.Input.Query(param.name) + } +} + +func parseValue(paramValue string, paramType reflect.Type) (result reflect.Value, err error) { + if paramValue == "" { + return reflect.Zero(paramType), nil } else { - value, err := param.parser.parse(strValue, paramType) + value, err := parse(paramValue, paramType) if err != nil { - logs.Debug(fmt.Sprintf("Error converting param %s to type %s. Value: %s, Error: %s", param.name, paramType, strValue, err)) - ctx.Abort(400, fmt.Sprintf("Invalid parameter %s. Can not convert %s to type %s", param.name, strValue, paramType)) + return result, err } - reflectValue, err = safeConvert(reflect.ValueOf(value), paramType) - if err != nil { - panic(err) - } + return safeConvert(reflect.ValueOf(value), paramType) } - return reflectValue } func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) { diff --git a/param/methodparams.go b/param/methodparams.go index a3e25a55..23fd6661 100644 --- a/param/methodparams.go +++ b/param/methodparams.go @@ -1,9 +1,13 @@ package param +import ( + "fmt" + "strings" +) + //Keeps param information to be auto passed to controller methods type MethodParam struct { name string - parser paramParser location paramLocation required bool defValue string @@ -13,45 +17,51 @@ type paramLocation byte const ( param paramLocation = iota + path body header ) -type MethodParamOption func(*MethodParam) - -func Bool(name string, opts ...MethodParamOption) *MethodParam { - return newParam(name, boolParser{}, opts) -} - -func String(name string, opts ...MethodParamOption) *MethodParam { - return newParam(name, stringParser{}, opts) -} - -func Int(name string, opts ...MethodParamOption) *MethodParam { - return newParam(name, intParser{}, opts) -} - -func Float(name string, opts ...MethodParamOption) *MethodParam { - return newParam(name, floatParser{}, opts) -} - -func Time(name string, opts ...MethodParamOption) *MethodParam { - return newParam(name, timeParser{}, opts) -} - -func Json(name string, opts ...MethodParamOption) *MethodParam { - return newParam(name, jsonParser{}, opts) -} - -func AsSlice(param *MethodParam) *MethodParam { - param.parser = sliceParser(param.parser) - return param +func New(name string, opts ...MethodParamOption) *MethodParam { + return newParam(name, nil, opts) } func newParam(name string, parser paramParser, opts []MethodParamOption) (param *MethodParam) { - param = &MethodParam{name: name, parser: parser} + param = &MethodParam{name: name} for _, option := range opts { option(param) } return } + +func Make(list ...*MethodParam) []*MethodParam { + if len(list) > 0 { + return list + } + return nil +} + +func (mp *MethodParam) String() string { + options := []string{} + result := "param.New(\"" + mp.name + "\"" + if mp.required { + options = append(options, "param.IsRequired") + } + switch mp.location { + case path: + options = append(options, "param.InPath") + case body: + options = append(options, "param.InBody") + case header: + options = append(options, "param.InHeader") + } + if mp.defValue != "" { + options = append(options, fmt.Sprintf(`param.Default("%s")`, mp.defValue)) + } + if len(options) > 0 { + result += ", " + } + result += strings.Join(options, ", ") + result += ")" + return result +} diff --git a/param/options.go b/param/options.go index 0692f9d1..0013c31e 100644 --- a/param/options.go +++ b/param/options.go @@ -4,6 +4,8 @@ import ( "fmt" ) +type MethodParamOption func(*MethodParam) + var IsRequired MethodParamOption = func(p *MethodParam) { p.required = true } @@ -12,6 +14,10 @@ var InHeader MethodParamOption = func(p *MethodParam) { p.location = header } +var InPath MethodParamOption = func(p *MethodParam) { + p.location = path +} + var InBody MethodParamOption = func(p *MethodParam) { p.location = body } diff --git a/param/parsers.go b/param/parsers.go index cfa1a981..64cabe49 100644 --- a/param/parsers.go +++ b/param/parsers.go @@ -12,6 +12,45 @@ type paramParser interface { parse(value string, toType reflect.Type) (interface{}, error) } +func parse(value string, t reflect.Type) (interface{}, error) { + parser := getParser(t) + return parser.parse(value, t) +} + +func getParser(t reflect.Type) paramParser { + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return intParser{} + case reflect.Slice: + if t.Elem().Kind() == reflect.Uint8 { //treat []byte as string + return stringParser{} + } + elemParser := getParser(t.Elem()) + if elemParser == (jsonParser{}) { + return elemParser + } + return sliceParser(elemParser) + case reflect.Bool: + return boolParser{} + case reflect.String: + return stringParser{} + case reflect.Float32, reflect.Float64: + return floatParser{} + case reflect.Ptr: + elemParser := getParser(t.Elem()) + if elemParser == (jsonParser{}) { + return elemParser + } + return ptrParser(elemParser) + default: + if t.PkgPath() == "time" && t.Name() == "Time" { + return timeParser{} + } + return jsonParser{} + } +} + type parserFunc func(value string, toType reflect.Type) (interface{}, error) func (f parserFunc) parse(value string, toType reflect.Type) (interface{}, error) { @@ -92,3 +131,21 @@ func sliceParser(elemParser paramParser) paramParser { return result.Interface(), nil }) } + +func ptrParser(elemParser paramParser) paramParser { + return parserFunc(func(value string, toType reflect.Type) (interface{}, error) { + parsedValue, err := elemParser.parse(value, toType.Elem()) + if err != nil { + return nil, err + } + newValPtr := reflect.New(toType.Elem()) + newVal := reflect.Indirect(newValPtr) + convertedVal, err := safeConvert(reflect.ValueOf(parsedValue), toType.Elem()) + if err != nil { + return nil, err + } + + newVal.Set(convertedVal) + return newValPtr.Interface(), nil + }) +} diff --git a/parser.go b/parser.go index d40ee3ce..43e052bd 100644 --- a/parser.go +++ b/parser.go @@ -24,10 +24,14 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "sort" + "strconv" "strings" + "unicode" "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/param" "github.com/astaxie/beego/utils" ) @@ -35,6 +39,7 @@ var globalRouterTemplate = `package routers import ( "github.com/astaxie/beego" + "github.com/astaxie/beego/param" ) func init() { @@ -81,7 +86,7 @@ func parserPkg(pkgRealpath, pkgpath string) error { if specDecl.Recv != nil { exp, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr) // Check that the type is correct first beforing throwing to parser if ok { - parserComments(specDecl.Doc, specDecl.Name.String(), fmt.Sprint(exp.X), pkgpath) + parserComments(specDecl, fmt.Sprint(exp.X), pkgpath) } } } @@ -93,44 +98,168 @@ func parserPkg(pkgRealpath, pkgpath string) error { return nil } -func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error { - if comments != nil && comments.List != nil { - for _, c := range comments.List { - t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) - if strings.HasPrefix(t, "@router") { - elements := strings.TrimLeft(t, "@router ") - e1 := strings.SplitN(elements, " ", 2) - if len(e1) < 1 { - return errors.New("you should has router information") - } - key := pkgpath + ":" + controllerName - cc := ControllerComments{} - cc.Method = funcName - cc.Router = e1[0] - if len(e1) == 2 && e1[1] != "" { - e1 = strings.SplitN(e1[1], " ", 2) - if len(e1) >= 1 { - cc.AllowHTTPMethods = strings.Split(strings.Trim(e1[0], "[]"), ",") - } else { - cc.AllowHTTPMethods = append(cc.AllowHTTPMethods, "get") - } - } else { - cc.AllowHTTPMethods = append(cc.AllowHTTPMethods, "get") - } - if len(e1) == 2 && e1[1] != "" { - keyval := strings.Split(strings.Trim(e1[1], "[]"), " ") - for _, kv := range keyval { - kk := strings.Split(kv, ":") - cc.Params = append(cc.Params, map[string]string{strings.Join(kk[:len(kk)-1], ":"): kk[len(kk)-1]}) - } - } - genInfoList[key] = append(genInfoList[key], cc) - } +type parsedComment struct { + routerPath string + methods []string + params map[string]parsedParam +} + +type parsedParam struct { + name string + datatype string + location string + defValue string + required bool +} + +func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error { + if f.Doc != nil { + parsedComment, err := parseComment(f.Doc.List) + if err != nil { + return err } + key := pkgpath + ":" + controllerName + cc := ControllerComments{} + cc.Method = f.Name.String() + cc.Router = parsedComment.routerPath + cc.AllowHTTPMethods = parsedComment.methods + cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment) + genInfoList[key] = append(genInfoList[key], cc) + } return nil } +func buildMethodParams(funcParams []*ast.Field, pc *parsedComment) []*param.MethodParam { + result := make([]*param.MethodParam, 0, len(funcParams)) + for _, fparam := range funcParams { + methodParam := buildMethodParam(fparam, pc) + result = append(result, methodParam) + } + return result +} + +func buildMethodParam(fparam *ast.Field, pc *parsedComment) *param.MethodParam { + options := []param.MethodParamOption{} + name := fparam.Names[0].Name + if cparam, ok := pc.params[name]; ok { + //Build param from comment info + name = cparam.name + if cparam.required { + options = append(options, param.IsRequired) + } + switch cparam.location { + case "body": + options = append(options, param.InBody) + case "header": + options = append(options, param.InHeader) + case "path": + options = append(options, param.InPath) + } + if cparam.defValue != "" { + options = append(options, param.Default(cparam.defValue)) + } + } else { + if paramInPath(name, pc.routerPath) { + options = append(options, param.InPath) + } + } + return param.New(name, options...) +} + +func paramInPath(name, route string) bool { + return strings.HasSuffix(route, ":"+name) || + strings.Contains(route, ":"+name+"/") +} + +var routeRegex = regexp.MustCompile(`@router\s+(\S+)(?:\s+\[(\S+)\])?`) + +func parseComment(lines []*ast.Comment) (pc *parsedComment, err error) { + pc = &parsedComment{} + for _, c := range lines { + t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) + if strings.HasPrefix(t, "@router") { + matches := routeRegex.FindStringSubmatch(t) + if len(matches) == 3 { + pc.routerPath = matches[1] + methods := matches[2] + if methods == "" { + pc.methods = []string{"get"} + //pc.hasGet = true + } else { + pc.methods = strings.Split(methods, ",") + //pc.hasGet = strings.Contains(methods, "get") + } + } else { + return nil, errors.New("Router information is missing") + } + } else if strings.HasPrefix(t, "@Param") { + pv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Param"))) + if len(pv) < 4 { + logs.Error("Invalid @Param format. Needs at least 4 parameters") + } + p := parsedParam{} + names := strings.Split(pv[0], "=") + funcParamName := names[0] + if len(names) > 1 { + p.name = names[1] + } else { + p.name = funcParamName + } + p.location = pv[1] + p.datatype = pv[2] + switch len(pv) { + case 5: + p.required, _ = strconv.ParseBool(pv[3]) + case 6: + p.defValue = pv[3] + p.required, _ = strconv.ParseBool(pv[4]) + } + if pc.params == nil { + pc.params = map[string]parsedParam{} + } + pc.params[funcParamName] = p + } + } + return +} + +// direct copy from bee\g_docs.go +// analisys params return []string +// @Param query form string true "The email for login" +// [query form string true "The email for login"] +func getparams(str string) []string { + var s []rune + var j int + var start bool + var r []string + var quoted int8 + for _, c := range []rune(str) { + if unicode.IsSpace(c) && quoted == 0 { + if !start { + continue + } else { + start = false + j++ + r = append(r, string(s)) + s = make([]rune, 0) + continue + } + } + + start = true + if c == '"' { + quoted ^= 1 + continue + } + s = append(s, c) + } + if len(s) > 0 { + r = append(r, string(s)) + } + return r +} + func genRouterCode(pkgRealpath string) { os.Mkdir(getRouterDir(pkgRealpath), 0755) logs.Info("generate router from comments") @@ -163,12 +292,24 @@ func genRouterCode(pkgRealpath string) { } params = strings.TrimRight(params, ",") + "}" } + methodParams := "param.Make(" + if len(c.MethodParams) > 0 { + lines := make([]string, 0, len(c.MethodParams)) + for _, m := range c.MethodParams { + lines = append(lines, fmt.Sprint(m)) + } + methodParams += "\n " + + strings.Join(lines, ",\n ") + + ",\n " + } + methodParams += ")" globalinfo = globalinfo + ` beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"], beego.ControllerComments{ Method: "` + strings.TrimSpace(c.Method) + `", ` + "Router: `" + c.Router + "`" + `, AllowHTTPMethods: ` + allmethod + `, + MethodParams: ` + methodParams + `, Params: ` + params + `}) ` } diff --git a/router.go b/router.go index 57479248..546db22d 100644 --- a/router.go +++ b/router.go @@ -908,6 +908,9 @@ func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, ex response.RenderMethodResult(resultValue, context) } } + if !context.ResponseWriter.Started && context.Output.Status == 0 { + context.Output.SetStatus(200) + } } // FindRouter Find Router info for URL From cbd831042a34af663669a2fb4cce2f0ab78e7064 Mon Sep 17 00:00:00 2001 From: eyalpost Date: Tue, 25 Apr 2017 18:39:42 +0300 Subject: [PATCH 06/21] move under context --- {param => context/param}/conv.go | 0 {param => context/param}/methodparams.go | 0 {param => context/param}/options.go | 0 {param => context/param}/parsers.go | 0 {response => context/response}/renderer.go | 0 {response => context/response}/responses.go | 0 controller.go | 2 +- parser.go | 4 ++-- router.go | 4 ++-- 9 files changed, 5 insertions(+), 5 deletions(-) rename {param => context/param}/conv.go (100%) rename {param => context/param}/methodparams.go (100%) rename {param => context/param}/options.go (100%) rename {param => context/param}/parsers.go (100%) rename {response => context/response}/renderer.go (100%) rename {response => context/response}/responses.go (100%) diff --git a/param/conv.go b/context/param/conv.go similarity index 100% rename from param/conv.go rename to context/param/conv.go diff --git a/param/methodparams.go b/context/param/methodparams.go similarity index 100% rename from param/methodparams.go rename to context/param/methodparams.go diff --git a/param/options.go b/context/param/options.go similarity index 100% rename from param/options.go rename to context/param/options.go diff --git a/param/parsers.go b/context/param/parsers.go similarity index 100% rename from param/parsers.go rename to context/param/parsers.go diff --git a/response/renderer.go b/context/response/renderer.go similarity index 100% rename from response/renderer.go rename to context/response/renderer.go diff --git a/response/responses.go b/context/response/responses.go similarity index 100% rename from response/responses.go rename to context/response/responses.go diff --git a/controller.go b/controller.go index 02e45738..510e16b8 100644 --- a/controller.go +++ b/controller.go @@ -28,7 +28,7 @@ import ( "strings" "github.com/astaxie/beego/context" - "github.com/astaxie/beego/param" + "github.com/astaxie/beego/context/param" "github.com/astaxie/beego/session" ) diff --git a/parser.go b/parser.go index 43e052bd..17d9a5d2 100644 --- a/parser.go +++ b/parser.go @@ -30,8 +30,8 @@ import ( "strings" "unicode" + "github.com/astaxie/beego/context/param" "github.com/astaxie/beego/logs" - "github.com/astaxie/beego/param" "github.com/astaxie/beego/utils" ) @@ -39,7 +39,7 @@ var globalRouterTemplate = `package routers import ( "github.com/astaxie/beego" - "github.com/astaxie/beego/param" + "github.com/astaxie/beego/context/param" ) func init() { diff --git a/router.go b/router.go index 546db22d..ebe70e72 100644 --- a/router.go +++ b/router.go @@ -27,9 +27,9 @@ import ( "time" beecontext "github.com/astaxie/beego/context" + "github.com/astaxie/beego/context/param" + "github.com/astaxie/beego/context/response" "github.com/astaxie/beego/logs" - "github.com/astaxie/beego/param" - "github.com/astaxie/beego/response" "github.com/astaxie/beego/toolbox" "github.com/astaxie/beego/utils" ) From 4cba78afd948470826c0bed5eda222f9824b8da7 Mon Sep 17 00:00:00 2001 From: eyalpost Date: Tue, 25 Apr 2017 23:42:35 +0300 Subject: [PATCH 07/21] small fixes --- context/param/conv.go | 25 +++++++++++++------------ context/param/parsers.go | 14 ++++++-------- parser.go | 9 ++++----- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/context/param/conv.go b/context/param/conv.go index 4938f45f..8e580c2e 100644 --- a/context/param/conv.go +++ b/context/param/conv.go @@ -8,6 +8,15 @@ import ( "github.com/astaxie/beego/logs" ) +func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) { + result = make([]reflect.Value, 0, len(methodParams)) + for i := 0; i < len(methodParams); i++ { + reflectValue := convertParam(methodParams[i], methodType.In(i), ctx) + result = append(result, reflectValue) + } + return +} + func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Context) (result reflect.Value) { paramValue := getParamValue(param, ctx) if paramValue == "" { @@ -18,7 +27,7 @@ func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Co } } - reflectValue, err := parseValue(paramValue, paramType) + reflectValue, err := parseValue(param, paramValue, paramType) if err != nil { logs.Debug(fmt.Sprintf("Error converting param %s to type %s. Value: %v, Error: %s", param.name, paramType, paramValue, err)) ctx.Abort(400, fmt.Sprintf("Invalid parameter %s. Can not convert %v to type %s", param.name, paramValue, paramType)) @@ -43,11 +52,12 @@ func getParamValue(param *MethodParam, ctx *beecontext.Context) string { } } -func parseValue(paramValue string, paramType reflect.Type) (result reflect.Value, err error) { +func parseValue(param *MethodParam, paramValue string, paramType reflect.Type) (result reflect.Value, err error) { if paramValue == "" { return reflect.Zero(paramType), nil } else { - value, err := parse(paramValue, paramType) + parser := getParser(param, paramType) + value, err := parser.parse(paramValue, paramType) if err != nil { return result, err } @@ -56,15 +66,6 @@ func parseValue(paramValue string, paramType reflect.Type) (result reflect.Value } } -func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) { - result = make([]reflect.Value, 0, len(methodParams)) - for i := 0; i < len(methodParams); i++ { - reflectValue := convertParam(methodParams[i], methodType.In(i), ctx) - result = append(result, reflectValue) - } - return -} - func safeConvert(value reflect.Value, t reflect.Type) (result reflect.Value, err error) { defer func() { if r := recover(); r != nil { diff --git a/context/param/parsers.go b/context/param/parsers.go index 64cabe49..2b48e878 100644 --- a/context/param/parsers.go +++ b/context/param/parsers.go @@ -12,12 +12,7 @@ type paramParser interface { parse(value string, toType reflect.Type) (interface{}, error) } -func parse(value string, t reflect.Type) (interface{}, error) { - parser := getParser(t) - return parser.parse(value, t) -} - -func getParser(t reflect.Type) paramParser { +func getParser(param *MethodParam, t reflect.Type) paramParser { switch t.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: @@ -26,7 +21,10 @@ func getParser(t reflect.Type) paramParser { if t.Elem().Kind() == reflect.Uint8 { //treat []byte as string return stringParser{} } - elemParser := getParser(t.Elem()) + if param.location == body { + return jsonParser{} + } + elemParser := getParser(param, t.Elem()) if elemParser == (jsonParser{}) { return elemParser } @@ -38,7 +36,7 @@ func getParser(t reflect.Type) paramParser { case reflect.Float32, reflect.Float64: return floatParser{} case reflect.Ptr: - elemParser := getParser(t.Elem()) + elemParser := getParser(param, t.Elem()) if elemParser == (jsonParser{}) { return elemParser } diff --git a/parser.go b/parser.go index 17d9a5d2..66f51d0b 100644 --- a/parser.go +++ b/parser.go @@ -199,12 +199,11 @@ func parseComment(lines []*ast.Comment) (pc *parsedComment, err error) { logs.Error("Invalid @Param format. Needs at least 4 parameters") } p := parsedParam{} - names := strings.Split(pv[0], "=") - funcParamName := names[0] + names := strings.SplitN(pv[0], "=>", 2) + p.name = names[0] + funcParamName := p.name if len(names) > 1 { - p.name = names[1] - } else { - p.name = funcParamName + funcParamName = names[1] } p.location = pv[1] p.datatype = pv[2] From a1bc94e648dbb04b02a06ddfd76aef3006ba52c9 Mon Sep 17 00:00:00 2001 From: eyalpost Date: Wed, 26 Apr 2017 01:00:25 +0300 Subject: [PATCH 08/21] dont generate comment if router not found --- parser.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/parser.go b/parser.go index 66f51d0b..037d5376 100644 --- a/parser.go +++ b/parser.go @@ -118,13 +118,15 @@ func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error { if err != nil { return err } - key := pkgpath + ":" + controllerName - cc := ControllerComments{} - cc.Method = f.Name.String() - cc.Router = parsedComment.routerPath - cc.AllowHTTPMethods = parsedComment.methods - cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment) - genInfoList[key] = append(genInfoList[key], cc) + if parsedComment.routerPath != "" { + key := pkgpath + ":" + controllerName + cc := ControllerComments{} + cc.Method = f.Name.String() + cc.Router = parsedComment.routerPath + cc.AllowHTTPMethods = parsedComment.methods + cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment) + genInfoList[key] = append(genInfoList[key], cc) + } } return nil From 1b8f05cef15029e184036c081b9d5b9542d1ecbe Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Sun, 30 Apr 2017 19:28:26 +0300 Subject: [PATCH 09/21] golint fixes --- context/param/conv.go | 16 ++++++++-------- context/param/methodparams.go | 4 +++- context/param/options.go | 6 ++++++ context/response/renderer.go | 3 +++ context/response/responses.go | 9 ++++++--- router.go | 2 +- 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/context/param/conv.go b/context/param/conv.go index 8e580c2e..e961b4ad 100644 --- a/context/param/conv.go +++ b/context/param/conv.go @@ -8,6 +8,7 @@ import ( "github.com/astaxie/beego/logs" ) +// ConvertParams converts http method params to values that will be passed to the method controller as arguments func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) { result = make([]reflect.Value, 0, len(methodParams)) for i := 0; i < len(methodParams); i++ { @@ -55,15 +56,14 @@ func getParamValue(param *MethodParam, ctx *beecontext.Context) string { func parseValue(param *MethodParam, paramValue string, paramType reflect.Type) (result reflect.Value, err error) { if paramValue == "" { return reflect.Zero(paramType), nil - } else { - parser := getParser(param, paramType) - value, err := parser.parse(paramValue, paramType) - if err != nil { - return result, err - } - - return safeConvert(reflect.ValueOf(value), paramType) } + parser := getParser(param, paramType) + value, err := parser.parse(paramValue, paramType) + if err != nil { + return result, err + } + + return safeConvert(reflect.ValueOf(value), paramType) } func safeConvert(value reflect.Value, t reflect.Type) (result reflect.Value, err error) { diff --git a/context/param/methodparams.go b/context/param/methodparams.go index 23fd6661..28772504 100644 --- a/context/param/methodparams.go +++ b/context/param/methodparams.go @@ -5,7 +5,7 @@ import ( "strings" ) -//Keeps param information to be auto passed to controller methods +//MethodParam keeps param information to be auto passed to controller methods type MethodParam struct { name string location paramLocation @@ -22,6 +22,7 @@ const ( header ) +//New creates a new MethodParam with name and specific options func New(name string, opts ...MethodParamOption) *MethodParam { return newParam(name, nil, opts) } @@ -34,6 +35,7 @@ func newParam(name string, parser paramParser, opts []MethodParamOption) (param return } +//Make creates an array of MethodParmas or an empty array func Make(list ...*MethodParam) []*MethodParam { if len(list) > 0 { return list diff --git a/context/param/options.go b/context/param/options.go index 0013c31e..846a59f6 100644 --- a/context/param/options.go +++ b/context/param/options.go @@ -4,24 +4,30 @@ import ( "fmt" ) +// MethodParamOption defines a func which apply options on a MethodParam type MethodParamOption func(*MethodParam) +// IsRequired indicates that this param is required and can not be ommited from the http request var IsRequired MethodParamOption = func(p *MethodParam) { p.required = true } +// InHeader indicates that this param is passed via an http header var InHeader MethodParamOption = func(p *MethodParam) { p.location = header } +// InPath indicates that this param is part of the URL path var InPath MethodParamOption = func(p *MethodParam) { p.location = path } +// InBody indicates that this param is passed as an http request body var InBody MethodParamOption = func(p *MethodParam) { p.location = body } +// Default provides a default value for the http param func Default(defValue interface{}) MethodParamOption { return func(p *MethodParam) { if defValue != nil { diff --git a/context/response/renderer.go b/context/response/renderer.go index f5f9a52d..2a4d2797 100644 --- a/context/response/renderer.go +++ b/context/response/renderer.go @@ -6,6 +6,7 @@ import ( beecontext "github.com/astaxie/beego/context" ) +// Renderer defines an http response renderer type Renderer interface { Render(ctx *beecontext.Context) } @@ -16,12 +17,14 @@ func (f rendererFunc) Render(ctx *beecontext.Context) { f(ctx) } +// StatusCode sets the http response status code type StatusCode int func (s StatusCode) Error() string { return strconv.Itoa(int(s)) } +// Render sets the http status code func (s StatusCode) Render(ctx *beecontext.Context) { ctx.Output.SetStatus(int(s)) } diff --git a/context/response/responses.go b/context/response/responses.go index 5fbe4be1..c033edf7 100644 --- a/context/response/responses.go +++ b/context/response/responses.go @@ -4,7 +4,8 @@ import ( beecontext "github.com/astaxie/beego/context" ) -func Json(value interface{}, encoding ...bool) Renderer { +// JSON renders value to the response as JSON +func JSON(value interface{}, encoding ...bool) Renderer { return rendererFunc(func(ctx *beecontext.Context) { var ( hasIndent = true @@ -28,12 +29,14 @@ func errorRenderer(err error) Renderer { }) } -func Redirect(localurl string) statusCodeWithRender { +// Redirect renders http 302 with a URL +func Redirect(localurl string) Renderer { return statusCodeWithRender{302, func(ctx *beecontext.Context) { ctx.Redirect(302, localurl) }} } +// RenderMethodResult renders the return value of a controller method to the output func RenderMethodResult(result interface{}, ctx *beecontext.Context) { if result != nil { renderer, ok := result.(Renderer) @@ -42,7 +45,7 @@ func RenderMethodResult(result interface{}, ctx *beecontext.Context) { if ok { renderer = errorRenderer(err) } else { - renderer = Json(result) + renderer = JSON(result) } } renderer.Render(ctx) diff --git a/router.go b/router.go index ebe70e72..33096039 100644 --- a/router.go +++ b/router.go @@ -813,7 +813,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) default: if !execController.HandlerFunc(runMethod) { method := vc.MethodByName(runMethod) - var in []reflect.Value = param.ConvertParams(methodParams, method.Type(), context) + in := param.ConvertParams(methodParams, method.Type(), context) out := method.Call(in) //For backward compatibility we only handle response if we had incoming methodParams From d3a16dca85d1c4ceae415134718411c7758689fc Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Mon, 1 May 2017 08:57:57 +0300 Subject: [PATCH 10/21] Redirect should returns error --- context/response/responses.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context/response/responses.go b/context/response/responses.go index c033edf7..10d1ed3f 100644 --- a/context/response/responses.go +++ b/context/response/responses.go @@ -30,7 +30,7 @@ func errorRenderer(err error) Renderer { } // Redirect renders http 302 with a URL -func Redirect(localurl string) Renderer { +func Redirect(localurl string) error { return statusCodeWithRender{302, func(ctx *beecontext.Context) { ctx.Redirect(302, localurl) }} From cb4f252a06455ac1a4f9e15d7d83e3c33cd188fb Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Thu, 11 May 2017 17:58:25 +0300 Subject: [PATCH 11/21] defValue -> defaultValue --- context/param/conv.go | 5 +---- context/param/methodparams.go | 12 ++++++------ context/param/options.go | 6 +++--- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/context/param/conv.go b/context/param/conv.go index e961b4ad..596d2b1e 100644 --- a/context/param/conv.go +++ b/context/param/conv.go @@ -24,7 +24,7 @@ func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Co if param.required { ctx.Abort(400, fmt.Sprintf("Missing parameter %s", param.name)) } else { - paramValue = param.defValue + paramValue = param.defaultValue } } @@ -43,9 +43,6 @@ func getParamValue(param *MethodParam, ctx *beecontext.Context) string { return string(ctx.Input.RequestBody) case header: return ctx.Input.Header(param.name) - // if strValue == "" && strings.Contains(param.name, "_") { //magically handle X-Headers? - // strValue = ctx.Input.Header(strings.Replace(param.name, "_", "-", -1)) - // } case path: return ctx.Input.Query(":" + param.name) default: diff --git a/context/param/methodparams.go b/context/param/methodparams.go index 28772504..fe4ab421 100644 --- a/context/param/methodparams.go +++ b/context/param/methodparams.go @@ -7,10 +7,10 @@ import ( //MethodParam keeps param information to be auto passed to controller methods type MethodParam struct { - name string - location paramLocation - required bool - defValue string + name string + location paramLocation + required bool + defaultValue string } type paramLocation byte @@ -57,8 +57,8 @@ func (mp *MethodParam) String() string { case header: options = append(options, "param.InHeader") } - if mp.defValue != "" { - options = append(options, fmt.Sprintf(`param.Default("%s")`, mp.defValue)) + if mp.defaultValue != "" { + options = append(options, fmt.Sprintf(`param.Default("%s")`, mp.defaultValue)) } if len(options) > 0 { result += ", " diff --git a/context/param/options.go b/context/param/options.go index 846a59f6..32402194 100644 --- a/context/param/options.go +++ b/context/param/options.go @@ -28,10 +28,10 @@ var InBody MethodParamOption = func(p *MethodParam) { } // Default provides a default value for the http param -func Default(defValue interface{}) MethodParamOption { +func Default(defaultValue interface{}) MethodParamOption { return func(p *MethodParam) { - if defValue != nil { - p.defValue = fmt.Sprint(defValue) + if defaultValue != nil { + p.defaultValue = fmt.Sprint(defaultValue) } } } From 74dc3c750028aee134e648f988b2f270e4985645 Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Thu, 11 May 2017 19:32:44 +0300 Subject: [PATCH 12/21] tests --- context/param/parsers_test.go | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 context/param/parsers_test.go diff --git a/context/param/parsers_test.go b/context/param/parsers_test.go new file mode 100644 index 00000000..6d305018 --- /dev/null +++ b/context/param/parsers_test.go @@ -0,0 +1,45 @@ +package param + +import "testing" +import "reflect" +import "time" + +type testDefinition struct { + strValue string + expectedValue interface{} + expectedParser paramParser +} + +func Test_Parsers(t *testing.T) { + checkParser(testDefinition{"1", 1, intParser{}}, t) + + checkParser(testDefinition{"1.0", 1.0, floatParser{}}, t) + + checkParser(testDefinition{"1", "1", stringParser{}}, t) + + checkParser(testDefinition{"true", true, boolParser{}}, t) + + checkParser(testDefinition{"2017-05-30T13:54:53Z", time.Date(2017, 5, 30, 13, 54, 53, 0, time.UTC), timeParser{}}, t) + + checkParser(testDefinition{`{"X": 5}`, struct{ X int }{5}, jsonParser{}}, t) + + checkParser(testDefinition{`1,2`, []int{1, 2}, sliceParser(intParser{})}, t) +} + +func checkParser(def testDefinition, t *testing.T) { + toType := reflect.TypeOf(def.expectedValue) + parser := getParser(&MethodParam{}, toType) + + if reflect.TypeOf(parser) != reflect.TypeOf(def.expectedParser) { + t.Errorf("Invalid parser for value %v. Expected: %v, actual: %v", def.strValue, reflect.TypeOf(def.expectedParser).Name(), reflect.TypeOf(parser).Name()) + return + } + result, err := parser.parse(def.strValue, toType) + if err != nil { + t.Errorf("Parsing error for value %v. Expected result: %v, error: %v", def.strValue, def.expectedValue, err) + return + } + if !reflect.DeepEqual(result, def.expectedValue) { + t.Errorf("Parsing error for value %v. Expected result: %v, actual: %v", def.strValue, def.expectedValue, result) + } +} From b6a35a8944ee928dbbcc4fbfb3b1760a6c21d5c2 Mon Sep 17 00:00:00 2001 From: eyalpost Date: Fri, 12 May 2017 09:25:12 +0300 Subject: [PATCH 13/21] more tests --- context/param/parsers_test.go | 51 ++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/context/param/parsers_test.go b/context/param/parsers_test.go index 6d305018..cc97c735 100644 --- a/context/param/parsers_test.go +++ b/context/param/parsers_test.go @@ -11,24 +11,58 @@ type testDefinition struct { } func Test_Parsers(t *testing.T) { + + //ints checkParser(testDefinition{"1", 1, intParser{}}, t) + checkParser(testDefinition{"-1", int64(-1), intParser{}}, t) + checkParser(testDefinition{"1", uint64(1), intParser{}}, t) - checkParser(testDefinition{"1.0", 1.0, floatParser{}}, t) + //floats + checkParser(testDefinition{"1.0", float32(1.0), floatParser{}}, t) + checkParser(testDefinition{"-1.0", float64(-1.0), floatParser{}}, t) - checkParser(testDefinition{"1", "1", stringParser{}}, t) + //strings + checkParser(testDefinition{"AB", "AB", stringParser{}}, t) + checkParser(testDefinition{"AB", []byte{65, 66}, stringParser{}}, t) + //bools checkParser(testDefinition{"true", true, boolParser{}}, t) + checkParser(testDefinition{"0", false, boolParser{}}, t) + //timeParser checkParser(testDefinition{"2017-05-30T13:54:53Z", time.Date(2017, 5, 30, 13, 54, 53, 0, time.UTC), timeParser{}}, t) + checkParser(testDefinition{"2017-05-30", time.Date(2017, 5, 30, 0, 0, 0, 0, time.UTC), timeParser{}}, t) - checkParser(testDefinition{`{"X": 5}`, struct{ X int }{5}, jsonParser{}}, t) + //json + checkParser(testDefinition{`{"X": 5, "Y":"Z"}`, struct { + X int + Y string + }{5, "Z"}, jsonParser{}}, t) + //slice in query is parsed as comma delimited checkParser(testDefinition{`1,2`, []int{1, 2}, sliceParser(intParser{})}, t) + + //slice in body is parsed as json + checkParser(testDefinition{`["a","b"]`, []string{"a", "b"}, jsonParser{}}, t, MethodParam{location: body}) + + //pointers + var someInt = 1 + checkParser(testDefinition{`1`, &someInt, ptrParser(intParser{})}, t) + + var someStruct = struct{ X int }{5} + checkParser(testDefinition{`{"X": 5}`, &someStruct, jsonParser{}}, t) + } -func checkParser(def testDefinition, t *testing.T) { +func checkParser(def testDefinition, t *testing.T, methodParam ...MethodParam) { toType := reflect.TypeOf(def.expectedValue) - parser := getParser(&MethodParam{}, toType) + var mp MethodParam + if len(methodParam) == 0 { + mp = MethodParam{} + } else { + mp = methodParam[0] + } + parser := getParser(&mp, toType) if reflect.TypeOf(parser) != reflect.TypeOf(def.expectedParser) { t.Errorf("Invalid parser for value %v. Expected: %v, actual: %v", def.strValue, reflect.TypeOf(def.expectedParser).Name(), reflect.TypeOf(parser).Name()) @@ -39,7 +73,12 @@ func checkParser(def testDefinition, t *testing.T) { t.Errorf("Parsing error for value %v. Expected result: %v, error: %v", def.strValue, def.expectedValue, err) return } - if !reflect.DeepEqual(result, def.expectedValue) { + convResult, err := safeConvert(reflect.ValueOf(result), toType) + if err != nil { + t.Errorf("Convertion error for %v. from value: %v, toType: %v, error: %v", def.strValue, result, toType, err) + return + } + if !reflect.DeepEqual(convResult.Interface(), def.expectedValue) { t.Errorf("Parsing error for value %v. Expected result: %v, actual: %v", def.strValue, def.expectedValue, result) } } From 0ac2e4716248cd0ec1e77e704f394fe87442a674 Mon Sep 17 00:00:00 2001 From: eyalpost Date: Fri, 12 May 2017 09:28:46 +0300 Subject: [PATCH 14/21] location=>paramType --- context/param/conv.go | 2 +- context/param/methodparams.go | 8 ++++---- context/param/options.go | 6 +++--- context/param/parsers.go | 2 +- context/param/parsers_test.go | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/context/param/conv.go b/context/param/conv.go index 596d2b1e..c200e008 100644 --- a/context/param/conv.go +++ b/context/param/conv.go @@ -38,7 +38,7 @@ func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Co } func getParamValue(param *MethodParam, ctx *beecontext.Context) string { - switch param.location { + switch param.in { case body: return string(ctx.Input.RequestBody) case header: diff --git a/context/param/methodparams.go b/context/param/methodparams.go index fe4ab421..cd6708a2 100644 --- a/context/param/methodparams.go +++ b/context/param/methodparams.go @@ -8,15 +8,15 @@ import ( //MethodParam keeps param information to be auto passed to controller methods type MethodParam struct { name string - location paramLocation + in paramType required bool defaultValue string } -type paramLocation byte +type paramType byte const ( - param paramLocation = iota + param paramType = iota path body header @@ -49,7 +49,7 @@ func (mp *MethodParam) String() string { if mp.required { options = append(options, "param.IsRequired") } - switch mp.location { + switch mp.in { case path: options = append(options, "param.InPath") case body: diff --git a/context/param/options.go b/context/param/options.go index 32402194..58bdc3d0 100644 --- a/context/param/options.go +++ b/context/param/options.go @@ -14,17 +14,17 @@ var IsRequired MethodParamOption = func(p *MethodParam) { // InHeader indicates that this param is passed via an http header var InHeader MethodParamOption = func(p *MethodParam) { - p.location = header + p.in = header } // InPath indicates that this param is part of the URL path var InPath MethodParamOption = func(p *MethodParam) { - p.location = path + p.in = path } // InBody indicates that this param is passed as an http request body var InBody MethodParamOption = func(p *MethodParam) { - p.location = body + p.in = body } // Default provides a default value for the http param diff --git a/context/param/parsers.go b/context/param/parsers.go index 2b48e878..421aecf0 100644 --- a/context/param/parsers.go +++ b/context/param/parsers.go @@ -21,7 +21,7 @@ func getParser(param *MethodParam, t reflect.Type) paramParser { if t.Elem().Kind() == reflect.Uint8 { //treat []byte as string return stringParser{} } - if param.location == body { + if param.in == body { return jsonParser{} } elemParser := getParser(param, t.Elem()) diff --git a/context/param/parsers_test.go b/context/param/parsers_test.go index cc97c735..b946ba08 100644 --- a/context/param/parsers_test.go +++ b/context/param/parsers_test.go @@ -43,7 +43,7 @@ func Test_Parsers(t *testing.T) { checkParser(testDefinition{`1,2`, []int{1, 2}, sliceParser(intParser{})}, t) //slice in body is parsed as json - checkParser(testDefinition{`["a","b"]`, []string{"a", "b"}, jsonParser{}}, t, MethodParam{location: body}) + checkParser(testDefinition{`["a","b"]`, []string{"a", "b"}, jsonParser{}}, t, MethodParam{in: body}) //pointers var someInt = 1 From 1004678005135f5d7a6eb940613724ec56a87741 Mon Sep 17 00:00:00 2001 From: eyalpost Date: Fri, 12 May 2017 09:57:56 +0300 Subject: [PATCH 15/21] popular status codes --- context/response/renderer.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/context/response/renderer.go b/context/response/renderer.go index 2a4d2797..38d0cdf6 100644 --- a/context/response/renderer.go +++ b/context/response/renderer.go @@ -3,9 +3,16 @@ package response import ( "strconv" + "net/http" + beecontext "github.com/astaxie/beego/context" ) +const ( + NotFound StatusCode = http.StatusNotFound + BadRequest StatusCode = http.StatusBadRequest +) + // Renderer defines an http response renderer type Renderer interface { Render(ctx *beecontext.Context) From 828cbbdf5d265d521bcc12300d9c750e7df505cc Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Wed, 17 May 2017 20:38:59 +0300 Subject: [PATCH 16/21] Refactor a bit to consolidate packages --- context/context.go | 16 +++++++++++ context/output.go | 13 +++++++++ context/renderer.go | 12 ++++++++ context/response/renderer.go | 46 ------------------------------ context/response/responses.go | 53 ----------------------------------- httpResponse/response.go | 52 ++++++++++++++++++++++++++++++++++ router.go | 3 +- 7 files changed, 94 insertions(+), 101 deletions(-) create mode 100644 context/renderer.go delete mode 100644 context/response/renderer.go delete mode 100644 context/response/responses.go create mode 100644 httpResponse/response.go diff --git a/context/context.go b/context/context.go index 03286097..8b32062c 100644 --- a/context/context.go +++ b/context/context.go @@ -171,6 +171,22 @@ func (ctx *Context) CheckXSRFCookie() bool { return true } +// RenderMethodResult renders the return value of a controller method to the output +func (ctx *Context) RenderMethodResult(result interface{}) { + if result != nil { + renderer, ok := result.(Renderer) + if !ok { + err, ok := result.(error) + if ok { + renderer = errorRenderer(err) + } else { + renderer = jsonRenderer(result) + } + } + renderer.Render(ctx) + } +} + //Response is a wrapper for the http.ResponseWriter //started set to true if response was written to then don't execute other handler type Response struct { diff --git a/context/output.go b/context/output.go index 564ef96d..835552b0 100644 --- a/context/output.go +++ b/context/output.go @@ -168,6 +168,19 @@ func sanitizeValue(v string) string { return cookieValueSanitizer.Replace(v) } +func jsonRenderer(value interface{}) Renderer { + return rendererFunc(func(ctx *Context) { + ctx.Output.JSON(value, false, false) + }) +} + +func errorRenderer(err error) Renderer { + return rendererFunc(func(ctx *Context) { + ctx.Output.SetStatus(500) + ctx.WriteString(err.Error()) + }) +} + // JSON writes json to response body. // if coding is true, it converts utf-8 to \u0000 type. func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) error { diff --git a/context/renderer.go b/context/renderer.go new file mode 100644 index 00000000..36a7cb53 --- /dev/null +++ b/context/renderer.go @@ -0,0 +1,12 @@ +package context + +// Renderer defines an http response renderer +type Renderer interface { + Render(ctx *Context) +} + +type rendererFunc func(ctx *Context) + +func (f rendererFunc) Render(ctx *Context) { + f(ctx) +} diff --git a/context/response/renderer.go b/context/response/renderer.go deleted file mode 100644 index 38d0cdf6..00000000 --- a/context/response/renderer.go +++ /dev/null @@ -1,46 +0,0 @@ -package response - -import ( - "strconv" - - "net/http" - - beecontext "github.com/astaxie/beego/context" -) - -const ( - NotFound StatusCode = http.StatusNotFound - BadRequest StatusCode = http.StatusBadRequest -) - -// Renderer defines an http response renderer -type Renderer interface { - Render(ctx *beecontext.Context) -} - -type rendererFunc func(ctx *beecontext.Context) - -func (f rendererFunc) Render(ctx *beecontext.Context) { - f(ctx) -} - -// StatusCode sets the http response status code -type StatusCode int - -func (s StatusCode) Error() string { - return strconv.Itoa(int(s)) -} - -// Render sets the http status code -func (s StatusCode) Render(ctx *beecontext.Context) { - ctx.Output.SetStatus(int(s)) -} - -type statusCodeWithRender struct { - statusCode int - rendererFunc -} - -func (s statusCodeWithRender) Error() string { - return strconv.Itoa(s.statusCode) -} diff --git a/context/response/responses.go b/context/response/responses.go deleted file mode 100644 index 10d1ed3f..00000000 --- a/context/response/responses.go +++ /dev/null @@ -1,53 +0,0 @@ -package response - -import ( - beecontext "github.com/astaxie/beego/context" -) - -// JSON renders value to the response as JSON -func JSON(value interface{}, encoding ...bool) Renderer { - return rendererFunc(func(ctx *beecontext.Context) { - var ( - hasIndent = true - hasEncoding = false - ) - //TODO: need access to BConfig :( - // if BConfig.RunMode == PROD { - // hasIndent = false - // } - if len(encoding) > 0 && encoding[0] { - hasEncoding = true - } - ctx.Output.JSON(value, hasIndent, hasEncoding) - }) -} - -func errorRenderer(err error) Renderer { - return rendererFunc(func(ctx *beecontext.Context) { - ctx.Output.SetStatus(500) - ctx.WriteString(err.Error()) - }) -} - -// Redirect renders http 302 with a URL -func Redirect(localurl string) error { - return statusCodeWithRender{302, func(ctx *beecontext.Context) { - ctx.Redirect(302, localurl) - }} -} - -// RenderMethodResult renders the return value of a controller method to the output -func RenderMethodResult(result interface{}, ctx *beecontext.Context) { - if result != nil { - renderer, ok := result.(Renderer) - if !ok { - err, ok := result.(error) - if ok { - renderer = errorRenderer(err) - } else { - renderer = JSON(result) - } - } - renderer.Render(ctx) - } -} diff --git a/httpResponse/response.go b/httpResponse/response.go new file mode 100644 index 00000000..ca74b85c --- /dev/null +++ b/httpResponse/response.go @@ -0,0 +1,52 @@ +package httpResponse + +import ( + "strconv" + + "net/http" + + beecontext "github.com/astaxie/beego/context" +) + +const ( + //BadRequest indicates http error 400 + BadRequest StatusCode = http.StatusBadRequest + + //NotFound indicates http error 404 + NotFound StatusCode = http.StatusNotFound +) + +// Redirect renders http 302 with a URL +func Redirect(localurl string) error { + return statusCodeWithRender{302, func(ctx *beecontext.Context) { + ctx.Redirect(302, localurl) + }} +} + +// StatusCode sets the http response status code +type StatusCode int + +func (s StatusCode) Error() string { + return strconv.Itoa(int(s)) +} + +// Render sets the http status code +func (s StatusCode) Render(ctx *beecontext.Context) { + ctx.Output.SetStatus(int(s)) +} + +type statusCodeWithRender struct { + statusCode int + f func(ctx *beecontext.Context) +} + +//assert that statusCodeWithRender implements Renderer interface +var _r beecontext.Renderer = (*statusCodeWithRender)(nil) + +func (s statusCodeWithRender) Error() string { + return strconv.Itoa(s.statusCode) +} + +func (s statusCodeWithRender) Render(ctx *beecontext.Context) { + s.f(ctx) +} diff --git a/router.go b/router.go index 33096039..72476ae8 100644 --- a/router.go +++ b/router.go @@ -28,7 +28,6 @@ import ( beecontext "github.com/astaxie/beego/context" "github.com/astaxie/beego/context/param" - "github.com/astaxie/beego/context/response" "github.com/astaxie/beego/logs" "github.com/astaxie/beego/toolbox" "github.com/astaxie/beego/utils" @@ -905,7 +904,7 @@ func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, ex result := results[i] if result.Kind() != reflect.Interface || !result.IsNil() { resultValue := result.Interface() - response.RenderMethodResult(resultValue, context) + context.RenderMethodResult(resultValue) } } if !context.ResponseWriter.Started && context.Output.Status == 0 { From ee1d8bc30ea2d89ecdb70402c7b1213ac8a4e5f1 Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Wed, 17 May 2017 20:50:41 +0300 Subject: [PATCH 17/21] fix gosimple --- parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 037d5376..a9cfd894 100644 --- a/parser.go +++ b/parser.go @@ -235,7 +235,7 @@ func getparams(str string) []string { var start bool var r []string var quoted int8 - for _, c := range []rune(str) { + for _, c := range str { if unicode.IsSpace(c) && quoted == 0 { if !start { continue From e32a18203b95391f627539c440834d65f704492a Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Wed, 17 May 2017 21:27:32 +0300 Subject: [PATCH 18/21] fix gosimple --- context/output.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/context/output.go b/context/output.go index 835552b0..cf9e7a7e 100644 --- a/context/output.go +++ b/context/output.go @@ -343,9 +343,8 @@ func (output *BeegoOutput) IsServerError() bool { } func stringsToJSON(str string) string { - rs := []rune(str) var jsons bytes.Buffer - for _, r := range rs { + for _, r := range str { rint := int(r) if rint < 128 { jsons.WriteRune(r) From 3e51823c0faba9dcf541ec72c90664a30fedceb6 Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Thu, 18 May 2017 09:05:49 +0300 Subject: [PATCH 19/21] move response --- {httpResponse => context/httpResponse}/response.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {httpResponse => context/httpResponse}/response.go (100%) diff --git a/httpResponse/response.go b/context/httpResponse/response.go similarity index 100% rename from httpResponse/response.go rename to context/httpResponse/response.go From 2513bcf584f655379bd5195dcaba83649aab9e8f Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Thu, 18 May 2017 10:32:51 +0300 Subject: [PATCH 20/21] remove Redirect to avoid confusion --- context/httpResponse/response.go | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/context/httpResponse/response.go b/context/httpResponse/response.go index ca74b85c..0b013719 100644 --- a/context/httpResponse/response.go +++ b/context/httpResponse/response.go @@ -16,13 +16,6 @@ const ( NotFound StatusCode = http.StatusNotFound ) -// Redirect renders http 302 with a URL -func Redirect(localurl string) error { - return statusCodeWithRender{302, func(ctx *beecontext.Context) { - ctx.Redirect(302, localurl) - }} -} - // StatusCode sets the http response status code type StatusCode int @@ -34,19 +27,3 @@ func (s StatusCode) Error() string { func (s StatusCode) Render(ctx *beecontext.Context) { ctx.Output.SetStatus(int(s)) } - -type statusCodeWithRender struct { - statusCode int - f func(ctx *beecontext.Context) -} - -//assert that statusCodeWithRender implements Renderer interface -var _r beecontext.Renderer = (*statusCodeWithRender)(nil) - -func (s statusCodeWithRender) Error() string { - return strconv.Itoa(s.statusCode) -} - -func (s statusCodeWithRender) Render(ctx *beecontext.Context) { - s.f(ctx) -} From 11b4bf8aaa7ba08f7ca0aba61ebf1cee0c7d83a6 Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Thu, 18 May 2017 10:38:12 +0300 Subject: [PATCH 21/21] move to context --- context/{httpResponse => }/response.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) rename context/{httpResponse => }/response.go (77%) diff --git a/context/httpResponse/response.go b/context/response.go similarity index 77% rename from context/httpResponse/response.go rename to context/response.go index 0b013719..9c3c715a 100644 --- a/context/httpResponse/response.go +++ b/context/response.go @@ -1,11 +1,9 @@ -package httpResponse +package context import ( "strconv" "net/http" - - beecontext "github.com/astaxie/beego/context" ) const ( @@ -24,6 +22,6 @@ func (s StatusCode) Error() string { } // Render sets the http status code -func (s StatusCode) Render(ctx *beecontext.Context) { +func (s StatusCode) Render(ctx *Context) { ctx.Output.SetStatus(int(s)) }