feat(binding): add BindPlain (#3904)

* add BindPlain

* fix ci/cd error
This commit is contained in:
guonaihong 2024-05-13 11:11:56 +08:00 committed by GitHub
parent 40131af124
commit 6ca8ddb1ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 238 additions and 1 deletions

View File

@ -84,6 +84,7 @@ var (
YAML BindingBody = yamlBinding{} YAML BindingBody = yamlBinding{}
Uri BindingUri = uriBinding{} Uri BindingUri = uriBinding{}
Header Binding = headerBinding{} Header Binding = headerBinding{}
Plain BindingBody = plainBinding{}
TOML BindingBody = tomlBinding{} TOML BindingBody = tomlBinding{}
) )

View File

@ -81,6 +81,7 @@ var (
Uri = uriBinding{} Uri = uriBinding{}
Header = headerBinding{} Header = headerBinding{}
TOML = tomlBinding{} TOML = tomlBinding{}
Plain = plainBinding{}
) )
// Default returns the appropriate Binding instance based on the HTTP method // Default returns the appropriate Binding instance based on the HTTP method

View File

@ -1342,6 +1342,46 @@ func (h hook) Read([]byte) (int, error) {
return 0, errors.New("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) { func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())

56
binding/plain.go Normal file
View File

@ -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)
}

View File

@ -614,7 +614,7 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
} }
defer src.Close() 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 return err
} }
@ -667,6 +667,11 @@ func (c *Context) BindTOML(obj any) error {
return c.MustBindWith(obj, binding.TOML) 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). // BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
func (c *Context) BindHeader(obj any) error { func (c *Context) BindHeader(obj any) error {
return c.MustBindWith(obj, binding.Header) return c.MustBindWith(obj, binding.Header)
@ -732,6 +737,11 @@ func (c *Context) ShouldBindTOML(obj any) error {
return c.ShouldBindWith(obj, binding.TOML) 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). // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
func (c *Context) ShouldBindHeader(obj any) error { func (c *Context) ShouldBindHeader(obj any) error {
return c.ShouldBindWith(obj, binding.Header) return c.ShouldBindWith(obj, binding.Header)
@ -794,6 +804,11 @@ func (c *Context) ShouldBindBodyWithTOML(obj any) error {
return c.ShouldBindBodyWith(obj, binding.TOML) 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. // 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. // 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]). // If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).

View File

@ -1670,6 +1670,31 @@ func TestContextBindWithXML(t *testing.T) {
assert.Equal(t, 0, w.Body.Len()) 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) { func TestContextBindHeader(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
@ -1816,6 +1841,31 @@ func TestContextShouldBindWithXML(t *testing.T) {
assert.Equal(t, 0, w.Body.Len()) 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) { func TestContextShouldBindHeader(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
@ -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) { func TestContextGolangContext(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))