Skip to content

Commit bcda48b

Browse files
authored
fix(net/ghttp):check parameter existence to determine using default or front-end value. (#4182)
1 parent a8a055f commit bcda48b

File tree

4 files changed

+168
-63
lines changed

4 files changed

+168
-63
lines changed

net/ghttp/ghttp_request_param.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ func (r *Request) doParse(pointer interface{}, requestType int) error {
107107
return err
108108
}
109109
}
110-
// TODO: https://github.com/gogf/gf/pull/2450
111110
// Validation.
112111
if err = gvalid.New().
113112
Bail().

net/ghttp/ghttp_request_param_request.go

Lines changed: 25 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package ghttp
88

99
import (
1010
"github.com/gogf/gf/v2/container/gvar"
11-
"github.com/gogf/gf/v2/internal/empty"
1211
"github.com/gogf/gf/v2/net/goai"
1312
"github.com/gogf/gf/v2/os/gstructs"
1413
"github.com/gogf/gf/v2/util/gconv"
@@ -178,36 +177,27 @@ func (r *Request) doGetRequestStruct(pointer interface{}, mapping ...map[string]
178177
if data == nil {
179178
data = map[string]interface{}{}
180179
}
181-
// Default struct values.
182-
if err = r.mergeDefaultStructValue(data, pointer); err != nil {
183-
return data, nil
184-
}
180+
185181
// `in` Tag Struct values.
186182
if err = r.mergeInTagStructValue(data); err != nil {
187183
return data, nil
188184
}
189185

186+
// Default struct values.
187+
if err = r.mergeDefaultStructValue(data, pointer); err != nil {
188+
return data, nil
189+
}
190+
190191
return data, gconv.Struct(data, pointer, mapping...)
191192
}
192193

193194
// mergeDefaultStructValue merges the request parameters with default values from struct tag definition.
194195
func (r *Request) mergeDefaultStructValue(data map[string]interface{}, pointer interface{}) error {
195196
fields := r.serveHandler.Handler.Info.ReqStructFields
196197
if len(fields) > 0 {
197-
var (
198-
foundKey string
199-
foundValue interface{}
200-
)
201198
for _, field := range fields {
202199
if tagValue := field.TagDefault(); tagValue != "" {
203-
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
204-
if foundKey == "" {
205-
data[field.Name()] = tagValue
206-
} else {
207-
if empty.IsEmpty(foundValue) {
208-
data[foundKey] = tagValue
209-
}
210-
}
200+
mergeTagValueWithFoundKey(data, false, field.Name(), field.Name(), tagValue)
211201
}
212202
}
213203
return nil
@@ -219,19 +209,8 @@ func (r *Request) mergeDefaultStructValue(data map[string]interface{}, pointer i
219209
return err
220210
}
221211
if len(tagFields) > 0 {
222-
var (
223-
foundKey string
224-
foundValue interface{}
225-
)
226212
for _, field := range tagFields {
227-
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
228-
if foundKey == "" {
229-
data[field.Name()] = field.TagValue
230-
} else {
231-
if empty.IsEmpty(foundValue) {
232-
data[foundKey] = field.TagValue
233-
}
234-
}
213+
mergeTagValueWithFoundKey(data, false, field.Name(), field.Name(), field.TagValue)
235214
}
236215
}
237216

@@ -261,34 +240,29 @@ func (r *Request) mergeInTagStructValue(data map[string]interface{}) error {
261240

262241
for _, field := range fields {
263242
if tagValue := field.TagIn(); tagValue != "" {
243+
findKey := field.TagPriorityName()
264244
switch tagValue {
265245
case goai.ParameterInHeader:
266-
foundHeaderKey, foundHeaderValue := gutil.MapPossibleItemByKey(headerMap, field.TagPriorityName())
267-
if foundHeaderKey != "" {
268-
foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundHeaderKey)
269-
if foundKey == "" {
270-
data[field.Name()] = foundHeaderValue
271-
} else {
272-
if empty.IsEmpty(foundValue) {
273-
data[foundKey] = foundHeaderValue
274-
}
275-
}
276-
}
246+
foundKey, foundValue = gutil.MapPossibleItemByKey(headerMap, findKey)
277247
case goai.ParameterInCookie:
278-
foundCookieKey, foundCookieValue := gutil.MapPossibleItemByKey(cookieMap, field.TagPriorityName())
279-
if foundCookieKey != "" {
280-
foundKey, foundValue = gutil.MapPossibleItemByKey(data, foundCookieKey)
281-
if foundKey == "" {
282-
data[field.Name()] = foundCookieValue
283-
} else {
284-
if empty.IsEmpty(foundValue) {
285-
data[foundKey] = foundCookieValue
286-
}
287-
}
288-
}
248+
foundKey, foundValue = gutil.MapPossibleItemByKey(cookieMap, findKey)
249+
}
250+
if foundKey != "" {
251+
mergeTagValueWithFoundKey(data, true, foundKey, field.Name(), foundValue)
289252
}
290253
}
291254
}
292255
}
293256
return nil
294257
}
258+
259+
// mergeTagValueWithFoundKey merges the request parameters when the key does not exist in the map or overwritten is true or the value is nil.
260+
func mergeTagValueWithFoundKey(data map[string]interface{}, overwritten bool, findKey string, fieldName string, tagValue interface{}) {
261+
if foundKey, foundValue := gutil.MapPossibleItemByKey(data, findKey); foundKey == "" {
262+
data[fieldName] = tagValue
263+
} else {
264+
if overwritten || foundValue == nil {
265+
data[foundKey] = tagValue
266+
}
267+
}
268+
}

net/ghttp/ghttp_z_unit_feature_request_param_test.go

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,34 @@ import (
1313
"github.com/gogf/gf/v2/util/guid"
1414
)
1515

16-
type UserReq struct {
16+
// UserTagInReq struct tag "in" supports: header, cookie
17+
type UserTagInReq struct {
1718
g.Meta `path:"/user" tags:"User" method:"post" summary:"user api" title:"api title"`
1819
Id int `v:"required" d:"1"`
1920
Name string `v:"required" in:"cookie"`
2021
Age string `v:"required" in:"header"`
21-
// header,query,cookie,form
2222
}
2323

24-
type UserRes struct {
24+
type UserTagInRes struct {
2525
g.Meta `mime:"text/html" example:"string"`
2626
}
2727

2828
var (
29-
User = cUser{}
29+
UserTagIn = cUserTagIn{}
3030
)
3131

32-
type cUser struct{}
32+
type cUserTagIn struct{}
3333

34-
func (c *cUser) User(ctx context.Context, req *UserReq) (res *UserRes, err error) {
34+
func (c *cUserTagIn) User(ctx context.Context, req *UserTagInReq) (res *UserTagInRes, err error) {
3535
g.RequestFromCtx(ctx).Response.WriteJson(req)
3636
return
3737
}
3838

39-
func Test_Params_Tag(t *testing.T) {
39+
func Test_ParamsTagIn(t *testing.T) {
4040
s := g.Server(guid.S())
4141
s.Group("/", func(group *ghttp.RouterGroup) {
4242
group.Middleware(ghttp.MiddlewareHandlerResponse)
43-
group.Bind(User)
43+
group.Bind(UserTagIn)
4444
})
4545
s.SetDumpRouterMap(false)
4646
s.Start()
@@ -56,17 +56,101 @@ func Test_Params_Tag(t *testing.T) {
5656
client.SetHeader("age", "18")
5757

5858
t.Assert(client.PostContent(ctx, "/user"), `{"Id":1,"Name":"john","Age":"18"}`)
59-
t.Assert(client.PostContent(ctx, "/user", "name=&age=&id="), `{"Id":1,"Name":"john","Age":"18"}`)
59+
t.Assert(client.PostContent(ctx, "/user", "name=&age="), `{"Id":1,"Name":"john","Age":"18"}`)
6060
})
6161
}
6262

63-
func Benchmark_ParamTag(b *testing.B) {
63+
type UserTagDefaultReq struct {
64+
g.Meta `path:"/user-default" method:"post,get" summary:"user default tag api"`
65+
Id int `v:"required" d:"1"`
66+
Name string `d:"john"`
67+
Age int `d:"18"`
68+
Score float64 `d:"99.9"`
69+
IsVip bool `d:"true"`
70+
NickName string `p:"nickname" d:"nickname-default"`
71+
EmptyStr string `d:""`
72+
Email string
73+
Address string
74+
}
75+
76+
type UserTagDefaultRes struct {
77+
g.Meta `mime:"application/json" example:"string"`
78+
}
79+
80+
var (
81+
UserTagDefault = cUserTagDefault{}
82+
)
83+
84+
type cUserTagDefault struct{}
85+
86+
func (c *cUserTagDefault) User(ctx context.Context, req *UserTagDefaultReq) (res *UserTagDefaultRes, err error) {
87+
g.RequestFromCtx(ctx).Response.WriteJson(req)
88+
return
89+
}
90+
91+
func Test_ParamsTagDefault(t *testing.T) {
92+
s := g.Server(guid.S())
93+
s.Group("/", func(group *ghttp.RouterGroup) {
94+
group.Middleware(ghttp.MiddlewareHandlerResponse)
95+
group.Bind(UserTagDefault)
96+
})
97+
s.SetDumpRouterMap(false)
98+
s.Start()
99+
defer s.Shutdown()
100+
101+
time.Sleep(100 * time.Millisecond)
102+
103+
gtest.C(t, func(t *gtest.T) {
104+
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
105+
client := g.Client()
106+
client.SetPrefix(prefix)
107+
108+
// Test with no parameters, should use all default values
109+
resp := client.GetContent(ctx, "/user-default")
110+
t.Assert(resp, `{"Id":1,"Name":"john","Age":18,"Score":99.9,"IsVip":true,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
111+
112+
// Test with partial parameters (query method), should use partial default values
113+
resp = client.GetContent(ctx, "/user-default?id=100&name=smith")
114+
t.Assert(resp, `{"Id":100,"Name":"smith","Age":18,"Score":99.9,"IsVip":true,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
115+
116+
// Test with partial parameters (query method), should use partial default values
117+
resp = client.GetContent(ctx, "/user-default?id=100&name=smith&age")
118+
t.Assert(resp, `{"Id":100,"Name":"smith","Age":18,"Score":99.9,"IsVip":true,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
119+
120+
// Test providing partial parameters via POST form
121+
resp = client.PostContent(ctx, "/user-default", "id=200&age=30&nickname=jack")
122+
t.Assert(resp, `{"Id":200,"Name":"john","Age":30,"Score":99.9,"IsVip":true,"NickName":"jack","EmptyStr":"","Email":"","Address":""}`)
123+
124+
// Test providing partial parameters via POST JSON
125+
resp = client.ContentJson().PostContent(ctx, "/user-default", g.Map{
126+
"id": 300,
127+
"name": "bob",
128+
"score": 88.8,
129+
"address": "beijing",
130+
})
131+
t.Assert(resp, `{"Id":300,"Name":"bob","Age":18,"Score":88.8,"IsVip":true,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":"beijing"}`)
132+
133+
// Test providing JSON content via GET request
134+
resp = client.ContentJson().PostContent(ctx, "/user-default", `{"id":500,"isVip":false}`)
135+
t.Assert(resp, `{"Id":500,"Name":"john","Age":18,"Score":99.9,"IsVip":false,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
136+
137+
// Test providing empty values, should use default values
138+
resp = client.PostContent(ctx, "/user-default", "id=400&name=&age=")
139+
t.Assert(resp, `{"Id":400,"Name":"","Age":0,"Score":99.9,"IsVip":true,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
140+
141+
// Test providing JSON content via GET request
142+
resp = client.ContentJson().GetContent(ctx, "/user-default", `{"id":500,"isVip":false}`)
143+
t.Assert(resp, `{"Id":500,"Name":"john","Age":18,"Score":99.9,"IsVip":false,"NickName":"nickname-default","EmptyStr":"","Email":"","Address":""}`)
144+
})
145+
}
146+
147+
func Benchmark_ParamTagIn(b *testing.B) {
64148
b.StopTimer()
65149

66150
s := g.Server(guid.S())
67151
s.Group("/", func(group *ghttp.RouterGroup) {
68152
group.Middleware(ghttp.MiddlewareHandlerResponse)
69-
group.Bind(User)
153+
group.Bind(UserTagIn)
70154
})
71155
s.SetDumpRouterMap(false)
72156
s.SetAccessLogEnabled(false)

net/ghttp/ghttp_z_unit_issue_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,3 +678,51 @@ func Test_Issue4047(t *testing.T) {
678678
t.Assert(s.Logger(), nil)
679679
})
680680
}
681+
682+
// Issue4093Req
683+
type Issue4093Req struct {
684+
g.Meta `path:"/test" method:"post"`
685+
Page int `json:"page" example:"10" d:"1" v:"min:1#页码最小值不能低于1" dc:"当前页码"`
686+
PerPage int `json:"pageSize" example:"1" d:"10" v:"min:1|max:200#每页数量最小值不能低于1|最大值不能大于200" dc:"每页数量"`
687+
Pagination bool `json:"pagination" d:"true" dc:"是否需要进行分页"`
688+
Name string `json:"name" d:"john"`
689+
Number int `json:"number" d:"1"`
690+
}
691+
692+
type Issue4093Res struct {
693+
g.Meta `mime:"text/html" example:"string"`
694+
}
695+
696+
var (
697+
Issue4093 = cIssue4093{}
698+
)
699+
700+
type cIssue4093 struct{}
701+
702+
func (c *cIssue4093) User(ctx context.Context, req *Issue4093Req) (res *Issue4093Res, err error) {
703+
g.RequestFromCtx(ctx).Response.WriteJson(req)
704+
return
705+
}
706+
707+
// https://github.com/gogf/gf/issues/4093
708+
func Test_Issue4093(t *testing.T) {
709+
s := g.Server(guid.S())
710+
s.Group("/", func(group *ghttp.RouterGroup) {
711+
group.Middleware(ghttp.MiddlewareHandlerResponse)
712+
group.Bind(Issue4093)
713+
})
714+
s.SetDumpRouterMap(false)
715+
s.Start()
716+
defer s.Shutdown()
717+
718+
time.Sleep(100 * time.Millisecond)
719+
720+
gtest.C(t, func(t *gtest.T) {
721+
prefix := fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())
722+
client := g.Client().ContentJson()
723+
client.SetPrefix(prefix)
724+
725+
t.Assert(client.PostContent(ctx, "/test", `{"pagination":false,"name":"","number":0}`), `{"page":1,"pageSize":10,"pagination":false,"name":"","number":0}`)
726+
t.Assert(client.PostContent(ctx, "/test"), `{"page":1,"pageSize":10,"pagination":true,"name":"john","number":1}`)
727+
})
728+
}

0 commit comments

Comments
 (0)