Compare commits

...

5 Commits

Author SHA1 Message Date
laurentcau
de7d4517ac
Merge 1e846348731ff56f0a7e9899da0253eb12c2ef6c into 49e9137c68e6dfaa529a2d0c9fe64d9e69a8554e 2025-04-12 02:08:04 +08:00
NezhaFan
49e9137c68
docs: fix comment (#4205)
Co-authored-by: voyager1 <voyager1@voyager1deMacBook-Pro.local>
2025-04-12 00:00:59 +08:00
Adlai Bridson-Boyczuk
1b53a47790
docs: Fixing English grammar mistakes and awkward sentence structure in doc/doc.md (#4207)
* docs: Fixing grammar mistakes and awkward sentences, such as modeling
binding section

* Update doc.md

Missed grammar mistake
2025-04-11 23:59:03 +08:00
Andrey Bolonin
3afff295a2
docs: add Upd language list (#4211)
* Upd language list

* Update url
2025-04-11 23:58:02 +08:00
Laurent Caumont
1e84634873 add bson protocol 2025-01-23 11:02:16 +01:00
13 changed files with 153 additions and 13 deletions

View File

@ -103,6 +103,7 @@ The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in
- [Turkish](https://gin-gonic.com/tr/docs/)
- [Persian](https://gin-gonic.com/fa/docs/)
- [Português](https://gin-gonic.com/pt/docs/)
- [Russian](https://gin-gonic.com/ru/docs/)
### Articles

View File

@ -23,6 +23,7 @@ const (
MIMEYAML = "application/x-yaml"
MIMEYAML2 = "application/yaml"
MIMETOML = "application/toml"
MIMEBSON = "application/bson"
)
// Binding describes the interface which needs to be implemented for binding the
@ -86,6 +87,7 @@ var (
Header Binding = headerBinding{}
Plain BindingBody = plainBinding{}
TOML BindingBody = tomlBinding{}
BSON BindingBody = bsonBinding{}
)
// Default returns the appropriate Binding instance based on the HTTP method
@ -110,6 +112,8 @@ func Default(method, contentType string) Binding {
return TOML
case MIMEMultipartPOSTForm:
return FormMultipart
case MIMEBSON:
return BSON
default: // case MIMEPOSTForm:
return Form
}

View File

@ -21,6 +21,7 @@ import (
"github.com/gin-gonic/gin/testdata/protoexample"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson"
"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.MethodPut, MIMETOML))
assert.Equal(t, BSON, Default(http.MethodPost, MIMEBSON))
assert.Equal(t, BSON, Default(http.MethodPut, MIMEBSON))
}
func TestBindingJSONNilBody(t *testing.T) {
@ -733,6 +737,16 @@ func TestBindingProtoBufFail(t *testing.T) {
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:]))
}
func TestValidationFails(t *testing.T) {
var obj FooStruct
req := requestWithBody(http.MethodPost, "/", `{"bar": "foo"}`)

30
binding/bson.go Normal file
View 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)
}

View File

@ -34,7 +34,7 @@ func (protobufBinding) BindBody(body []byte, obj any) error {
if err := proto.Unmarshal(body, msg); err != nil {
return err
}
// Here it's same to return validate(obj), but util now we can't add
// Here it's same to return validate(obj), but until now we can't add
// `binding:""` to the struct which automatically generate by gen-proto
return nil
// return validate(obj)

View File

@ -37,6 +37,7 @@ const (
MIMEYAML = binding.MIMEYAML
MIMEYAML2 = binding.MIMEYAML2
MIMETOML = binding.MIMETOML
MIMEBSON = binding.MIMEBSON
)
// BodyBytesKey indicates a default body bytes key.
@ -1131,6 +1132,11 @@ func (c *Context) ProtoBuf(code int, obj any) {
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.
func (c *Context) String(code int, format string, values ...any) {
c.Render(code, render.String{Format: format, Data: values})
@ -1237,6 +1243,7 @@ type Negotiate struct {
YAMLData any
Data any
TOMLData any
BSONData any
}
// Negotiate calls different Render according to acceptable Accept format.
@ -1262,6 +1269,10 @@ func (c *Context) Negotiate(code int, config Negotiate) {
data := chooseData(config.TOMLData, config.Data)
c.TOML(code, data)
case binding.MIMEBSON:
data := chooseData(config.BSONData, config.Data)
c.BSON(code, data)
default:
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck
}

View File

@ -31,6 +31,7 @@ import (
testdata "github.com/gin-gonic/gin/testdata/protoexample"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson"
"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"))
}
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) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)

View File

@ -70,7 +70,7 @@
### Build with json replacement
Gin uses `encoding/json` as default json package but you can change it by build from other tags.
Gin uses `encoding/json` as the default JSON package but you can change it by building from other tags.
[jsoniter](https://github.com/json-iterator/go)
@ -84,7 +84,7 @@ go build -tags=jsoniter .
go build -tags=go_json .
```
[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.)
[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu supports avx instruction.)
```sh
$ go build -tags="sonic avx" .
@ -120,7 +120,7 @@ func main() {
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)
// By default it serves on :8080 unless a
// By default, it serves on :8080 unless a
// PORT environment variable was defined.
router.Run()
// router.Run(":3000") for a hard coded port
@ -172,7 +172,7 @@ func main() {
router := gin.Default()
// Query string parameters are parsed using the existing underlying request object.
// The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe
// The request responds to a URL matching: /welcome?firstname=Jane&lastname=Doe
router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest")
lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
@ -300,7 +300,7 @@ curl -X POST http://localhost:8080/upload \
#### Multiple files
See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple).
See the detailed [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple).
```go
func main() {
@ -704,7 +704,7 @@ $ curl -v -X POST \
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
```
Skip validate: when running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again.
Skip-validation: Running the example above using the `curl` command returns an error. This is because the example uses `binding:"required"` for `Password`. If instead, you use `binding:"-"` for `Password`, then it will not return an error when you run the example again.
### Custom Validators
@ -1187,7 +1187,7 @@ func main() {
})
r.GET("/moreJSON", func(c *gin.Context) {
// You also can use a struct
// You can also use a struct
var msg struct {
Name string `json:"user"`
Message string
@ -1490,7 +1490,7 @@ You may use custom delims
#### Custom Template Funcs
See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template).
See the detailed [example code](https://github.com/gin-gonic/examples/tree/master/template).
main.go
@ -1542,7 +1542,7 @@ Date: 2017/07/01
### Multitemplate
Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.
Gin allows only one html.Template by default. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.
### Redirects
@ -2091,7 +2091,7 @@ type formB struct {
func SomeHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// This c.ShouldBind consumes c.Request.Body and it cannot be reused.
// Calling c.ShouldBind consumes c.Request.Body and it cannot be reused.
if errA := c.ShouldBind(&objA); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// Always an error is occurred by this because c.Request.Body is EOF now.
@ -2324,7 +2324,7 @@ or network CIDRs from where clients which their request headers related to clien
IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
IPv6 CIDRs.
**Attention:** Gin trust all proxies by default if you don't specify a trusted
**Attention:** Gin trusts all proxies by default if you don't specify a trusted
proxy using the function above, **this is NOT safe**. At the same time, if you don't
use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,
then `Context.ClientIP()` will return the remote address directly to avoid some

View File

@ -154,7 +154,7 @@ func RunUnix(file string) (err error) {
// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests
// through the specified file descriptor.
// Note: the method will block the calling goroutine indefinitely unless on error happens.
// Note: the method will block the calling goroutine indefinitely unless an error happens.
func RunFd(fd int) (err error) {
return engine().RunFd(fd)
}

1
go.mod
View File

@ -35,6 +35,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.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.4.0 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.36.0 // indirect

2
go.sum
View File

@ -80,6 +80,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/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
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.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=

36
render/bson.go Normal file
View 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)
}

View File

@ -19,6 +19,7 @@ import (
testdata "github.com/gin-gonic/gin/testdata/protoexample"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson"
"google.golang.org/protobuf/proto"
)
@ -352,6 +353,28 @@ func TestRenderProtoBufFail(t *testing.T) {
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, string(bsonData), w.Body.String())
assert.Equal(t, "application/bson", w.Header().Get("Content-Type"))
}
func TestRenderXML(t *testing.T) {
w := httptest.NewRecorder()
data := xmlmap{