Skip to content
This repository was archived by the owner on Dec 23, 2025. It is now read-only.

Commit 4bcdbb3

Browse files
committed
feat(binding): Support the level of handling tags
Change-Id: Ifc81a5f332ff5ff077bcc988dac4dcf91e7d3ba9
1 parent e70a540 commit 4bcdbb3

File tree

2 files changed

+81
-26
lines changed

2 files changed

+81
-26
lines changed

binding/bind.go

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,24 @@ import (
1111
"github.com/henrylee2cn/goutil/tpack"
1212
)
1313

14+
// Level the level of handling tags
15+
type Level uint8
16+
17+
const (
18+
// OnlyFirst handle only the first level field tags
19+
OnlyFirst Level = iota
20+
// FirstForUntagged for untagged fields, only the first level is handled
21+
FirstForUntagged
22+
// Any handle any level field tags
23+
Any
24+
)
25+
1426
// Binding binding and verification tool for http request
1527
type Binding struct {
28+
level Level
1629
vd *validator.Validator
17-
bindErrFactory func(failField, msg string) error
1830
recvs goutil.Map
31+
bindErrFactory func(failField, msg string) error
1932
}
2033

2134
// New creates a binding tool.
@@ -25,11 +38,24 @@ func New(tagName string) *Binding {
2538
if tagName == "" {
2639
tagName = "api"
2740
}
28-
return &Binding{
29-
vd: validator.New(tagName).SetErrorFactory(defaultValidatingErrFactory),
30-
bindErrFactory: defaultBindErrFactory,
31-
recvs: goutil.AtomicMap(),
41+
b := &Binding{
42+
vd: validator.New(tagName),
43+
recvs: goutil.AtomicMap(),
44+
}
45+
return b.SetLevel(OnlyFirst).SetErrorFactory(nil, nil)
46+
}
47+
48+
// SetLevel set the level of handling tags.
49+
// NOTE:
50+
// default is First
51+
func (b *Binding) SetLevel(level Level) *Binding {
52+
switch level {
53+
case OnlyFirst, FirstForUntagged, Any:
54+
b.level = level
55+
default:
56+
b.level = OnlyFirst
3257
}
58+
return b
3359
}
3460

3561
var defaultValidatingErrFactory = newDefaultErrorFactory("invalid parameter")
@@ -114,21 +140,50 @@ func (b *Binding) getObjOrPrepare(value reflect.Value) (*receiver, error) {
114140
var errMsg string
115141

116142
expr.RangeFields(func(fh *tagexpr.FieldHandler) bool {
117-
fieldSelector := fh.StringSelector()
143+
paths, name := fh.FieldSelector().Split()
144+
var evals map[tagexpr.ExprSelector]func() interface{}
145+
146+
switch b.level {
147+
case OnlyFirst:
148+
if len(paths) > 0 {
149+
return true
150+
}
151+
152+
case FirstForUntagged:
153+
if len(paths) > 0 {
154+
var canHandle bool
155+
evals = fh.EvalFuncs()
156+
for es := range evals {
157+
switch v := es.Name(); v {
158+
case "raw_body", "body", "query", "path", "header", "cookie", "required":
159+
canHandle = true
160+
break
161+
}
162+
}
163+
if !canHandle {
164+
return true
165+
}
166+
}
167+
168+
default:
169+
// Any
170+
}
118171

119172
if !fh.Value(true).CanSet() {
120-
errMsg = "field cannot be set: " + fieldSelector
121-
errExprSelector = tagexpr.ExprSelector(fieldSelector)
173+
selector := fh.StringSelector()
174+
errMsg = "field cannot be set: " + selector
175+
errExprSelector = tagexpr.ExprSelector(selector)
122176
return false
123177
}
124178

125-
p := recv.getOrAddParam(fh, b.bindErrFactory)
126179
in := auto
127-
name := fh.FieldSelector().Name()
180+
p := recv.getOrAddParam(fh, b.bindErrFactory)
181+
if evals == nil {
182+
evals = fh.EvalFuncs()
183+
}
128184

129185
L:
130-
for es, eval := range fh.EvalFuncs() {
131-
186+
for es, eval := range evals {
132187
switch es.Name() {
133188
case validator.MatchExprName:
134189
recv.hasVd = true

binding/bind_test.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestRawBody(t *testing.T) {
2929
bodyBytes := []byte("rawbody.............")
3030
req := newRequest("", nil, nil, bytes.NewReader(bodyBytes))
3131
recv := new(Recv)
32-
binder := binding.New("api")
32+
binder := binding.New("api").SetLevel(binding.FirstForUntagged)
3333
err := binder.BindAndValidate(recv, req, nil)
3434
assert.NotNil(t, err)
3535
assert.Equal(t, err.Error(), "too long")
@@ -59,7 +59,7 @@ func TestQueryString(t *testing.T) {
5959
}
6060
req := newRequest("http://localhost:8080/?a=a1&a=a2&b=b1&c=c1&c=c2&d=d1&d=d2&y=y1", nil, nil, nil)
6161
recv := new(Recv)
62-
binder := binding.New("api")
62+
binder := binding.New("api").SetLevel(binding.FirstForUntagged)
6363
err := binder.BindAndValidate(recv, req, nil)
6464
assert.Nil(t, err)
6565
assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A)
@@ -83,7 +83,7 @@ func TestQueryNum(t *testing.T) {
8383
}
8484
req := newRequest("http://localhost:8080/?a=11&a=12&b=21&c=31&c=32&d=41&d=42&y=true", nil, nil, nil)
8585
recv := new(Recv)
86-
binder := binding.New("api")
86+
binder := binding.New("api").SetLevel(binding.FirstForUntagged)
8787
err := binder.BindAndValidate(recv, req, nil)
8888
assert.Nil(t, err)
8989
assert.Equal(t, []int{11, 12}, (**recv.X).A)
@@ -116,7 +116,7 @@ func TestHeaderString(t *testing.T) {
116116
header.Add("X-Y", "y1")
117117
req := newRequest("", header, nil, nil)
118118
recv := new(Recv)
119-
binder := binding.New("api")
119+
binder := binding.New("api").SetLevel(binding.FirstForUntagged)
120120
err := binder.BindAndValidate(recv, req, nil)
121121
assert.Nil(t, err)
122122
assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A)
@@ -149,7 +149,7 @@ func TestHeaderNum(t *testing.T) {
149149
header.Add("X-Y", "true")
150150
req := newRequest("", header, nil, nil)
151151
recv := new(Recv)
152-
binder := binding.New("api")
152+
binder := binding.New("api").SetLevel(binding.FirstForUntagged)
153153
err := binder.BindAndValidate(recv, req, nil)
154154
assert.Nil(t, err)
155155
assert.Equal(t, []int{11, 12}, (**recv.X).A)
@@ -183,7 +183,7 @@ func TestCookieString(t *testing.T) {
183183
}
184184
req := newRequest("", nil, cookies, nil)
185185
recv := new(Recv)
186-
binder := binding.New("api")
186+
binder := binding.New("api").SetLevel(binding.FirstForUntagged)
187187
err := binder.BindAndValidate(recv, req, nil)
188188
assert.Nil(t, err)
189189
assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A)
@@ -217,7 +217,7 @@ func TestCookieNum(t *testing.T) {
217217
}
218218
req := newRequest("", nil, cookies, nil)
219219
recv := new(Recv)
220-
binder := binding.New("api")
220+
binder := binding.New("api").SetLevel(binding.FirstForUntagged)
221221
err := binder.BindAndValidate(recv, req, nil)
222222
assert.Nil(t, err)
223223
assert.Equal(t, []int{11, 12}, (**recv.X).A)
@@ -258,7 +258,7 @@ func TestFormString(t *testing.T) {
258258
header.Set("Content-Type", contentType)
259259
req := newRequest("", header, nil, bodyReader)
260260
recv := new(Recv)
261-
binder := binding.New("api")
261+
binder := binding.New("api").SetLevel(binding.FirstForUntagged)
262262
err := binder.BindAndValidate(recv, req, nil)
263263
assert.Nil(t, err)
264264
assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A)
@@ -300,7 +300,7 @@ func TestFormNum(t *testing.T) {
300300
header.Set("Content-Type", contentType)
301301
req := newRequest("", header, nil, bodyReader)
302302
recv := new(Recv)
303-
binder := binding.New("api")
303+
binder := binding.New("api").SetLevel(binding.FirstForUntagged)
304304
err := binder.BindAndValidate(recv, req, nil)
305305
assert.Nil(t, err)
306306
assert.Equal(t, []int{11, 12}, (**recv.X).A)
@@ -338,7 +338,7 @@ func TestJSON(t *testing.T) {
338338
header.Set("Content-Type", "application/json")
339339
req := newRequest("", header, nil, bodyReader)
340340
recv := new(Recv)
341-
binder := binding.New("api")
341+
binder := binding.New("api").SetLevel(binding.FirstForUntagged)
342342
err := binder.BindAndValidate(recv, req, nil)
343343
assert.Nil(t, err)
344344
assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A)
@@ -384,7 +384,7 @@ func TestPath(t *testing.T) {
384384

385385
req := newRequest("", nil, nil, nil)
386386
recv := new(Recv)
387-
binder := binding.New("api")
387+
binder := binding.New("api").SetLevel(binding.FirstForUntagged)
388388
err := binder.BindAndValidate(recv, req, new(testPathParams))
389389
assert.Nil(t, err)
390390
assert.Equal(t, []string{"a1"}, (**recv.X).A)
@@ -401,10 +401,10 @@ func TestAuto(t *testing.T) {
401401
A []string `api:""`
402402
B int32 `api:""`
403403
C *[]uint16 `api:"{required:true}"`
404-
D *float32 `api:""`
404+
D *float32
405405
}
406406
Y string `api:"{required:true}"`
407-
Z *int64 `api:""`
407+
Z *int64
408408
}
409409
query := make(url.Values)
410410
query.Add("A", "a1")
@@ -427,7 +427,7 @@ func TestAuto(t *testing.T) {
427427
header.Set("Content-Type", contentType)
428428
req := newRequest("http://localhost/?"+query.Encode(), header, nil, bodyReader)
429429
recv := new(Recv)
430-
binder := binding.New("api")
430+
binder := binding.New("api").SetLevel(binding.Any)
431431
err := binder.BindAndValidate(recv, req, nil)
432432
assert.Nil(t, err)
433433
assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A)

0 commit comments

Comments
 (0)