From 16cd8cdd4ef9257b1e86b5119df1536de71c6a87 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 25 Oct 2020 17:08:30 +0800 Subject: [PATCH 1/3] ci: romove go1.11 for gin1.7 (#2540) --- .travis.yml | 2 -- README.md | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d7086b38..0795665d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ language: go matrix: fast_finish: true include: - - go: 1.11.x - env: GO111MODULE=on - go: 1.12.x env: GO111MODULE=on - go: 1.13.x diff --git a/README.md b/README.md index 7e8359ee..18b19430 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. The first need [Go](https://golang.org/) installed (**version 1.11+ is required**), then you can use the below Go command to install Gin. +1. The first need [Go](https://golang.org/) installed (**version 1.12+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin From 65ed60ed1334140d8583a3d0533cea76c66b69fe Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Fri, 30 Oct 2020 23:20:47 +0000 Subject: [PATCH 2/3] Allow bind with a map[string]string (#2484) Co-authored-by: thinkerou --- binding/binding_test.go | 93 +++++++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 41 ++++++++++++++++++ binding/json_test.go | 9 ++++ 3 files changed, 143 insertions(+) diff --git a/binding/binding_test.go b/binding/binding_test.go index 4424bab9..c354be94 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -13,6 +13,7 @@ import ( "mime/multipart" "net/http" "os" + "reflect" "strconv" "strings" "testing" @@ -200,6 +201,12 @@ func TestBindingJSONDisallowUnknownFields(t *testing.T) { `{"foo": "bar"}`, `{"foo": "bar", "what": "this"}`) } +func TestBindingJSONStringMap(t *testing.T) { + testBodyBindingStringMap(t, JSON, + "/", "/", + `{"foo": "bar", "hello": "world"}`, `{"num": 2}`) +} + func TestBindingForm(t *testing.T) { testFormBinding(t, "POST", "/", "/", @@ -336,6 +343,37 @@ func TestBindingFormForType(t *testing.T) { "", "", "StructPointer") } +func TestBindingFormStringMap(t *testing.T) { + testBodyBindingStringMap(t, Form, + "/", "", + `foo=bar&hello=world`, "") + // Should pick the last value + testBodyBindingStringMap(t, Form, + "/", "", + `foo=something&foo=bar&hello=world`, "") +} + +func TestBindingFormStringSliceMap(t *testing.T) { + obj := make(map[string][]string) + req := requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") + req.Header.Add("Content-Type", MIMEPOSTForm) + err := Form.Bind(req, &obj) + assert.NoError(t, err) + assert.NotNil(t, obj) + assert.Len(t, obj, 2) + target := map[string][]string{ + "foo": {"something", "bar"}, + "hello": {"world"}, + } + assert.True(t, reflect.DeepEqual(obj, target)) + + objInvalid := make(map[string][]int) + req = requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") + req.Header.Add("Content-Type", MIMEPOSTForm) + err = Form.Bind(req, &objInvalid) + assert.Error(t, err) +} + func TestBindingQuery(t *testing.T) { testQueryBinding(t, "POST", "/?foo=bar&bar=foo", "/", @@ -366,6 +404,28 @@ func TestBindingQueryBoolFail(t *testing.T) { "bool_foo=unused", "") } +func TestBindingQueryStringMap(t *testing.T) { + b := Query + + obj := make(map[string]string) + req := requestWithBody("GET", "/?foo=bar&hello=world", "") + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.NotNil(t, obj) + assert.Len(t, obj, 2) + assert.Equal(t, "bar", obj["foo"]) + assert.Equal(t, "world", obj["hello"]) + + obj = make(map[string]string) + req = requestWithBody("GET", "/?foo=bar&foo=2&hello=world", "") // should pick last + err = b.Bind(req, &obj) + assert.NoError(t, err) + assert.NotNil(t, obj) + assert.Len(t, obj, 2) + assert.Equal(t, "2", obj["foo"]) + assert.Equal(t, "world", obj["hello"]) +} + func TestBindingXML(t *testing.T) { testBodyBinding(t, XML, "xml", @@ -387,6 +447,13 @@ func TestBindingYAML(t *testing.T) { `foo: bar`, `bar: foo`) } +func TestBindingYAMLStringMap(t *testing.T) { + // YAML is a superset of JSON, so the test below is JSON (to avoid newlines) + testBodyBindingStringMap(t, YAML, + "/", "/", + `{"foo": "bar", "hello": "world"}`, `{"nested": {"foo": "bar"}}`) +} + func TestBindingYAMLFail(t *testing.T) { testBodyBindingFail(t, YAML, "yaml", @@ -1114,6 +1181,32 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody assert.Error(t, err) } +func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) { + obj := make(map[string]string) + req := requestWithBody("POST", path, body) + if b.Name() == "form" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.NotNil(t, obj) + assert.Len(t, obj, 2) + assert.Equal(t, "bar", obj["foo"]) + assert.Equal(t, "world", obj["hello"]) + + if badPath != "" && badBody != "" { + obj = make(map[string]string) + req = requestWithBody("POST", badPath, badBody) + err = b.Bind(req, &obj) + assert.Error(t, err) + } + + objInt := make(map[string]int) + req = requestWithBody("POST", path, body) + err = b.Bind(req, &objInt) + assert.Error(t, err) +} + func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index f0913ea5..2f4e45b4 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -29,6 +29,21 @@ func mapForm(ptr interface{}, form map[string][]string) error { var emptyField = reflect.StructField{} func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { + // Check if ptr is a map + ptrVal := reflect.ValueOf(ptr) + var pointed interface{} + if ptrVal.Kind() == reflect.Ptr { + ptrVal = ptrVal.Elem() + pointed = ptrVal.Interface() + } + if ptrVal.Kind() == reflect.Map && + ptrVal.Type().Key().Kind() == reflect.String { + if pointed != nil { + ptr = pointed + } + return setFormMap(ptr, form) + } + return mappingByPtr(ptr, formSource(form), tag) } @@ -349,3 +364,29 @@ func head(str, sep string) (head string, tail string) { } return str[:idx], str[idx+len(sep):] } + +func setFormMap(ptr interface{}, form map[string][]string) error { + el := reflect.TypeOf(ptr).Elem() + + if el.Kind() == reflect.Slice { + ptrMap, ok := ptr.(map[string][]string) + if !ok { + return errors.New("cannot convert to map slices of strings") + } + for k, v := range form { + ptrMap[k] = v + } + + return nil + } + + ptrMap, ok := ptr.(map[string]string) + if !ok { + return errors.New("cannot convert to map of strings") + } + for k, v := range form { + ptrMap[k] = v[len(v)-1] // pick last + } + + return nil +} diff --git a/binding/json_test.go b/binding/json_test.go index cae4cccc..fbd5c527 100644 --- a/binding/json_test.go +++ b/binding/json_test.go @@ -19,3 +19,12 @@ func TestJSONBindingBindBody(t *testing.T) { require.NoError(t, err) assert.Equal(t, "FOO", s.Foo) } + +func TestJSONBindingBindBodyMap(t *testing.T) { + s := make(map[string]string) + err := jsonBinding{}.BindBody([]byte(`{"foo": "FOO","hello":"world"}`), &s) + require.NoError(t, err) + assert.Len(t, s, 2) + assert.Equal(t, "FOO", s["foo"]) + assert.Equal(t, "world", s["hello"]) +} From 7742ff50e0a05d079a0c468ccfbf7c6ecfe2414b Mon Sep 17 00:00:00 2001 From: "An Xiao (Luffy)" Date: Wed, 11 Nov 2020 09:41:35 +0800 Subject: [PATCH 3/3] Fix typos in context.go (#2551) --- context.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 71fb5937..3d6b56d6 100644 --- a/context.go +++ b/context.go @@ -891,7 +891,7 @@ func (c *Context) SecureJSON(code int, obj interface{}) { } // JSONP serializes the given struct as JSON into the response body. -// It add padding to response body to request data from a server residing in a different domain than the client. +// It adds padding to response body to request data from a server residing in a different domain than the client. // It also sets the Content-Type as "application/javascript". func (c *Context) JSONP(code int, obj interface{}) { callback := c.DefaultQuery("callback", "") @@ -968,7 +968,7 @@ func (c *Context) DataFromReader(code int, contentLength int64, contentType stri }) } -// File writes the specified file into the body stream in a efficient way. +// File writes the specified file into the body stream in an efficient way. func (c *Context) File(filepath string) { http.ServeFile(c.Writer, c.Request, filepath) }