Skip to content

Commit

Permalink
feat(binding): add BindPlain (#3904)
Browse files Browse the repository at this point in the history
* add BindPlain

* fix ci/cd error
  • Loading branch information
guonaihong committed May 13, 2024
1 parent 40131af commit 6ca8ddb
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 1 deletion.
1 change: 1 addition & 0 deletions binding/binding.go
Expand Up @@ -84,6 +84,7 @@ var (
YAML BindingBody = yamlBinding{}
Uri BindingUri = uriBinding{}
Header Binding = headerBinding{}
Plain BindingBody = plainBinding{}
TOML BindingBody = tomlBinding{}
)

Expand Down
1 change: 1 addition & 0 deletions binding/binding_nomsgpack.go
Expand Up @@ -81,6 +81,7 @@ var (
Uri = uriBinding{}
Header = headerBinding{}
TOML = tomlBinding{}
Plain = plainBinding{}
)

// Default returns the appropriate Binding instance based on the HTTP method
Expand Down
40 changes: 40 additions & 0 deletions binding/binding_test.go
Expand Up @@ -1342,6 +1342,46 @@ func (h hook) Read([]byte) (int, error) {
return 0, errors.New("error")
}

type failRead struct{}

func (f *failRead) Read(b []byte) (n int, err error) {
return 0, errors.New("my fail")
}

func (f *failRead) Close() error {
return nil
}

func TestPlainBinding(t *testing.T) {
p := Plain
assert.Equal(t, "plain", p.Name())

var s string
req := requestWithBody("POST", "/", "test string")
assert.NoError(t, p.Bind(req, &s))
assert.Equal(t, s, "test string")

var bs []byte
req = requestWithBody("POST", "/", "test []byte")
assert.NoError(t, p.Bind(req, &bs))
assert.Equal(t, bs, []byte("test []byte"))

var i int
req = requestWithBody("POST", "/", "test fail")
assert.Error(t, p.Bind(req, &i))

req = requestWithBody("POST", "/", "")
req.Body = &failRead{}
assert.Error(t, p.Bind(req, &s))

req = requestWithBody("POST", "/", "")
assert.Nil(t, p.Bind(req, nil))

var ptr *string
req = requestWithBody("POST", "/", "")
assert.Nil(t, p.Bind(req, ptr))
}

func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name())

Expand Down
56 changes: 56 additions & 0 deletions binding/plain.go
@@ -0,0 +1,56 @@
package binding

import (
"fmt"
"io"
"net/http"
"reflect"

"github.com/gin-gonic/gin/internal/bytesconv"
)

type plainBinding struct{}

func (plainBinding) Name() string {
return "plain"
}

func (plainBinding) Bind(req *http.Request, obj interface{}) error {
all, err := io.ReadAll(req.Body)
if err != nil {
return err
}

return decodePlain(all, obj)
}

func (plainBinding) BindBody(body []byte, obj any) error {
return decodePlain(body, obj)
}

func decodePlain(data []byte, obj any) error {
if obj == nil {
return nil
}

v := reflect.ValueOf(obj)

for v.Kind() == reflect.Ptr {
if v.IsNil() {
return nil
}
v = v.Elem()
}

if v.Kind() == reflect.String {
v.SetString(bytesconv.BytesToString(data))
return nil
}

if _, ok := v.Interface().([]byte); ok {
v.SetBytes(data)
return nil
}

return fmt.Errorf("type (%T) unknown type", v)
}
17 changes: 16 additions & 1 deletion context.go
Expand Up @@ -614,7 +614,7 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
}
defer src.Close()

