mirror of
https://github.com/gin-gonic/gin.git
synced 2025-05-22 12:19:16 +08:00
Merge c1fa092ad1b3bd3f85c07dae9d705b0bc2657d15 into 674522db91d637d179c16c372d87756ea26fa089
This commit is contained in:
commit
31c69d45a8
@ -23,6 +23,7 @@ const (
|
|||||||
MIMEYAML = "application/x-yaml"
|
MIMEYAML = "application/x-yaml"
|
||||||
MIMEYAML2 = "application/yaml"
|
MIMEYAML2 = "application/yaml"
|
||||||
MIMETOML = "application/toml"
|
MIMETOML = "application/toml"
|
||||||
|
MIMEBSON = "application/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Binding describes the interface which needs to be implemented for binding the
|
// Binding describes the interface which needs to be implemented for binding the
|
||||||
@ -86,6 +87,7 @@ var (
|
|||||||
Header Binding = headerBinding{}
|
Header Binding = headerBinding{}
|
||||||
Plain BindingBody = plainBinding{}
|
Plain BindingBody = plainBinding{}
|
||||||
TOML BindingBody = tomlBinding{}
|
TOML BindingBody = tomlBinding{}
|
||||||
|
BSON BindingBody = bsonBinding{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default returns the appropriate Binding instance based on the HTTP method
|
// Default returns the appropriate Binding instance based on the HTTP method
|
||||||
@ -110,6 +112,8 @@ func Default(method, contentType string) Binding {
|
|||||||
return TOML
|
return TOML
|
||||||
case MIMEMultipartPOSTForm:
|
case MIMEMultipartPOSTForm:
|
||||||
return FormMultipart
|
return FormMultipart
|
||||||
|
case MIMEBSON:
|
||||||
|
return BSON
|
||||||
default: // case MIMEPOSTForm:
|
default: // case MIMEPOSTForm:
|
||||||
return Form
|
return Form
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin/testdata/protoexample"
|
"github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -174,6 +175,9 @@ func TestBindingDefault(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, TOML, Default(http.MethodPost, MIMETOML))
|
assert.Equal(t, TOML, Default(http.MethodPost, MIMETOML))
|
||||||
assert.Equal(t, TOML, Default(http.MethodPut, MIMETOML))
|
assert.Equal(t, TOML, Default(http.MethodPut, MIMETOML))
|
||||||
|
|
||||||
|
assert.Equal(t, BSON, Default(http.MethodPost, MIMEBSON))
|
||||||
|
assert.Equal(t, BSON, Default(http.MethodPut, MIMEBSON))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingJSONNilBody(t *testing.T) {
|
func TestBindingJSONNilBody(t *testing.T) {
|
||||||
@ -733,6 +737,16 @@ func TestBindingProtoBufFail(t *testing.T) {
|
|||||||
string(data), string(data[1:]))
|
string(data), string(data[1:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBindingBSON(t *testing.T) {
|
||||||
|
var obj FooStruct
|
||||||
|
obj.Foo = "bar"
|
||||||
|
data, _ := bson.Marshal(&obj)
|
||||||
|
testBodyBinding(t,
|
||||||
|
BSON, "bson",
|
||||||
|
"/", "/",
|
||||||
|
string(data), string(data[1:])) //note: for badbody, we remove first byte
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidationFails(t *testing.T) {
|
func TestValidationFails(t *testing.T) {
|
||||||
var obj FooStruct
|
var obj FooStruct
|
||||||
req := requestWithBody(http.MethodPost, "/", `{"bar": "foo"}`)
|
req := requestWithBody(http.MethodPost, "/", `{"bar": "foo"}`)
|
||||||
|
30
binding/bson.go
Normal file
30
binding/bson.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bsonBinding struct{}
|
||||||
|
|
||||||
|
func (bsonBinding) Name() string {
|
||||||
|
return "bson"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bsonBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
buf, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.BindBody(buf, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bsonBinding) BindBody(body []byte, obj any) error {
|
||||||
|
return bson.Unmarshal(body, obj)
|
||||||
|
}
|
11
context.go
11
context.go
@ -37,6 +37,7 @@ const (
|
|||||||
MIMEYAML = binding.MIMEYAML
|
MIMEYAML = binding.MIMEYAML
|
||||||
MIMEYAML2 = binding.MIMEYAML2
|
MIMEYAML2 = binding.MIMEYAML2
|
||||||
MIMETOML = binding.MIMETOML
|
MIMETOML = binding.MIMETOML
|
||||||
|
MIMEBSON = binding.MIMEBSON
|
||||||
)
|
)
|
||||||
|
|
||||||
// BodyBytesKey indicates a default body bytes key.
|
// BodyBytesKey indicates a default body bytes key.
|
||||||
@ -1144,6 +1145,11 @@ func (c *Context) ProtoBuf(code int, obj any) {
|
|||||||
c.Render(code, render.ProtoBuf{Data: obj})
|
c.Render(code, render.ProtoBuf{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BSON serializes the given struct as BSON into the response body.
|
||||||
|
func (c *Context) BSON(code int, obj any) {
|
||||||
|
c.Render(code, render.BSON{Data: obj})
|
||||||
|
}
|
||||||
|
|
||||||
// String writes the given string into the response body.
|
// String writes the given string into the response body.
|
||||||
func (c *Context) String(code int, format string, values ...any) {
|
func (c *Context) String(code int, format string, values ...any) {
|
||||||
c.Render(code, render.String{Format: format, Data: values})
|
c.Render(code, render.String{Format: format, Data: values})
|
||||||
@ -1250,6 +1256,7 @@ type Negotiate struct {
|
|||||||
YAMLData any
|
YAMLData any
|
||||||
Data any
|
Data any
|
||||||
TOMLData any
|
TOMLData any
|
||||||
|
BSONData any
|
||||||
}
|
}
|
||||||
|
|
||||||
// Negotiate calls different Render according to acceptable Accept format.
|
// Negotiate calls different Render according to acceptable Accept format.
|
||||||
@ -1275,6 +1282,10 @@ func (c *Context) Negotiate(code int, config Negotiate) {
|
|||||||
data := chooseData(config.TOMLData, config.Data)
|
data := chooseData(config.TOMLData, config.Data)
|
||||||
c.TOML(code, data)
|
c.TOML(code, data)
|
||||||
|
|
||||||
|
case binding.MIMEBSON:
|
||||||
|
data := chooseData(config.BSONData, config.Data)
|
||||||
|
c.BSON(code, data)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck
|
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1499,6 +1500,23 @@ func TestContextNegotiationWithHTML(t *testing.T) {
|
|||||||
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextNegotiationWithBSON(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
c.Request, _ = http.NewRequest(http.MethodPost, "", nil)
|
||||||
|
|
||||||
|
c.Negotiate(http.StatusOK, Negotiate{
|
||||||
|
Offered: []string{MIMEBSON, MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2},
|
||||||
|
Data: H{"foo": "bar"},
|
||||||
|
})
|
||||||
|
|
||||||
|
bData, _ := bson.Marshal(H{"foo": "bar"})
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, string(bData), w.Body.String())
|
||||||
|
assert.Equal(t, "application/bson", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextNegotiationNotSupport(t *testing.T) {
|
func TestContextNegotiationNotSupport(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
1
go.mod
1
go.mod
@ -35,6 +35,7 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
go.mongodb.org/mongo-driver v1.17.2 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||||
golang.org/x/crypto v0.38.0 // indirect
|
golang.org/x/crypto v0.38.0 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -78,6 +78,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
|
||||||
|
go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||||
|
36
render/bson.go
Normal file
36
render/bson.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BSON contains the given interface object.
|
||||||
|
type BSON struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
var bsonContentType = []string{"application/bson"}
|
||||||
|
|
||||||
|
// Render (BSON) marshals the given interface object and writes data with custom ContentType.
|
||||||
|
func (r BSON) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
|
||||||
|
bytes, err := bson.Marshal(&r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(bytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (BSONBuf) writes BSONBuf ContentType.
|
||||||
|
func (r BSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, bsonContentType)
|
||||||
|
}
|
@ -19,6 +19,7 @@ import (
|
|||||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -352,6 +353,28 @@ func TestRenderProtoBufFail(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderBSON(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
type mystruct struct {
|
||||||
|
Label string
|
||||||
|
Reps []int64
|
||||||
|
}
|
||||||
|
var data mystruct = mystruct{
|
||||||
|
Label: "test",
|
||||||
|
Reps: []int64{int64(1), int64(2)}}
|
||||||
|
|
||||||
|
(BSON{data}).WriteContentType(w)
|
||||||
|
bsonData, err := bson.Marshal(data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "application/bson", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
err = (BSON{data}).Render(w)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, bsonData, w.Body.Bytes())
|
||||||
|
assert.Equal(t, "application/bson", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestRenderXML(t *testing.T) {
|
func TestRenderXML(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := xmlmap{
|
data := xmlmap{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user