Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(binding): add BindPlain #3904

Merged
merged 2 commits into from May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)

Check warning on line 28 in binding/plain.go

View check run for this annotation

Codecov / codecov/patch

binding/plain.go#L27-L28

Added lines #L27 - L28 were not covered by tests
}

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution should be exercised regarding permission modifications to files

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two lines of code are actually the same, except for the representation of file permissions using different formats for octal notation.

In the first line, the file permissions are represented using octal notation, specifically 0750. In octal notation, a number starting with 0 denotes that it's an octal number. Therefore, 0750 represents permissions set to rwxr-x--- (i.e., the owner has read, write, and execute permissions, the group users have read and execute permissions, and others have no permissions).

In the second line, the new octal literal notation is used, denoted by 0o750. In this notation, numbers start with 0o to indicate octal representation. So, 0o750 also represents permissions set to rwxr-x---.

In summary, these two lines of code are entirely equivalent; they just use different formats for representing permissions when using octal notation.

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 @@ -1657,6 +1657,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 @@ -1803,6 +1828,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 @@ -2234,6 +2284,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