if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil {
if err = os.MkdirAll(filepath.Dir(dst), 0o750); err != nil {
return err
}

Expand Down Expand Up @@ -667,6 +667,11 @@ func (c *Context) BindTOML(obj any) error {
return c.MustBindWith(obj, binding.TOML)
}

// BindPlain is a shortcut for c.MustBindWith(obj, binding.Plain).
func (c *Context) BindPlain(obj any) error {
return c.MustBindWith(obj, binding.Plain)
}

// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
func (c *Context) BindHeader(obj any) error {
return c.MustBindWith(obj, binding.Header)
Expand Down Expand Up @@ -732,6 +737,11 @@ func (c *Context) ShouldBindTOML(obj any) error {
return c.ShouldBindWith(obj, binding.TOML)
}

// ShouldBindPlain is a shortcut for c.ShouldBindWith(obj, binding.Plain).
func (c *Context) ShouldBindPlain(obj any) error {
return c.ShouldBindWith(obj, binding.Plain)
}

// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
func (c *Context) ShouldBindHeader(obj any) error {
return c.ShouldBindWith(obj, binding.Header)
Expand Down Expand Up @@ -794,6 +804,11 @@ func (c *Context) ShouldBindBodyWithTOML(obj any) error {
return c.ShouldBindBodyWith(obj, binding.TOML)
}

// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON).
func (c *Context) ShouldBindBodyWithPlain(obj any) error {
return c.ShouldBindBodyWith(obj, binding.Plain)
}

// ClientIP implements one best effort algorithm to return the real client IP.
// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
Expand Down
124 changes: 124 additions & 0 deletions context_test.go
Expand Up @@ -1670,6 +1670,31 @@ func TestContextBindWithXML(t *testing.T) {
assert.Equal(t, 0, w.Body.Len())
}

func TestContextBindPlain(t *testing.T) {

// string
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`))
c.Request.Header.Add("Content-Type", MIMEPlain)

var s string

assert.NoError(t, c.BindPlain(&s))
assert.Equal(t, "test string", s)
assert.Equal(t, 0, w.Body.Len())

// []byte
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`))
c.Request.Header.Add("Content-Type", MIMEPlain)

var bs []byte

assert.NoError(t, c.BindPlain(&bs))
assert.Equal(t, []byte("test []byte"), bs)
assert.Equal(t, 0, w.Body.Len())
}

func TestContextBindHeader(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
Expand Down Expand Up @@ -1816,6 +1841,31 @@ func TestContextShouldBindWithXML(t *testing.T) {
assert.Equal(t, 0, w.Body.Len())
}

func TestContextShouldBindPlain(t *testing.T) {
// string
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`))
c.Request.Header.Add("Content-Type", MIMEPlain)

var s string

assert.NoError(t, c.ShouldBindPlain(&s))
assert.Equal(t, "test string", s)
assert.Equal(t, 0, w.Body.Len())
// []byte

c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`))
c.Request.Header.Add("Content-Type", MIMEPlain)

var bs []byte

assert.NoError(t, c.ShouldBindPlain(&bs))
assert.Equal(t, []byte("test []byte"), bs)
assert.Equal(t, 0, w.Body.Len())

}

func TestContextShouldBindHeader(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
Expand Down Expand Up @@ -2247,6 +2297,80 @@ func TestContextShouldBindBodyWithTOML(t *testing.T) {
}
}

func TestContextShouldBindBodyWithPlain(t *testing.T) {
for _, tt := range []struct {
name string
bindingBody binding.BindingBody
body string
}{
{
name: " JSON & JSON-BODY ",
bindingBody: binding.JSON,
body: `{"foo":"FOO"}`,
},
{
name: " JSON & XML-BODY ",
bindingBody: binding.XML,
body: `<?xml version="1.0" encoding="UTF-8"?>
<root>
<foo>FOO</foo>
</root>`,
},
{
name: " JSON & YAML-BODY ",
bindingBody: binding.YAML,
body: `foo: FOO`,
},
{
name: " JSON & TOM-BODY ",
bindingBody: binding.TOML,
body: `foo=FOO`,
},
{
name: " JSON & Plain-BODY ",
bindingBody: binding.Plain,
body: `foo=FOO`,
},
} {
t.Logf("testing: %s", tt.name)

w := httptest.NewRecorder()
c, _ := CreateTestContext(w)

c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body))

type typeJSON struct {
Foo string `json:"foo" binding:"required"`
}
objJSON := typeJSON{}

if tt.bindingBody == binding.Plain {
body := ""
assert.NoError(t, c.ShouldBindBodyWithPlain(&body))
assert.Equal(t, body, "foo=FOO")
}

if tt.bindingBody == binding.JSON {
assert.NoError(t, c.ShouldBindBodyWithJSON(&objJSON))
assert.Equal(t, typeJSON{"FOO"}, objJSON)
}

if tt.bindingBody == binding.XML {
assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON))
assert.Equal(t, typeJSON{}, objJSON)
}

if tt.bindingBody == binding.YAML {
assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON))
assert.Equal(t, typeJSON{}, objJSON)
}

if tt.bindingBody == binding.TOML {
assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON))
assert.Equal(t, typeJSON{}, objJSON)
}
}
}
func TestContextGolangContext(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
Expand Down

0 comments on commit 6ca8ddb

Please sign in to comment.