From 84566648ccc153f2dd1c6896ba08ba218838811e Mon Sep 17 00:00:00 2001 From: guonaihong Date: Fri, 6 Dec 2019 15:49:43 +0800 Subject: [PATCH 1/4] fix empty value error Here is the code that can report an error ```go package main import ( "fmt" "github.com/gin-gonic/gin" "io" "net/http" "os" "time" ) type header struct { Duration time.Duration `header:"duration"` CreateTime time.Time `header:"createTime" time_format:"unix"` } func needFix1() { g := gin.Default() g.GET("/", func(c *gin.Context) { h := header{} err := c.ShouldBindHeader(&h) if err != nil { c.JSON(500, fmt.Sprintf("fail:%s\n", err)) return } c.JSON(200, h) }) g.Run(":8081") } func needFix2() { g := gin.Default() g.GET("/", func(c *gin.Context) { h := header{} err := c.ShouldBindHeader(&h) if err != nil { c.JSON(500, fmt.Sprintf("fail:%s\n", err)) return } c.JSON(200, h) }) g.Run(":8082") } func sendNeedFix1() { // send to needFix1 sendBadData("http://127.0.0.1:8081", "duration") } func sendNeedFix2() { // send to needFix2 sendBadData("http://127.0.0.1:8082", "createTime") } func sendBadData(url, key string) { req, err := http.NewRequest("GET", "http://127.0.0.1:8081", nil) if err != nil { fmt.Printf("err:%s\n", err) return } // Only the key and no value can cause an error req.Header.Add(key, "") rsp, err := http.DefaultClient.Do(req) if err != nil { return } io.Copy(os.Stdout, rsp.Body) rsp.Body.Close() } func main() { go needFix1() go needFix2() time.Sleep(time.Second / 1000 * 200) // 200ms sendNeedFix1() sendNeedFix2() } ``` --- binding/binding_test.go | 27 +++++++++++++++++++++++++++ binding/form_mapping.go | 14 +++++++++----- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 07619ebf..e03a59f5 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1416,3 +1416,30 @@ func requestWithBody(method, path, body string) (req *http.Request) { req, _ = http.NewRequest(method, path, bytes.NewBufferString(body)) return } + +type bindTestData struct { + need interface{} + got interface{} + in map[string][]string +} + +func Test_Binding_BaseType(t *testing.T) { + type needFixDurationEmpty struct { + Duration time.Duration `form:"duration"` + } + + type needFixUnixNanoEmpty struct { + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + } + + tests := []bindTestData{ + {need: &needFixDurationEmpty{}, got: &needFixDurationEmpty{}, in: http.Header{"duration": []string{}}}, + {need: &needFixUnixNanoEmpty{}, got: &needFixUnixNanoEmpty{}, in: http.Header{"createTime": []string{}}}, + } + + for _, v := range tests { + err := mapForm(v.got, v.in) + assert.NoError(t, err) + assert.Equal(t, v.need, v.got) + } +} diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 1244b522..743cfdbc 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -404,6 +404,11 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val timeFormat = time.RFC3339 } + if val == "" { + value.Set(reflect.ValueOf(time.Time{})) + return nil + } + switch tf := strings.ToLower(timeFormat); tf { case "unix", "unixmilli", "unixmicro", "unixnano": tv, err := strconv.ParseInt(val, 10, 64) @@ -427,11 +432,6 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val return nil } - if val == "" { - value.Set(reflect.ValueOf(time.Time{})) - return nil - } - l := time.Local if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { l = time.UTC @@ -475,6 +475,10 @@ func setSlice(vals []string, value reflect.Value, field reflect.StructField) err } func setTimeDuration(val string, value reflect.Value) error { + if val == "" { + val = "0" + } + d, err := time.ParseDuration(val) if err != nil { return err From 6341a255c1aa722f34a1294ac5c4cae187366d8b Mon Sep 17 00:00:00 2001 From: guonaihong Date: Wed, 11 Dec 2019 19:05:54 +0800 Subject: [PATCH 2/4] modify code --- binding/binding_test.go | 27 ------------------------- binding/form_mapping.go | 4 ++++ binding/form_mapping_test.go | 39 ++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index e03a59f5..07619ebf 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1416,30 +1416,3 @@ func requestWithBody(method, path, body string) (req *http.Request) { req, _ = http.NewRequest(method, path, bytes.NewBufferString(body)) return } - -type bindTestData struct { - need interface{} - got interface{} - in map[string][]string -} - -func Test_Binding_BaseType(t *testing.T) { - type needFixDurationEmpty struct { - Duration time.Duration `form:"duration"` - } - - type needFixUnixNanoEmpty struct { - CreateTime time.Time `form:"createTime" time_format:"unixNano"` - } - - tests := []bindTestData{ - {need: &needFixDurationEmpty{}, got: &needFixDurationEmpty{}, in: http.Header{"duration": []string{}}}, - {need: &needFixUnixNanoEmpty{}, got: &needFixUnixNanoEmpty{}, in: http.Header{"createTime": []string{}}}, - } - - for _, v := range tests { - err := mapForm(v.got, v.in) - assert.NoError(t, err) - assert.Equal(t, v.need, v.got) - } -} diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 743cfdbc..41f38b94 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -300,6 +300,10 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ } func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { + if value.Kind() != reflect.String { + val = strings.TrimSpace(val) + } + switch value.Kind() { case reflect.Int: return setIntField(val, 0, value) diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 006eddf1..e1a7752b 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -226,7 +226,35 @@ func TestMappingTime(t *testing.T) { require.Error(t, err) } +type bindTestData struct { + need interface{} + got interface{} + in map[string][]string +} + +func TestMappingTimeUnixNano(t *testing.T) { + type needFixUnixNanoEmpty struct { + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + } + + // ok + tests := []bindTestData{ + {need: &needFixUnixNanoEmpty{}, got: &needFixUnixNanoEmpty{}, in: formSource{"createTime": []string{" "}}}, + {need: &needFixUnixNanoEmpty{}, got: &needFixUnixNanoEmpty{}, in: formSource{"createTime": []string{}}}, + } + + for _, v := range tests { + err := mapForm(v.got, v.in) + assert.NoError(t, err) + assert.Equal(t, v.need, v.got) + } +} + func TestMappingTimeDuration(t *testing.T) { + type needFixDurationEmpty struct { + Duration time.Duration `form:"duration"` + } + var s struct { D time.Duration } @@ -236,6 +264,17 @@ func TestMappingTimeDuration(t *testing.T) { require.NoError(t, err) assert.Equal(t, 5*time.Second, s.D) + // ok + tests := []bindTestData{ + {need: &needFixDurationEmpty{}, got: &needFixDurationEmpty{}, in: formSource{"duration": []string{" "}}}, + {need: &needFixDurationEmpty{}, got: &needFixDurationEmpty{}, in: formSource{"duration": []string{}}}, + } + + for _, v := range tests { + err := mapForm(v.got, v.in) + assert.NoError(t, err) + assert.Equal(t, v.need, v.got) + } // error err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form") require.Error(t, err) From 2891214fac22630f02ae3e0b5bec1aa5b88e3a73 Mon Sep 17 00:00:00 2001 From: guonaihong Date: Thu, 16 Jan 2020 22:52:42 +0800 Subject: [PATCH 3/4] add comment --- binding/form_mapping.go | 1 + 1 file changed, 1 insertion(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 41f38b94..e76e7510 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -300,6 +300,7 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ } func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { + // If it is a string type, no spaces are removed, and the user data is not modified here if value.Kind() != reflect.String { val = strings.TrimSpace(val) } From af5561cdbc885569b9707202072118ffcc56e37c Mon Sep 17 00:00:00 2001 From: guonaihong Date: Sun, 30 Nov 2025 21:08:43 +0800 Subject: [PATCH 4/4] test(binding): use 'any' alias and require.NoError in form mapping tests - Replace 'interface{}' with 'any' alias in bindTestData struct - Change assert.NoError to require.NoError in TestMappingTimeUnixNano and TestMappingTimeDuration to fail fast on mapping errors --- binding/form_mapping_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index e1a7752b..e007573c 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -227,8 +227,8 @@ func TestMappingTime(t *testing.T) { } type bindTestData struct { - need interface{} - got interface{} + need any + got any in map[string][]string } @@ -245,7 +245,7 @@ func TestMappingTimeUnixNano(t *testing.T) { for _, v := range tests { err := mapForm(v.got, v.in) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, v.need, v.got) } } @@ -272,7 +272,7 @@ func TestMappingTimeDuration(t *testing.T) { for _, v := range tests { err := mapForm(v.got, v.in) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, v.need, v.got) } // error