improve package ghttp and internal/structs

This commit is contained in:
Jack
2020-10-22 15:16:31 +08:00
parent ab689a7792
commit 9c3b978b50
7 changed files with 184 additions and 29 deletions

View File

@ -22,6 +22,21 @@ import (
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func MapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
// If <pointer> points to an invalid address, for example a nil variable,
// it here creates an empty struct using reflect feature.
var (
tempValue reflect.Value
pointerValue = reflect.ValueOf(pointer)
)
for pointerValue.Kind() == reflect.Ptr {
tempValue = pointerValue.Elem()
if !tempValue.IsValid() {
pointer = reflect.New(pointerValue.Type().Elem()).Elem()
break
} else {
pointerValue = tempValue
}
}
var (
fields []*structs.Field
fieldMap = make(map[string]*Field)
@ -59,8 +74,10 @@ func MapField(pointer interface{}, priority []string, recursive bool) map[string
}
}
if recursive {
rv := reflect.ValueOf(field.Value())
kind := rv.Kind()
var (
rv = reflect.ValueOf(field.Value())
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()

View File

@ -27,6 +27,21 @@ func TagFields(pointer interface{}, priority []string, recursive bool) []*Field
// tag internally.
// The parameter <pointer> should be type of struct/*struct.
func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap map[string]struct{}) []*Field {
// If <pointer> points to an invalid address, for example a nil variable,
// it here creates an empty struct using reflect feature.
var (
tempValue reflect.Value
pointerValue = reflect.ValueOf(pointer)
)
for pointerValue.Kind() == reflect.Ptr {
tempValue = pointerValue.Elem()
if !tempValue.IsValid() {
pointer = reflect.New(pointerValue.Type().Elem()).Elem()
break
} else {
pointerValue = tempValue
}
}
var fields []*structs.Field
if v, ok := pointer.(reflect.Value); ok {
fields = structs.Fields(v.Interface())

View File

@ -71,3 +71,20 @@ func Test_Basic(t *testing.T) {
t.Assert(structs.TagMapName(user2, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"})
})
}
func Test_StructOfNilPointer(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Name string `params:"name"`
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
}
var user *User
t.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}, true), g.Map{"name": "Name", "pass": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}, true), g.Map{"name": "Name", "pass1": "Pass"})
t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}, true), g.Map{"name": "Name", "pass2": "Pass"})
})
}

View File

