mirror of
https://github.com/astaxie/beego.git
synced 2024-11-23 05:20:57 +00:00
Merge pull request #2586 from eyalpost/develop
Automatic Parameter Router
This commit is contained in:
commit
655484b4df
2
cache/conv.go
vendored
2
cache/conv.go
vendored
@ -28,7 +28,7 @@ func GetString(v interface{}) string {
|
|||||||
return string(result)
|
return string(result)
|
||||||
default:
|
default:
|
||||||
if v != nil {
|
if v != nil {
|
||||||
return fmt.Sprintf("%v", result)
|
return fmt.Sprint(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
@ -171,6 +171,22 @@ func (ctx *Context) CheckXSRFCookie() bool {
|
|||||||
return true
|
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
|
//Response is a wrapper for the http.ResponseWriter
|
||||||
//started set to true if response was written to then don't execute other handler
|
//started set to true if response was written to then don't execute other handler
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
@ -168,6 +168,19 @@ func sanitizeValue(v string) string {
|
|||||||
return cookieValueSanitizer.Replace(v)
|
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.
|
// JSON writes json to response body.
|
||||||
// if coding is true, it converts utf-8 to \u0000 type.
|
// if coding is true, it converts utf-8 to \u0000 type.
|
||||||
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) error {
|
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) error {
|
||||||
@ -330,9 +343,8 @@ func (output *BeegoOutput) IsServerError() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func stringsToJSON(str string) string {
|
func stringsToJSON(str string) string {
|
||||||
rs := []rune(str)
|
|
||||||
var jsons bytes.Buffer
|
var jsons bytes.Buffer
|
||||||
for _, r := range rs {
|
for _, r := range str {
|
||||||
rint := int(r)
|
rint := int(r)
|
||||||
if rint < 128 {
|
if rint < 128 {
|
||||||
jsons.WriteRune(r)
|
jsons.WriteRune(r)
|
||||||
|
78
context/param/conv.go
Normal file
78
context/param/conv.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
beecontext "github.com/astaxie/beego/context"
|
||||||
|
"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++ {
|
||||||
|
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 == "" {
|
||||||
|
if param.required {
|
||||||
|
ctx.Abort(400, fmt.Sprintf("Missing parameter %s", param.name))
|
||||||
|
} else {
|
||||||
|
paramValue = param.defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflectValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getParamValue(param *MethodParam, ctx *beecontext.Context) string {
|
||||||
|
switch param.in {
|
||||||
|
case body:
|
||||||
|
return string(ctx.Input.RequestBody)
|
||||||
|
case header:
|
||||||
|
return ctx.Input.Header(param.name)
|
||||||
|
case path:
|
||||||
|
return ctx.Input.Query(":" + param.name)
|
||||||
|
default:
|
||||||
|
return ctx.Input.Query(param.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseValue(param *MethodParam, paramValue string, paramType reflect.Type) (result reflect.Value, err error) {
|
||||||
|
if paramValue == "" {
|
||||||
|
return reflect.Zero(paramType), nil
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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
|
||||||
|
}
|
69
context/param/methodparams.go
Normal file
69
context/param/methodparams.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//MethodParam keeps param information to be auto passed to controller methods
|
||||||
|
type MethodParam struct {
|
||||||
|
name string
|
||||||
|
in paramType
|
||||||
|
required bool
|
||||||
|
defaultValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
type paramType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
param paramType = iota
|
||||||
|
path
|
||||||
|
body
|
||||||
|
header
|
||||||
|
)
|
||||||
|
|
||||||
|
//New creates a new MethodParam with name and specific options
|
||||||
|
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}
|
||||||
|
for _, option := range opts {
|
||||||
|
option(param)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Make creates an array of MethodParmas or an empty array
|
||||||
|
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.in {
|
||||||
|
case path:
|
||||||
|
options = append(options, "param.InPath")
|
||||||
|
case body:
|
||||||
|
options = append(options, "param.InBody")
|
||||||
|
case header:
|
||||||
|
options = append(options, "param.InHeader")
|
||||||
|
}
|
||||||
|
if mp.defaultValue != "" {
|
||||||
|
options = append(options, fmt.Sprintf(`param.Default("%s")`, mp.defaultValue))
|
||||||
|
}
|
||||||
|
if len(options) > 0 {
|
||||||
|
result += ", "
|
||||||
|
}
|
||||||
|
result += strings.Join(options, ", ")
|
||||||
|
result += ")"
|
||||||
|
return result
|
||||||
|
}
|
37
context/param/options.go
Normal file
37
context/param/options.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
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.in = header
|
||||||
|
}
|
||||||
|
|
||||||
|
// InPath indicates that this param is part of the URL path
|
||||||
|
var InPath MethodParamOption = func(p *MethodParam) {
|
||||||
|
p.in = path
|
||||||
|
}
|
||||||
|
|
||||||
|
// InBody indicates that this param is passed as an http request body
|
||||||
|
var InBody MethodParamOption = func(p *MethodParam) {
|
||||||
|
p.in = body
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default provides a default value for the http param
|
||||||
|
func Default(defaultValue interface{}) MethodParamOption {
|
||||||
|
return func(p *MethodParam) {
|
||||||
|
if defaultValue != nil {
|
||||||
|
p.defaultValue = fmt.Sprint(defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
149
context/param/parsers.go
Normal file
149
context/param/parsers.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type paramParser interface {
|
||||||
|
parse(value string, toType reflect.Type) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
return intParser{}
|
||||||
|
case reflect.Slice:
|
||||||
|
if t.Elem().Kind() == reflect.Uint8 { //treat []byte as string
|
||||||
|
return stringParser{}
|
||||||
|
}
|
||||||
|
if param.in == body {
|
||||||
|
return jsonParser{}
|
||||||
|
}
|
||||||
|
elemParser := getParser(param, 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(param, 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) {
|
||||||
|
return f(value, toType)
|
||||||
|
}
|
||||||
|
|
||||||
|
type boolParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p boolParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
return strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p stringParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type intParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p intParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
return strconv.Atoi(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type floatParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p floatParser) parse(value string, toType reflect.Type) (interface{}, error) {
|
||||||
|
if toType.Kind() == reflect.Float32 {
|
||||||
|
res, err := strconv.ParseFloat(value, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return float32(res), nil
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
84
context/param/parsers_test.go
Normal file
84
context/param/parsers_test.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package param
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
import "reflect"
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type testDefinition struct {
|
||||||
|
strValue string
|
||||||
|
expectedValue interface{}
|
||||||
|
expectedParser paramParser
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
//floats
|
||||||
|
checkParser(testDefinition{"1.0", float32(1.0), floatParser{}}, t)
|
||||||
|
checkParser(testDefinition{"-1.0", float64(-1.0), floatParser{}}, 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)
|
||||||
|
|
||||||
|
//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{in: 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, methodParam ...MethodParam) {
|
||||||
|
toType := reflect.TypeOf(def.expectedValue)
|
||||||
|
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())
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
12
context/renderer.go
Normal file
12
context/renderer.go
Normal file
@ -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)
|
||||||
|
}
|
27
context/response.go
Normal file
27
context/response.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
//BadRequest indicates http error 400
|
||||||
|
BadRequest StatusCode = http.StatusBadRequest
|
||||||
|
|
||||||
|
//NotFound indicates http error 404
|
||||||
|
NotFound StatusCode = http.StatusNotFound
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 *Context) {
|
||||||
|
ctx.Output.SetStatus(int(s))
|
||||||
|
}
|
@ -28,6 +28,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
|
"github.com/astaxie/beego/context/param"
|
||||||
"github.com/astaxie/beego/session"
|
"github.com/astaxie/beego/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ type ControllerComments struct {
|
|||||||
Router string
|
Router string
|
||||||
AllowHTTPMethods []string
|
AllowHTTPMethods []string
|
||||||
Params []map[string]string
|
Params []map[string]string
|
||||||
|
MethodParams []*param.MethodParam
|
||||||
}
|
}
|
||||||
|
|
||||||
// Controller defines some basic http request handler operations, such as
|
// Controller defines some basic http request handler operations, such as
|
||||||
|
202
parser.go
202
parser.go
@ -24,9 +24,13 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/context/param"
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
"github.com/astaxie/beego/utils"
|
"github.com/astaxie/beego/utils"
|
||||||
)
|
)
|
||||||
@ -35,6 +39,7 @@ var globalRouterTemplate = `package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/astaxie/beego/context/param"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -81,7 +86,7 @@ func parserPkg(pkgRealpath, pkgpath string) error {
|
|||||||
if specDecl.Recv != nil {
|
if specDecl.Recv != nil {
|
||||||
exp, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr) // Check that the type is correct first beforing throwing to parser
|
exp, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr) // Check that the type is correct first beforing throwing to parser
|
||||||
if ok {
|
if ok {
|
||||||
parserComments(specDecl.Doc, specDecl.Name.String(), fmt.Sprint(exp.X), pkgpath)
|
parserComments(specDecl, fmt.Sprint(exp.X), pkgpath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,44 +98,169 @@ func parserPkg(pkgRealpath, pkgpath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error {
|
type parsedComment struct {
|
||||||
if comments != nil && comments.List != nil {
|
routerPath string
|
||||||
for _, c := range comments.List {
|
methods []string
|
||||||
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
|
params map[string]parsedParam
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if parsedComment.routerPath != "" {
|
||||||
key := pkgpath + ":" + controllerName
|
key := pkgpath + ":" + controllerName
|
||||||
cc := ControllerComments{}
|
cc := ControllerComments{}
|
||||||
cc.Method = funcName
|
cc.Method = f.Name.String()
|
||||||
cc.Router = e1[0]
|
cc.Router = parsedComment.routerPath
|
||||||
if len(e1) == 2 && e1[1] != "" {
|
cc.AllowHTTPMethods = parsedComment.methods
|
||||||
e1 = strings.SplitN(e1[1], " ", 2)
|
cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment)
|
||||||
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)
|
genInfoList[key] = append(genInfoList[key], cc)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
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.SplitN(pv[0], "=>", 2)
|
||||||
|
p.name = names[0]
|
||||||
|
funcParamName := p.name
|
||||||
|
if len(names) > 1 {
|
||||||
|
funcParamName = names[1]
|
||||||
|
}
|
||||||
|
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 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) {
|
func genRouterCode(pkgRealpath string) {
|
||||||
os.Mkdir(getRouterDir(pkgRealpath), 0755)
|
os.Mkdir(getRouterDir(pkgRealpath), 0755)
|
||||||
logs.Info("generate router from comments")
|
logs.Info("generate router from comments")
|
||||||
@ -163,12 +293,24 @@ func genRouterCode(pkgRealpath string) {
|
|||||||
}
|
}
|
||||||
params = strings.TrimRight(params, ",") + "}"
|
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 + `
|
globalinfo = globalinfo + `
|
||||||
beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
|
beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
|
||||||
beego.ControllerComments{
|
beego.ControllerComments{
|
||||||
Method: "` + strings.TrimSpace(c.Method) + `",
|
Method: "` + strings.TrimSpace(c.Method) + `",
|
||||||
` + "Router: `" + c.Router + "`" + `,
|
` + "Router: `" + c.Router + "`" + `,
|
||||||
AllowHTTPMethods: ` + allmethod + `,
|
AllowHTTPMethods: ` + allmethod + `,
|
||||||
|
MethodParams: ` + methodParams + `,
|
||||||
Params: ` + params + `})
|
Params: ` + params + `})
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
34
router.go
34
router.go
@ -27,6 +27,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
beecontext "github.com/astaxie/beego/context"
|
beecontext "github.com/astaxie/beego/context"
|
||||||
|
"github.com/astaxie/beego/context/param"
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
"github.com/astaxie/beego/toolbox"
|
"github.com/astaxie/beego/toolbox"
|
||||||
"github.com/astaxie/beego/utils"
|
"github.com/astaxie/beego/utils"
|
||||||
@ -116,6 +117,7 @@ type ControllerInfo struct {
|
|||||||
handler http.Handler
|
handler http.Handler
|
||||||
runFunction FilterFunc
|
runFunction FilterFunc
|
||||||
routerType int
|
routerType int
|
||||||
|
methodParams []*param.MethodParam
|
||||||
}
|
}
|
||||||
|
|
||||||
// ControllerRegister containers registered router rules, controller handlers and filters.
|
// ControllerRegister containers registered router rules, controller handlers and filters.
|
||||||
@ -151,6 +153,10 @@ func NewControllerRegister() *ControllerRegister {
|
|||||||
// Add("/api",&RestController{},"get,post:ApiFunc"
|
// Add("/api",&RestController{},"get,post:ApiFunc"
|
||||||
// Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc")
|
// Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc")
|
||||||
func (p *ControllerRegister) Add(pattern string, c ControllerInterface, mappingMethods ...string) {
|
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)
|
reflectVal := reflect.ValueOf(c)
|
||||||
t := reflect.Indirect(reflectVal).Type()
|
t := reflect.Indirect(reflectVal).Type()
|
||||||
methods := make(map[string]string)
|
methods := make(map[string]string)
|
||||||
@ -181,6 +187,7 @@ func (p *ControllerRegister) Add(pattern string, c ControllerInterface, mappingM
|
|||||||
route.methods = methods
|
route.methods = methods
|
||||||
route.routerType = routerTypeBeego
|
route.routerType = routerTypeBeego
|
||||||
route.controllerType = t
|
route.controllerType = t
|
||||||
|
route.methodParams = methodParams
|
||||||
if len(methods) == 0 {
|
if len(methods) == 0 {
|
||||||
for _, m := range HTTPMETHOD {
|
for _, m := range HTTPMETHOD {
|
||||||
p.addToRouter(m, pattern, route)
|
p.addToRouter(m, pattern, route)
|
||||||
@ -245,7 +252,7 @@ func (p *ControllerRegister) Include(cList ...ControllerInterface) {
|
|||||||
key := t.PkgPath() + ":" + t.Name()
|
key := t.PkgPath() + ":" + t.Name()
|
||||||
if comm, ok := GlobalControllerRouter[key]; ok {
|
if comm, ok := GlobalControllerRouter[key]; ok {
|
||||||
for _, a := range comm {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -627,6 +634,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
runRouter reflect.Type
|
runRouter reflect.Type
|
||||||
findRouter bool
|
findRouter bool
|
||||||
runMethod string
|
runMethod string
|
||||||
|
methodParams []*param.MethodParam
|
||||||
routerInfo *ControllerInfo
|
routerInfo *ControllerInfo
|
||||||
isRunnable bool
|
isRunnable bool
|
||||||
)
|
)
|
||||||
@ -740,6 +748,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
routerInfo.handler.ServeHTTP(rw, r)
|
routerInfo.handler.ServeHTTP(rw, r)
|
||||||
} else {
|
} else {
|
||||||
runRouter = routerInfo.controllerType
|
runRouter = routerInfo.controllerType
|
||||||
|
methodParams = routerInfo.methodParams
|
||||||
method := r.Method
|
method := r.Method
|
||||||
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPost {
|
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPost {
|
||||||
method = http.MethodPut
|
method = http.MethodPut
|
||||||
@ -802,9 +811,14 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
execController.Options()
|
execController.Options()
|
||||||
default:
|
default:
|
||||||
if !execController.HandlerFunc(runMethod) {
|
if !execController.HandlerFunc(runMethod) {
|
||||||
var in []reflect.Value
|
|
||||||
method := vc.MethodByName(runMethod)
|
method := vc.MethodByName(runMethod)
|
||||||
method.Call(in)
|
in := 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -884,6 +898,20 @@ 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.Kind() != reflect.Interface || !result.IsNil() {
|
||||||
|
resultValue := result.Interface()
|
||||||
|
context.RenderMethodResult(resultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !context.ResponseWriter.Started && context.Output.Status == 0 {
|
||||||
|
context.Output.SetStatus(200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FindRouter Find Router info for URL
|
// FindRouter Find Router info for URL
|
||||||
func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo *ControllerInfo, isFind bool) {
|
func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo *ControllerInfo, isFind bool) {
|
||||||
var urlPath = context.Input.URL()
|
var urlPath = context.Input.URL()
|
||||||
|
Loading…
Reference in New Issue
Block a user