feat(form): add array collection format in form binding (#3986)
* feat(form): add array collection format in form binding * feat(form): add array collection format in form binding * test(form): fix test code for array collection format in form binding
This commit is contained in:
parent
cc4e11438c
commit
3cb30679b5
@ -182,6 +182,38 @@ func trySetCustom(val string, value reflect.Value) (isSet bool, err error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trySplit(vs []string, field reflect.StructField) (newVs []string, err error) {
|
||||||
|
cfTag := field.Tag.Get("collection_format")
|
||||||
|
if cfTag == "" || cfTag == "multi" {
|
||||||
|
return vs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var sep string
|
||||||
|
switch cfTag {
|
||||||
|
case "csv":
|
||||||
|
sep = ","
|
||||||
|
case "ssv":
|
||||||
|
sep = " "
|
||||||
|
case "tsv":
|
||||||
|
sep = "\t"
|
||||||
|
case "pipes":
|
||||||
|
sep = "|"
|
||||||
|
default:
|
||||||
|
return vs, fmt.Errorf("%s is not supported in the collection_format. (csv, ssv, pipes)", cfTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalLength := 0
|
||||||
|
for _, v := range vs {
|
||||||
|
totalLength += strings.Count(v, sep) + 1
|
||||||
|
}
|
||||||
|
newVs = make([]string, 0, totalLength)
|
||||||
|
for _, v := range vs {
|
||||||
|
newVs = append(newVs, strings.Split(v, sep)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newVs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
|
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
|
||||||
vs, ok := form[tagValue]
|
vs, ok := form[tagValue]
|
||||||
if !ok && !opt.isDefaultExists {
|
if !ok && !opt.isDefaultExists {
|
||||||
@ -198,6 +230,10 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
|||||||
return ok, err
|
return ok, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if vs, err = trySplit(vs, field); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
return true, setSlice(vs, value, field)
|
return true, setSlice(vs, value, field)
|
||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -208,6 +244,10 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
|||||||
return ok, err
|
return ok, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if vs, err = trySplit(vs, field); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
if len(vs) != value.Len() {
|
if len(vs) != value.Len() {
|
||||||
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
|
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
|
||||||
}
|
}
|
||||||
|
@ -264,6 +264,45 @@ func TestMappingArray(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMappingCollectionFormat(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
SliceMulti []int `form:"slice_multi" collection_format:"multi"`
|
||||||
|
SliceCsv []int `form:"slice_csv" collection_format:"csv"`
|
||||||
|
SliceSsv []int `form:"slice_ssv" collection_format:"ssv"`
|
||||||
|
SliceTsv []int `form:"slice_tsv" collection_format:"tsv"`
|
||||||
|
SlicePipes []int `form:"slice_pipes" collection_format:"pipes"`
|
||||||
|
ArrayMulti [2]int `form:"array_multi" collection_format:"multi"`
|
||||||
|
ArrayCsv [2]int `form:"array_csv" collection_format:"csv"`
|
||||||
|
ArraySsv [2]int `form:"array_ssv" collection_format:"ssv"`
|
||||||
|
ArrayTsv [2]int `form:"array_tsv" collection_format:"tsv"`
|
||||||
|
ArrayPipes [2]int `form:"array_pipes" collection_format:"pipes"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{
|
||||||
|
"slice_multi": {"1", "2"},
|
||||||
|
"slice_csv": {"1,2"},
|
||||||
|
"slice_ssv": {"1 2"},
|
||||||
|
"slice_tsv": {"1 2"},
|
||||||
|
"slice_pipes": {"1|2"},
|
||||||
|
"array_multi": {"1", "2"},
|
||||||
|
"array_csv": {"1,2"},
|
||||||
|
"array_ssv": {"1 2"},
|
||||||
|
"array_tsv": {"1 2"},
|
||||||
|
"array_pipes": {"1|2"},
|
||||||
|
}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, []int{1, 2}, s.SliceMulti)
|
||||||
|
assert.Equal(t, []int{1, 2}, s.SliceCsv)
|
||||||
|
assert.Equal(t, []int{1, 2}, s.SliceSsv)
|
||||||
|
assert.Equal(t, []int{1, 2}, s.SliceTsv)
|
||||||
|
assert.Equal(t, []int{1, 2}, s.SlicePipes)
|
||||||
|
assert.Equal(t, [2]int{1, 2}, s.ArrayMulti)
|
||||||
|
assert.Equal(t, [2]int{1, 2}, s.ArrayCsv)
|
||||||
|
assert.Equal(t, [2]int{1, 2}, s.ArraySsv)
|
||||||
|
assert.Equal(t, [2]int{1, 2}, s.ArrayTsv)
|
||||||
|
assert.Equal(t, [2]int{1, 2}, s.ArrayPipes)
|
||||||
|
}
|
||||||
|
|
||||||
func TestMappingStructField(t *testing.T) {
|
func TestMappingStructField(t *testing.T) {
|
||||||
var s struct {
|
var s struct {
|
||||||
J struct {
|
J struct {
|
||||||
|
54
docs/doc.md
54
docs/doc.md
@ -26,6 +26,7 @@
|
|||||||
- [Custom Validators](#custom-validators)
|
- [Custom Validators](#custom-validators)
|
||||||
- [Only Bind Query String](#only-bind-query-string)
|
- [Only Bind Query String](#only-bind-query-string)
|
||||||
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
||||||
|
- [Collection format for arrays](#collection-format-for-arrays)
|
||||||
- [Bind Uri](#bind-uri)
|
- [Bind Uri](#bind-uri)
|
||||||
- [Bind custom unmarshaler](#bind-custom-unmarshaler)
|
- [Bind custom unmarshaler](#bind-custom-unmarshaler)
|
||||||
- [Bind Header](#bind-header)
|
- [Bind Header](#bind-header)
|
||||||
@ -861,6 +862,59 @@ Test it with:
|
|||||||
curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
|
curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Collection format for arrays
|
||||||
|
|
||||||
|
| Format | Description | Example |
|
||||||
|
| --------------- | --------------------------------------------------------- | ----------------------- |
|
||||||
|
| multi (default) | Multiple parameter instances rather than multiple values. | key=foo&key=bar&key=baz |
|
||||||
|
| csv | Comma-separated values. | foo,bar,baz |
|
||||||
|
| ssv | Space-separated values. | foo bar baz |
|
||||||
|
| tsv | Tab-separated values. | "foo\tbar\tbaz" |
|
||||||
|
| pipes | Pipe-separated values. | foo\|bar\|baz |
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Name string `form:"name"`
|
||||||
|
Addresses []string `form:"addresses" collection_format:"csv"`
|
||||||
|
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
|
||||||
|
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
|
||||||
|
UnixTime time.Time `form:"unixTime" time_format:"unix"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
route := gin.Default()
|
||||||
|
route.GET("/testing", startPage)
|
||||||
|
route.Run(":8085")
|
||||||
|
}
|
||||||
|
func startPage(c *gin.Context) {
|
||||||
|
var person Person
|
||||||
|
// If `GET`, only `Form` binding engine (`query`) used.
|
||||||
|
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
|
||||||
|
// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
|
||||||
|
if c.ShouldBind(&person) == nil {
|
||||||
|
log.Println(person.Name)
|
||||||
|
log.Println(person.Addresses)
|
||||||
|
log.Println(person.Birthday)
|
||||||
|
log.Println(person.CreateTime)
|
||||||
|
log.Println(person.UnixTime)
|
||||||
|
}
|
||||||
|
c.String(200, "Success")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Test it with:
|
||||||
|
```sh
|
||||||
|
$ curl -X GET "localhost:8085/testing?name=appleboy&addresses=foo,bar&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
|
||||||
|
```
|
||||||
|
|
||||||
### Bind Uri
|
### Bind Uri
|
||||||
|
|
||||||
See the [detail information](https://github.com/gin-gonic/gin/issues/846).
|
See the [detail information](https://github.com/gin-gonic/gin/issues/846).
|
||||||
|
Loading…
Reference in New Issue
Block a user