@ -25,6 +25,12 @@ import (
"strings"
)
const (
parseTypeRequest = 0
parseTypeQuery = 1
parseTypeForm = 2
)
var (
// xmlHeaderBytes is the most common XML format header.
xmlHeaderBytes = []byte("<?xml")
@ -37,11 +43,26 @@ var (
// The parameter <pointer> can be type of: *struct/**struct/*[]struct/*[]*struct.
//
// It supports single and multiple struct convertion:
// 1. Single struct, post content like: {"id":1, "name":"john"}
// 1. Single struct, post content like: {"id":1, "name":"john"} or ?id=1&name=john
// 2. Multiple struct, post content like: [{"id":1, "name":"john"}, {"id":, "name":"smith"}]
//
// TODO: Improve the performance by reducing duplicated reflect usage on the same variable across packages.
func (r *Request) Parse(pointer interface{}) error {
return r.doParse(pointer, parseTypeRequest)
}
// ParseQuery performs like function Parse, but only parses the query parameters.
func (r *Request) ParseQuery(pointer interface{}) error {
return r.doParse(pointer, parseTypeQuery)
}
// ParseForm performs like function Parse, but only parses the form parameters or the body content.
func (r *Request) ParseForm(pointer interface{}) error {
return r.doParse(pointer, parseTypeForm)
}
// doParse parses the request data to struct/structs according to request type.
func (r *Request) doParse(pointer interface{}, requestType int) error {
var (
reflectVal1 = reflect.ValueOf(pointer)
reflectKind1 = reflectVal1.Kind()
@ -58,18 +79,31 @@ func (r *Request) Parse(pointer interface{}) error {
)
switch reflectKind2 {
// Single struct, post content like:
// {"id":1, "name":"john"}
// 1. {"id":1, "name":"john"}
// 2. ?id=1&name=john
case reflect.Ptr, reflect.Struct:
// Conversion.
if err := r.GetStruct(pointer); err != nil {
return err
// Converting.
switch requestType {
case parseTypeQuery:
if err := r.GetQueryStruct(pointer); err != nil {
return err
}
case parseTypeForm:
if err := r.GetFormStruct(pointer); err != nil {
return err
}
default:
if err := r.GetStruct(pointer); err != nil {
return err
}
}
// Validation.
if err := gvalid.CheckStruct(pointer, nil); err != nil {
return err
}
// Multiple struct, post content like:
// Multiple struct, it only supports JSON type post content like:
// [{"id":1, "name":"john"}, {"id":, "name":"smith"}]
case reflect.Array, reflect.Slice:
// If struct slice conversion, it might post JSON/XML content,
@ -371,11 +405,14 @@ func (r *Request) parseForm() {
}
}
}
if r.formMap == nil {
}
// It parses the request body without checking the Content-Type.
if r.formMap == nil {
if r.Method != "GET" {
r.parseBody()
if len(r.bodyMap) > 0 {
r.formMap = r.bodyMap
}
}
if len(r.bodyMap) > 0 {
r.formMap = r.bodyMap
}
}
}

View File

@ -34,7 +34,9 @@ func (r *Request) GetQuery(key string, def ...interface{}) interface{} {
return v
}
}
r.parseBody()
if r.Method == "GET" {
r.parseBody()
}
if len(r.bodyMap) > 0 {
if v, ok := r.bodyMap[key]; ok {
return v
@ -118,7 +120,9 @@ func (r *Request) GetQueryInterfaces(key string, def ...interface{}) []interface
// in order of priority: query > body.
func (r *Request) GetQueryMap(kvMap ...map[string]interface{}) map[string]interface{} {
r.parseQuery()
r.parseBody()
if r.Method == "GET" {
r.parseBody()
}
var m map[string]interface{}
if len(kvMap) > 0 && kvMap[0] != nil {
if len(r.queryMap) == 0 && len(r.bodyMap) == 0 {

View File

@ -27,13 +27,11 @@ func Test_Params_Parse(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/parse", func(r *ghttp.Request) {
if m := r.GetMap(); len(m) > 0 {
var user *User
if err := r.Parse(&user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user.Map["id"], user.Map["score"])
var user *User
if err := r.Parse(&user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user.Map["id"], user.Map["score"])
})
s.SetPort(p)
s.SetDumpRouterMap(false)
@ -48,7 +46,76 @@ func Test_Params_Parse(t *testing.T) {
})
}
func Test_Params_Parse2(t *testing.T) {
func Test_Params_ParseQuery(t *testing.T) {
type User struct {
Id int
Name string
}
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/parse-query", func(r *ghttp.Request) {
var user *User
if err := r.ParseQuery(&user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user.Id, user.Name)
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(c.GetContent("/parse-query"), `0`)
t.Assert(c.GetContent("/parse-query?id=1&name=john"), `1john`)
t.Assert(c.PostContent("/parse-query"), `0`)
t.Assert(c.PostContent("/parse-query", g.Map{
"id": 1,
"name": "john",
}), `0`)
})
}
func Test_Params_ParseForm(t *testing.T) {
type User struct {
Id int
Name string
}
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/parse-form", func(r *ghttp.Request) {
var user *User
if err := r.ParseForm(&user); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(user.Id, user.Name)
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(c.GetContent("/parse-form"), `0`)
t.Assert(c.GetContent("/parse-form", g.Map{
"id": 1,
"name": "john",
}), 0)
t.Assert(c.PostContent("/parse-form"), `0`)
t.Assert(c.PostContent("/parse-form", g.Map{
"id": 1,
"name": "john",
}), `1john`)
})
}
func Test_Params_ComplexJsonStruct(t *testing.T) {
type ItemEnv struct {
Type string
Key string

View File

@ -433,10 +433,10 @@ func Test_Params_SupportChars(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/form-value", func(r *ghttp.Request) {
r.Response.Write(r.GetQuery("test-value"))
r.Response.Write(r.GetForm("test-value"))
})
s.BindHandler("/form-array", func(r *ghttp.Request) {
r.Response.Write(r.GetQuery("test-array"))
r.Response.Write(r.GetForm("test-array"))
})
s.SetPort(p)
s.SetDumpRouterMap(false)
@ -445,12 +445,10 @@ func Test_Params_SupportChars(t *testing.T) {
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
prefix := fmt.Sprintf("http://127.0.0.1:%d", p)
client := ghttp.NewClient()
client.SetPrefix(prefix)
t.Assert(client.PostContent("/form-value", "test-value=100"), "100")
t.Assert(client.PostContent("/form-array", "test-array[]=1&test-array[]=2"), `["1","2"]`)
c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(c.PostContent("/form-value", "test-value=100"), "100")
t.Assert(c.PostContent("/form-array", "test-array[]=1&test-array[]=2"), `["1","2"]`)
})
}