Skip to content

Commit a9c8f77

Browse files
committed
feat: Update binding and add go.mod
Change-Id: I3de610d1560ada8ced81296055712c626934a7fd
1 parent ab250cd commit a9c8f77

File tree

9 files changed

+171
-86
lines changed

9 files changed

+171
-86
lines changed

binding/bind.go

+34-21
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,22 @@ type Binding struct {
1717
recvs map[int32]*receiver
1818
lock sync.RWMutex
1919
bindErrFactory func(failField, msg string) error
20-
tagNames TagNames
20+
config Config
2121
}
2222

2323
// New creates a binding tool.
2424
// NOTE:
25-
// Use default tag name for tagNames fields that are empty
26-
func New(tagNames *TagNames) *Binding {
27-
if tagNames == nil {
28-
tagNames = new(TagNames)
25+
// Use default tag name for config fields that are empty
26+
func New(config *Config) *Binding {
27+
if config == nil {
28+
config = new(Config)
2929
}
3030
b := &Binding{
31-
recvs: make(map[int32]*receiver, 1024),
32-
tagNames: *tagNames,
31+
recvs: make(map[int32]*receiver, 1024),
32+
config: *config,
3333
}
34-
b.tagNames.init()
35-
b.vd = validator.New(b.tagNames.Validator)
34+
b.config.init()
35+
b.vd = validator.New(b.config.Validator)
3636
return b.SetErrorFactory(nil, nil)
3737
}
3838

@@ -48,6 +48,18 @@ func ResetJSONUnmarshaler(verifyingRequired bool, fn func(data []byte, v interfa
4848
jsonUnmarshalFunc = fn
4949
}
5050

51+
// SetLooseZeroMode if set to true,
52+
// the empty string request parameter is bound to the zero value of parameter.
53+
// NOTE:
54+
// The default is false.
55+
func (b *Binding) SetLooseZeroMode(enable bool) *Binding {
56+
b.config.LooseZeroMode = enable
57+
for k := range b.recvs {
58+
delete(b.recvs, k)
59+
}
60+
return b
61+
}
62+
5163
var defaultValidatingErrFactory = newDefaultErrorFactory("validating")
5264
var defaultBindErrFactory = newDefaultErrorFactory("binding")
5365

@@ -216,7 +228,8 @@ func (b *Binding) getOrPrepareReceiver(value reflect.Value) (*receiver, error) {
216228
return nil, err
217229
}
218230
recv = &receiver{
219-
params: make([]*paramInfo, 0, 16),
231+
params: make([]*paramInfo, 0, 16),
232+
looseZeroMode: b.config.LooseZeroMode,
220233
}
221234
var errExprSelector tagexpr.ExprSelector
222235
var errMsg string
@@ -229,38 +242,38 @@ func (b *Binding) getOrPrepareReceiver(value reflect.Value) (*receiver, error) {
229242
return false
230243
}
231244

232-
tagKVs := b.tagNames.parse(fh.StructField())
245+
tagKVs := b.config.parse(fh.StructField())
233246
p := recv.getOrAddParam(fh, b.bindErrFactory)
234247
tagInfos := [maxIn]*tagInfo{}
235248
L:
236249
for _, tagKV := range tagKVs {
237250
paramIn := auto
238251
switch tagKV.name {
239-
case b.tagNames.Validator:
252+
case b.config.Validator:
240253
recv.hasVd = true
241254
continue L
242255

243-
case b.tagNames.Query:
256+
case b.config.Query:
244257
recv.hasQuery = true
245258
paramIn = query
246-
case b.tagNames.PathParam:
259+
case b.config.PathParam:
247260
recv.hasPath = true
248261
paramIn = path
249-
case b.tagNames.Header:
262+
case b.config.Header:
250263
paramIn = header
251-
case b.tagNames.Cookie:
264+
case b.config.Cookie:
252265
recv.hasCookie = true
253266
paramIn = cookie
254-
case b.tagNames.RawBody:
267+
case b.config.RawBody:
255268
recv.hasBody = true
256269
paramIn = raw_body
257-
case b.tagNames.FormBody:
270+
case b.config.FormBody:
258271
recv.hasBody = true
259272
paramIn = form
260-
case b.tagNames.protobufBody:
273+
case b.config.protobufBody:
261274
recv.hasBody = true
262275
paramIn = protobuf
263-
case b.tagNames.jsonBody:
276+
case b.config.jsonBody:
264277
recv.hasBody = true
265278
paramIn = json
266279

@@ -287,7 +300,7 @@ func (b *Binding) getOrPrepareReceiver(value reflect.Value) (*receiver, error) {
287300
recv.hasBody = true
288301
}
289302
if !recv.hasVd {
290-
_, recv.hasVd = tagKVs.lookup(b.tagNames.Validator)
303+
_, recv.hasVd = tagKVs.lookup(b.config.Validator)
291304
}
292305
return true
293306
})

binding/bind_test.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,21 @@ func TestQueryString(t *testing.T) {
5454
B string `query:"b"`
5555
C *[]string `query:"c,required"`
5656
D *string `query:"d"`
57+
E *[]***int `query:"e"`
5758
}
5859
Y string `query:"y,required"`
5960
Z *string `query:"z"`
6061
}
61-
req := newRequest("http://localhost:8080/?a=a1&a=a2&b=b1&c=c1&c=c2&d=d1&d=d2&y=y1", nil, nil, nil)
62+
req := newRequest("http://localhost:8080/?a=a1&a=a2&b=b1&c=c1&c=c2&d=d1&d=d2&e=&e=2&y=y1", nil, nil, nil)
6263
recv := new(Recv)
6364
binder := binding.New(nil)
6465
err := binder.BindAndValidate(recv, req, nil)
66+
assert.EqualError(t, err, "binding X.E: parameter type does not match binding data")
67+
binder.SetLooseZeroMode(true)
68+
err = binder.BindAndValidate(recv, req, nil)
6569
assert.Nil(t, err)
70+
assert.Equal(t, 0, ***(*(**recv.X).E)[0])
71+
assert.Equal(t, 2, ***(*(**recv.X).E)[1])
6672
assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A)
6773
assert.Equal(t, "b1", (**recv.X).B)
6874
assert.Equal(t, []string{"c1", "c2"}, *(**recv.X).C)

binding/default.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,20 @@ var defaultBinding = New(nil)
1414
// form tag name is 'form';
1515
// validator tag name is 'vd';
1616
// protobuf tag name is 'protobuf';
17-
// json tag name is 'json'.
17+
// json tag name is 'json';
18+
// LooseZeroMode is false.
1819
func Default() *Binding {
1920
return defaultBinding
2021
}
2122

23+
// SetLooseZeroMode if set to true,
24+
// the empty string request parameter is bound to the zero value of parameter.
25+
// NOTE:
26+
// The default is false.
27+
func SetLooseZeroMode(enable bool) {
28+
defaultBinding.SetLooseZeroMode(enable)
29+
}
30+
2231
// SetErrorFactory customizes the factory of validation error.
2332
// NOTE:
2433
// If errFactory==nil, the default is used

binding/param_info.go

+40-39
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type paramInfo struct {
1717
tagInfos []*tagInfo
1818
omitIns map[in]bool
1919
bindErrFactory func(failField, msg string) error
20+
looseZeroMode bool
2021
}
2122

2223
func (p *paramInfo) name(paramIn in) string {
@@ -156,102 +157,102 @@ func (p *paramInfo) bindMapStrings(info *tagInfo, expr *tagexpr.TagExpr, values
156157
return true, p.bindStringSlice(info, expr, r)
157158
}
158159

160+
// NOTE: len(a)>0
159161
func (p *paramInfo) bindStringSlice(info *tagInfo, expr *tagexpr.TagExpr, a []string) error {
160162
v, err := p.getField(expr, true)
161163
if err != nil || !v.IsValid() {
162164
return err
163165
}
164-
return setStringSlice(info, v, a)
165-
}
166166

167-
func setStringSlice(info *tagInfo, v reflect.Value, a []string) error {
168167
v = goutil.DereferenceValue(v)
169168
switch v.Kind() {
170169
case reflect.String:
171170
v.Set(reflect.ValueOf(a[0]))
172171
return nil
173172

174173
case reflect.Bool:
175-
bol, err := strconv.ParseBool(a[0])
176-
if err == nil {
174+
var bol bool
175+
bol, err = strconv.ParseBool(a[0])
176+
if err == nil || (a[0] == "" && p.looseZeroMode) {
177177
v.SetBool(bol)
178178
return nil
179179
}
180-
return nil
181-
182180
case reflect.Float32:
183-
f, err := strconv.ParseFloat(a[0], 32)
184-
if err == nil {
181+
var f float64
182+
f, err = strconv.ParseFloat(a[0], 32)
183+
if err == nil || (a[0] == "" && p.looseZeroMode) {
185184
v.SetFloat(f)
186185
return nil
187186
}
188-
return nil
189187
case reflect.Float64:
190-
f, err := strconv.ParseFloat(a[0], 64)
191-
if err == nil {
188+
var f float64
189+
f, err = strconv.ParseFloat(a[0], 64)
190+
if err == nil || (a[0] == "" && p.looseZeroMode) {
192191
v.SetFloat(f)
193192
return nil
194193
}
195-
return nil
196-
197194
case reflect.Int64, reflect.Int:
198-
i, err := strconv.ParseInt(a[0], 10, 64)
199-
if err == nil {
195+
var i int64
196+
i, err = strconv.ParseInt(a[0], 10, 64)
197+
if err == nil || (a[0] == "" && p.looseZeroMode) {
200198
v.SetInt(i)
201199
return nil
202200
}
203201
case reflect.Int32:
204-
i, err := strconv.ParseInt(a[0], 10, 32)
205-
if err == nil {
202+
var i int64
203+
i, err = strconv.ParseInt(a[0], 10, 32)
204+
if err == nil || (a[0] == "" && p.looseZeroMode) {
206205
v.SetInt(i)
207206
return nil
208207
}
209208
case reflect.Int16:
210-
i, err := strconv.ParseInt(a[0], 10, 16)
211-
if err == nil {
209+
var i int64
210+
i, err = strconv.ParseInt(a[0], 10, 16)
211+
if err == nil || (a[0] == "" && p.looseZeroMode) {
212212
v.SetInt(i)
213213
return nil
214214
}
215215
case reflect.Int8:
216-
i, err := strconv.ParseInt(a[0], 10, 8)
217-
if err == nil {
216+
var i int64
217+
i, err = strconv.ParseInt(a[0], 10, 8)
218+
if err == nil || (a[0] == "" && p.looseZeroMode) {
218219
v.SetInt(i)
219220
return nil
220221
}
221-
222222
case reflect.Uint64, reflect.Uint:
223-
i, err := strconv.ParseUint(a[0], 10, 64)
224-
if err == nil {
225-
v.SetUint(i)
223+
var u uint64
224+
u, err = strconv.ParseUint(a[0], 10, 64)
225+
if err == nil || (a[0] == "" && p.looseZeroMode) {
226+
v.SetUint(u)
226227
return nil
227228
}
228229
case reflect.Uint32:
229-
i, err := strconv.ParseUint(a[0], 10, 32)
230-
if err == nil {
231-
v.SetUint(i)
230+
var u uint64
231+
u, err = strconv.ParseUint(a[0], 10, 32)
232+
if err == nil || (a[0] == "" && p.looseZeroMode) {
233+
v.SetUint(u)
232234
return nil
233235
}
234236
case reflect.Uint16:
235-
i, err := strconv.ParseUint(a[0], 10, 16)
236-
if err == nil {
237-
v.SetUint(i)
237+
var u uint64
238+
u, err = strconv.ParseUint(a[0], 10, 16)
239+
if err == nil || (a[0] == "" && p.looseZeroMode) {
240+
v.SetUint(u)
238241
return nil
239242
}
240243
case reflect.Uint8:
241-
i, err := strconv.ParseUint(a[0], 10, 8)
242-
if err == nil {
243-
v.SetUint(i)
244+
var u uint64
245+
u, err = strconv.ParseUint(a[0], 10, 8)
246+
if err == nil || (a[0] == "" && p.looseZeroMode) {
247+
v.SetUint(u)
244248
return nil
245249
}
246-
247250
case reflect.Slice:
248-
tt := v.Type().Elem()
249-
vv, err := stringsToValue(tt.Kind(), a)
251+
vv, err := stringsToValue(v.Type().Elem(), a, p.looseZeroMode)
250252
if err == nil {
251253
v.Set(vv)
252254
return nil
253255
}
254256
}
255-
256257
return info.typeError
257258
}

binding/receiver.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ type receiver struct {
5454
hasQuery, hasCookie, hasPath, hasBody, hasVd bool
5555

5656
params []*paramInfo
57+
58+
looseZeroMode bool
5759
}
5860

5961
func (r *receiver) getParam(fieldSelector string) *paramInfo {
@@ -71,11 +73,13 @@ func (r *receiver) getOrAddParam(fh *tagexpr.FieldHandler, bindErrFactory func(f
7173
if p != nil {
7274
return p
7375
}
74-
p = new(paramInfo)
75-
p.fieldSelector = fieldSelector
76-
p.structField = fh.StructField()
77-
p.omitIns = make(map[in]bool, maxIn)
78-
p.bindErrFactory = bindErrFactory
76+
p = &paramInfo{
77+
fieldSelector: fieldSelector,
78+
structField: fh.StructField(),
79+
omitIns: make(map[in]bool, maxIn),
80+
bindErrFactory: bindErrFactory,
81+
looseZeroMode: r.looseZeroMode,
82+
}
7983
r.params = append(r.params, p)
8084
return p
8185
}

binding/tag_names.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ const (
2222
tagJSON = "json"
2323
)
2424

25-
// TagNames struct tag naming
26-
type TagNames struct {
25+
// Config the struct tag naming and so on
26+
type Config struct {
27+
// LooseZeroMode if set to true,
28+
// the empty string request parameter is bound to the zero value of parameter.
29+
LooseZeroMode bool
2730
// PathParam use 'path' by default when empty
2831
PathParam string
2932
// Query use 'query' by default when empty
@@ -46,7 +49,7 @@ type TagNames struct {
4649
list []string
4750
}
4851

49-
func (t *TagNames) init() {
52+
func (t *Config) init() {
5053
t.list = []string{
5154
goutil.InitAndGetString(&t.PathParam, defaultTagPath),
5255
goutil.InitAndGetString(&t.Query, defaultTagQuery),
@@ -60,7 +63,7 @@ func (t *TagNames) init() {
6063
}
6164
}
6265

63-
func (t *TagNames) parse(field reflect.StructField) tagKVs {
66+
func (t *Config) parse(field reflect.StructField) tagKVs {
6467
tag := field.Tag
6568
fieldName := field.Name
6669

0 commit comments

Comments
 (0)