1
0
mirror of https://github.com/gogf/gf.git synced 2025-04-05 03:05:05 +08:00

fix(net/ghttp):check parameter existence to determine using default or front-end value. (#4182)

This commit is contained in:
River 2025-03-08 20:56:27 +08:00 committed by GitHub
parent a8a055f122
commit bcda48bf82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 168 additions and 63 deletions

View File

@ -107,7 +107,6 @@ func (r *Request) doParse(pointer interface{}, requestType int) error {
return err
}
}
// TODO: https://github.com/gogf/gf/pull/2450
// Validation.
if err = gvalid.New().
Bail().

View File

@ -8,7 +8,6 @@ package ghttp
import (
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/net/goai"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/util/gconv"
@ -178,15 +177,17 @@ func (r *Request) doGetRequestStruct(pointer interface{}, mapping ...map[string]
if data == nil {
data = map[string]interface{}{}
}
// Default struct values.
if err = r.mergeDefaultStructValue(data, pointer); err != nil {
return data, nil
}
// `in` Tag Struct values.
if err = r.mergeInTagStructValue(data); err != nil {
return data, nil
}
// Default struct values.
if err = r.mergeDefaultStructValue(data, pointer); err != nil {
return data, nil
}
return data, gconv.Struct(data, pointer, mapping...)
}
@ -194,20 +195,9 @@ func (r *Request) doGetRequestStruct(pointer interface{}, mapping ...map[string]
func (r *Request) mergeDefaultStructValue(data map[string]interface{}, pointer interface{}) error {
fields := r.serveHandler.Handler.Info.ReqStructFields
if len(fields) > 0 {
var (
foundKey string
foundValue interface{}
)
for _, field := range fields {
if tagValue := field.TagDefault(); tagValue != "" {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
if foundKey == "" {
data[field.Name()] = tagValue
} else {
if empty.IsEmpty(foundValue) {
data[foundKey] = tagValue
}
}
mergeTagValueWithFoundKey(data, false, field.Name(), field.Name(), tagValue)
}
}
return nil
@ -219,19 +209,8 @@ func (r *Request) mergeDefaultStructValue(data map[string]interface{}, pointer i
return err
}
if len(tagFields) > 0 {
var (
foundKey string
foundValue interface{}
)
for _, field := range tagFields {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
if foundKey == "" {
data[field.Name()] = field.TagValue
} else {
if empty.IsEmpty(foundValue) {
data[foundKey] = field.TagValue
}
}
mergeTagValueWithFoundKey(data, false, field.Name(), field.Name(), field.TagValue)
}
}
@ -261,34 +240,29 @@ func (r *Request) mergeInTagStructValue(data map[string]interface{}) error {
for _, field := range fields {
if tagValue := field.TagIn(); tagValue != "" {
findKey := field.TagPriorityName()
switch tagValue {
case goai.ParameterInHeader:
foundHeaderKey, foundHeaderValue := gutil.MapPossibleItemByKey(headerMap, field.TagPriorityName())
if foundHeaderKey != "" {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundHeaderKey)
if foundKey == "" {
data[field.Name()] = foundHeaderValue
} else {
if empty.IsEmpty(foundValue) {
data[foundKey] = foundHeaderValue
}
}
}
foundKey, foundValue = gutil.MapPossibleItemByKey(headerMap, findKey)
case goai.ParameterInCookie:
foundCookieKey, foundCookieValue := gutil.MapPossibleItemByKey(cookieMap, field.TagPriorityName())
if foundCookieKey != "" {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundCookieKey)
if foundKey == "" {
data[field.Name()] = foundCookieValue
} else {
if empty.IsEmpty(foundValue) {
data[foundKey] = foundCookieValue
}
}
}
foundKey, foundValue = gutil.MapPossibleItemByKey(cookieMap, findKey)
}
if foundKey != "" {
mergeTagValueWithFoundKey(data, true, foundKey, field.Name(), foundValue)
}
}
}
}
return nil
}
// mergeTagValueWithFoundKey merges the request parameters when the key does not exist in the map or overwritten is true or the value is nil.
func mergeTagValueWithFoundKey(data map[string]interface{}, overwritten bool, findKey string, fieldName string, tagValue interface{}) {
if foundKey, foundValue := gutil.MapPossibleItemByKey(data, findKey); foundKey == "" {
data[fieldName] = tagValue
} else {
if overwritten || foundValue == nil {
data[foundKey] = tagValue
}
}
}

View File

@ -13,34 +13,34 @@ import (
"github.com/gogf/gf/v2/util/guid"
)
type UserReq struct {
// UserTagInReq struct tag "in" supports: header, cookie
type UserTagInReq struct {
g.Meta `path:"/user" tags:"User" method:"post" summary:"user api" title:"api title"`
Id int `v:"required" d:"1"`
Name string `v:"required" in:"cookie"`
Age string `v:"required" in:"header"`
// header,query,cookie,form
}
type UserRes struct {
type UserTagInRes struct {
g.Meta `mime:"text/html" example:"string"`
}
var (
User = cUser{}
UserTagIn = cUserTagIn{}
)
type cUser struct{}
type cUserTagIn struct{}
func (c *cUser) User(ctx context.Context, req *UserReq) (res *UserRes, err error) {
func (c *cUserTagIn) User(ctx context.Context, req *UserTagInReq) (res *UserTagInRes, err error) {
g.RequestFromCtx(ctx).Response.WriteJson(req)
return
}
func Test_Params_Tag(t *testing.T) {
func Test_ParamsTagIn(t *testing.T) {
s := g.Server(guid.S())
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(User)
group.Bind(UserTagIn)
})
s.SetDumpRouterMap(false)
s.Start()
@ -56,17 +56,101 @@ func Test_Params_Tag(t *testing.T) {
client.SetHeader("age", "18")
t.Assert(client.PostContent(ctx, "/user"), `{"Id":1,"Name":"john","Age":"18"}`)
t.Assert(client.PostContent(ctx, "/user", "name=&age=&id="), `{"Id":1,"Name":"john","Age":"18"}`)
t.Assert(client.PostContent(ctx, "/user", "name=&age="), `{"Id":1,"Name":"john","Age":"18"}`)
})
}
func Benchmark_ParamTag(b *testing.B) {
type UserTagDefaultReq struct {
g.Meta `path:"/user-default" method:"post,get" summary:"user default tag api"`
Id int `v:"required" d:"1"`
Name string `d:"john"`
Age int `d:"18"`
Score float64 `d:"99.9"`
IsVip bool `d:"true"`
NickName string `p:"nickname" d:"nickname-default"`
EmptyStr string `d:""`
Email string
Address string
}
type UserTagDefaultRes struct {
g.Meta `mime:"application/json" example:"string"`
}
var (
UserTagDefault = cUserTagDefault{}
)
type cUserTagDefault struct{}
func (c *cUserTagDefault) User(ctx context.Context, req *UserTagDefaultReq) (res *UserTagDefaultRes, err error) {
g.RequestFromCtx(ctx).Response.WriteJson(req)
return
}
func Test_ParamsTagDefault(t *testing.T) {
s := g.Server(guid.S())
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(UserTagDefault)
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
client := g.Client()
client.SetPrefix(prefix)
// Test with no parameters, should use all default values
resp := client.GetContent(ctx, "/user-default")
t.Assert(resp, `{"Id":1,"Name":"john","Age":18,"Score":99.9,"IsVip":true,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
// Test with partial parameters (query method), should use partial default values
resp = client.GetContent(ctx, "/user-default?id=100&name=smith")
t.Assert(resp, `{"Id":100,"Name":"smith","Age":18,"Score":99.9,"IsVip":true,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
// Test with partial parameters (query method), should use partial default values
resp = client.GetContent(ctx, "/user-default?id=100&name=smith&age")
t.Assert(resp, `{"Id":100,"Name":"smith","Age":18,"Score":99.9,"IsVip":true,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
// Test providing partial parameters via POST form
resp = client.PostContent(ctx, "/user-default", "id=200&age=30&nickname=jack")
t.Assert(resp, `{"Id":200,"Name":"john","Age":30,"Score":99.9,"IsVip":true,"NickName":"jack","EmptyStr":"","Email":"","Address":""}`)
// Test providing partial parameters via POST JSON
resp = client.ContentJson().PostContent(ctx, "/user-default", g.Map{
"id": 300,
"name": "bob",
"score": 88.8,
"address": "beijing",
})
t.Assert(resp, `{"Id":300,"Name":"bob","Age":18,"Score":88.8,"IsVip":true,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":"beijing"}`)
// Test providing JSON content via GET request
resp = client.ContentJson().PostContent(ctx, "/user-default", `{"id":500,"isVip":false}`)
t.Assert(resp, `{"Id":500,"Name":"john","Age":18,"Score":99.9,"IsVip":false,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
// Test providing empty values, should use default values
resp = client.PostContent(ctx, "/user-default", "id=400&name=&age=")
t.Assert(resp, `{"Id":400,"Name":"","Age":0,"Score":99.9,"IsVip":true,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
// Test providing JSON content via GET request
resp = client.ContentJson().GetContent(ctx, "/user-default", `{"id":500,"isVip":false}`)
t.Assert(resp, `{"Id":500,"Name":"john","Age":18,"Score":99.9,"IsVip":false,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
})
}
func Benchmark_ParamTagIn(b *testing.B) {
b.StopTimer()
s := g.Server(guid.S())
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(User)
group.Bind(UserTagIn)
})
s.SetDumpRouterMap(false)
s.SetAccessLogEnabled(false)

View File

@ -678,3 +678,51 @@ func Test_Issue4047(t *testing.T) {
t.Assert(s.Logger(), nil)
})
}
// Issue4093Req
type Issue4093Req struct {
g.Meta `path:"/test" method:"post"`
Page int `json:"page" example:"10" d:"1" v:"min:1#页码最小值不能低于1" dc:"当前页码"`
PerPage int `json:"pageSize" example:"1" d:"10" v:"min:1|max:200#每页数量最小值不能低于1|最大值不能大于200" dc:"每页数量"`
Pagination bool `json:"pagination" d:"true" dc:"是否需要进行分页"`
Name string `json:"name" d:"john"`
Number int `json:"number" d:"1"`
}
type Issue4093Res struct {
g.Meta `mime:"text/html" example:"string"`
}
var (
Issue4093 = cIssue4093{}
)
type cIssue4093 struct{}
func (c *cIssue4093) User(ctx context.Context, req *Issue4093Req) (res *Issue4093Res, err error) {
g.RequestFromCtx(ctx).Response.WriteJson(req)
return
}
// https://github.com/gogf/gf/issues/4093
func Test_Issue4093(t *testing.T) {
s := g.Server(guid.S())
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(Issue4093)
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
client := g.Client().ContentJson()
client.SetPrefix(prefix)
t.Assert(client.PostContent(ctx, "/test", `{"pagination":false,"name":"","number":0}`), `{"page":1,"pageSize":10,"pagination":false,"name":"","number":0}`)
t.Assert(client.PostContent(ctx, "/test"), `{"page":1,"pageSize":10,"pagination":true,"name":"john","number":1}`)
})
}