Skip to content

Commit 7b6627b

Browse files
committed
tonic: Refactor handling of comma-separated lists
1 parent 66ad463 commit 7b6627b

File tree

4 files changed

+51
-33
lines changed

4 files changed

+51
-33
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
language: go
22

33
go:
4-
- "1.8.x"
5-
- "1.9.x"
64
- "1.10.x"
5+
- "1.11.x"
6+
- "1.12.x"
77
- "master"
88

99
jobs:

tonic/handler.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,10 @@ func bind(c *gin.Context, v reflect.Value, tag string, extract extractor) error
195195
}
196196
// Set-up context for extractors.
197197
// Query.
198-
c.Set(ExplodeTag, true) // default
199-
if explodeVal, ok := ft.Tag.Lookup(ExplodeTag); ok {
200-
if explode, err := strconv.ParseBool(explodeVal); err == nil && !explode {
201-
c.Set(ExplodeTag, false)
198+
c.Set(ArrayTag, false)
199+
if arrayVal, ok := ft.Tag.Lookup(ArrayTag); ok {
200+
if array, err := strconv.ParseBool(arrayVal); err == nil && array {
201+
c.Set(ArrayTag, true)
202202
}
203203
}
204204
_, fieldValues, err := extract(c, tagValue)
@@ -209,7 +209,7 @@ func bind(c *gin.Context, v reflect.Value, tag string, extract extractor) error
209209
// if no values were returned.
210210
def, ok := ft.Tag.Lookup(DefaultTag)
211211
if ok && len(fieldValues) == 0 {
212-
if c.GetBool(ExplodeTag) {
212+
if c.GetBool(ArrayTag) {
213213
fieldValues = append(fieldValues, strings.Split(def, ",")...)
214214
} else {
215215
fieldValues = append(fieldValues, def)

tonic/tonic.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const (
2929
RequiredTag = "required"
3030
DefaultTag = "default"
3131
ValidationTag = "validate"
32-
ExplodeTag = "explode"
32+
ArrayTag = "commalist"
3333
)
3434

3535
const (
@@ -264,7 +264,7 @@ func extractQuery(c *gin.Context, tag string) (string, []string, error) {
264264
var params []string
265265
query := c.Request.URL.Query()[name]
266266

267-
if c.GetBool(ExplodeTag) {
267+
if !c.GetBool(ArrayTag) {
268268
// Delete empty elements so default and required arguments
269269
// will play nice together. Append to a new collection to
270270
// preserve order without too much copying.
@@ -303,18 +303,28 @@ func extractPath(c *gin.Context, tag string) (string, []string, error) {
303303
if err != nil {
304304
return "", nil, err
305305
}
306-
p := c.Param(name)
306+
307+
var params []string
308+
309+
if !c.GetBool(ArrayTag) {
310+
params = []string{c.Param(name)}
311+
} else {
312+
splitFn := func(c rune) bool {
313+
return c == ','
314+
}
315+
params = strings.FieldsFunc(c.Param(name), splitFn)
316+
}
307317

308318
// XXX: deprecated, use of "default" tag is preferred
309-
if p == "" && defaultVal != "" {
319+
if len(params) == 0 && defaultVal != "" {
310320
return name, []string{defaultVal}, nil
311321
}
312322
// XXX: deprecated, use of "validate" tag is preferred
313-
if p == "" && required {
323+
if len(params) == 0 && required {
314324
return "", nil, fmt.Errorf("missing path parameter: %s", name)
315325
}
316326

317-
return name, []string{p}, nil
327+
return name, params, nil
318328
}
319329

320330
// extractHeader is an extractor that operates on the headers

tonic/tonic_test.go

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func TestMain(m *testing.M) {
3232
g.GET("/scalar", tonic.Handler(scalarHandler, 200))
3333
g.GET("/error", tonic.Handler(errorHandler, 200))
3434
g.GET("/path/:param", tonic.Handler(pathHandler, 200))
35+
g.GET("/path-list/:param-path-list", tonic.Handler(pathListHandler, 200))
3536
g.GET("/query", tonic.Handler(queryHandler, 200))
3637
g.GET("/query-old", tonic.Handler(queryHandlerOld, 200))
3738
g.POST("/body", tonic.Handler(bodyHandler, 200))
@@ -87,13 +88,13 @@ func TestPathQuery(t *testing.T) {
8788

8889
tester.AddCall("query-complex", "GET", fmt.Sprintf("/query?param=foo&param-complex=%s", now), "").Checkers(iffy.ExpectStatus(200), expectString("param-complex", string(now)))
8990

90-
// Explode.
91-
tester.AddCall("query-explode", "GET", "/query?param=foo&param-explode=a&param-explode=b&param-explode=c", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-explode", "a", "b", "c"))
92-
tester.AddCall("query-explode-disabled-ok", "GET", "/query?param=foo&param-explode-disabled=x,y,z", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-explode-disabled", "x", "y", "z"))
93-
tester.AddCall("query-explode-disabled-error", "GET", "/query?param=foo&param-explode-disabled=a&param-explode-disabled=b", "").Checkers(iffy.ExpectStatus(400))
94-
tester.AddCall("query-explode-string", "GET", "/query?param=foo&param-explode-string=x,y,z", "").Checkers(iffy.ExpectStatus(200), expectString("param-explode-string", "x,y,z"))
95-
tester.AddCall("query-explode-default", "GET", "/query?param=foo", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-explode-default", "1", "2", "3")) // default with explode
96-
tester.AddCall("query-explode-disabled-default", "GET", "/query?param=foo", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-explode-disabled-default", "1,2,3")) // default without explode
91+
// Array split
92+
tester.AddCall("query-list-nosplit", "GET", "/query?param=foo&param-list-nosplit=a&param-list-nosplit=b&param-list-nosplit=c", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-list-nosplit", "a", "b", "c"))
93+
tester.AddCall("query-list-split", "GET", "/query?param=foo&param-list-split=x,y,z", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-list-split", "x", "y", "z"))
94+
tester.AddCall("query-list-split-repeated", "GET", "/query?param=foo&param-list-split=a&param-list-split=b", "").Checkers(iffy.ExpectStatus(400))
95+
tester.AddCall("query-list-nosplit-single", "GET", "/query?param=foo&param-list-string-nosplit=x,y,z", "").Checkers(iffy.ExpectStatus(200), expectString("param-list-string-nosplit", "x,y,z"))
96+
tester.AddCall("query-list-default", "GET", "/query?param=foo", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-list-default", "1", "2", "3")) // default with explode
97+
tester.AddCall("path-list", "GET", "/path-list/1,2,3", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-path-list", "1", "2", "3"))
9798

9899
tester.Run()
99100
}
@@ -150,20 +151,27 @@ func pathHandler(c *gin.Context, in *pathIn) (*pathIn, error) {
150151
return in, nil
151152
}
152153

154+
type pathListIn struct {
155+
ParamPathList []string `path:"param-path-list" json:"param-path-list" commalist:"true"`
156+
}
157+
158+
func pathListHandler(c *gin.Context, in *pathListIn) (*pathListIn, error) {
159+
return in, nil
160+
}
161+
153162
type queryIn struct {
154-
Param string `query:"param" json:"param" validate:"required"`
155-
ParamOptional string `query:"param-optional" json:"param-optional"`
156-
Params []string `query:"params" json:"params"`
157-
ParamInt int `query:"param-int" json:"param-int"`
158-
ParamBool bool `query:"param-bool" json:"param-bool"`
159-
ParamDefault string `query:"param-default" json:"param-default" default:"default" validate:"required"`
160-
ParamPtr *string `query:"param-ptr" json:"param-ptr"`
161-
ParamComplex time.Time `query:"param-complex" json:"param-complex"`
162-
ParamExplode []string `query:"param-explode" json:"param-explode" explode:"true"`
163-
ParamExplodeDisabled []string `query:"param-explode-disabled" json:"param-explode-disabled" explode:"false"`
164-
ParamExplodeString string `query:"param-explode-string" json:"param-explode-string" explode:"true"`
165-
ParamExplodeDefault []string `query:"param-explode-default" json:"param-explode-default" default:"1,2,3" explode:"true"`
166-
ParamExplodeDefaultDisabled []string `query:"param-explode-disabled-default" json:"param-explode-disabled-default" default:"1,2,3" explode:"false"`
163+
Param string `query:"param" json:"param" validate:"required"`
164+
ParamOptional string `query:"param-optional" json:"param-optional"`
165+
Params []string `query:"params" json:"params"`
166+
ParamInt int `query:"param-int" json:"param-int"`
167+
ParamBool bool `query:"param-bool" json:"param-bool"`
168+
ParamDefault string `query:"param-default" json:"param-default" default:"default" validate:"required"`
169+
ParamPtr *string `query:"param-ptr" json:"param-ptr"`
170+
ParamComplex time.Time `query:"param-complex" json:"param-complex"`
171+
ParamListNoSplit []string `query:"param-list-nosplit" json:"param-list-nosplit"`
172+
ParamListSplit []string `query:"param-list-split" json:"param-list-split" commalist:"true"`
173+
ParamListStringNoSplit string `query:"param-list-string-nosplit" json:"param-list-string-nosplit"`
174+
ParamListDefault []string `query:"param-list-default" json:"param-list-default" default:"1,2,3" commalist:"true"`
167175
*DoubleEmbedded
168176
}
169177

0 commit comments

Comments
 (0)