mirror of
https://github.com/astaxie/beego.git
synced 2025-06-11 14:10:39 +00:00
Move package
This commit is contained in:
78
pkg/context/param/conv.go
Normal file
78
pkg/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
pkg/context/param/methodparams.go
Normal file
69
pkg/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
pkg/context/param/options.go
Normal file
37
pkg/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 omitted 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
pkg/context/param/parsers.go
Normal file
149
pkg/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
pkg/context/param/parsers_test.go
Normal file
84
pkg/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("Conversion 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)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user