Merge 26c932b14546985ea5387c57afcd5fbe1d0d87e0 into 857db39f82fb82456af2906ccea972ae1d65ff57

This commit is contained in:
Jakub Slocki 2024-01-29 11:34:42 +01:00 committed by GitHub
commit 3f01464295
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 170 additions and 3 deletions

View File

@ -197,6 +197,18 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
} }
} }
type jsonUnmarshaler interface {
UnmarshalJSON([]byte) error
}
type textUnmarshaler interface {
UnmarshalText([]byte) error
}
type binaryUnmarshaler interface {
UnmarshalBinary([]byte) error
}
func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
switch value.Kind() { switch value.Kind() {
case reflect.Int: case reflect.Int:
@ -236,6 +248,15 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
case time.Time: case time.Time:
return setTimeField(val, field, value) return setTimeField(val, field, value)
} }
if unmarshaler, ok := value.Addr().Interface().(jsonUnmarshaler); ok {
return unmarshaler.UnmarshalJSON(bytesconv.StringToBytes(val))
}
if unmarshaler, ok := value.Addr().Interface().(textUnmarshaler); ok {
return unmarshaler.UnmarshalText(bytesconv.StringToBytes(val))
}
if unmarshaler, ok := value.Addr().Interface().(binaryUnmarshaler); ok {
return unmarshaler.UnmarshalBinary(bytesconv.StringToBytes(val))
}
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
case reflect.Map: case reflect.Map:
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())

View File

@ -5,7 +5,9 @@
package binding package binding
import ( import (
"errors"
"reflect" "reflect"
"strings"
"testing" "testing"
"time" "time"
@ -321,3 +323,93 @@ func TestMappingIgnoredCircularRef(t *testing.T) {
err := mappingByPtr(&s, formSource{}, "form") err := mappingByPtr(&s, formSource{}, "form")
assert.NoError(t, err) assert.NoError(t, err)
} }
// this structure has special json unmarshaller, in order to parse email (as an example) as specific structure
type withJsonUnmarshaller struct {
Name string
Host string
}
func (o *withJsonUnmarshaller) UnmarshalJSON(data []byte) error {
elems := strings.Split(string(data), "@")
if len(elems) != 2 {
return errors.New("cannot parse %q as email")
}
o.Name = elems[0]
o.Host = elems[1]
return nil
}
func TestMappingStructFieldJSONUnmarshaller(t *testing.T) {
var s struct {
Email withJsonUnmarshaller
}
err := mappingByPtr(&s, formSource{"Email": {`test@example.org`}}, "form")
assert.NoError(t, err)
assert.Equal(t, "test", s.Email.Name)
assert.Equal(t, "example.org", s.Email.Host)
err = mappingByPtr(&s, formSource{"Email": {`not an email`}}, "form")
assert.Error(t, err)
}
// this structure has special text unmarshaller, in order to parse email (as an example) as specific structure
type withTextUnmarshaller struct {
Name string
Host string
}
func (o *withTextUnmarshaller) UnmarshalText(data []byte) error {
elems := strings.Split(string(data), "@")
if len(elems) != 2 {
return errors.New("cannot parse %q as email")
}
o.Name = elems[0]
o.Host = elems[1]
return nil
}
func TestMappingStructFieldTextUnmarshaller(t *testing.T) {
var s struct {
Email withTextUnmarshaller
}
err := mappingByPtr(&s, formSource{"Email": {`test@example.org`}}, "form")
assert.NoError(t, err)
assert.Equal(t, "test", s.Email.Name)
assert.Equal(t, "example.org", s.Email.Host)
err = mappingByPtr(&s, formSource{"Email": {`not an email`}}, "form")
assert.Error(t, err)
}
// this structure has special binary unmarshaller, in order to parse email (as an example) as specific structure
type withBinaryUnmarshaller struct {
Name string
Host string
}
func (o *withBinaryUnmarshaller) UnmarshalBinary(data []byte) error {
elems := strings.Split(string(data), "@")
if len(elems) != 2 {
return errors.New("cannot parse %q as email")
}
o.Name = elems[0]
o.Host = elems[1]
return nil
}
func TestMappingStructFieldBinaryUnmarshaller(t *testing.T) {
var s struct {
Email withBinaryUnmarshaller
}
err := mappingByPtr(&s, formSource{"Email": {`test@example.org`}}, "form")
assert.NoError(t, err)
assert.Equal(t, "test", s.Email.Name)
assert.Equal(t, "example.org", s.Email.Host)
err = mappingByPtr(&s, formSource{"Email": {`not an email`}}, "form")
assert.Error(t, err)
}

View File

@ -57,6 +57,7 @@
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
- [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag) - [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag)
- [Bind Query with custom unmarshalers](#bind-query-with-custom-unmarshalers)
- [http2 server push](#http2-server-push) - [http2 server push](#http2-server-push)
- [Define format for the log of routes](#define-format-for-the-log-of-routes) - [Define format for the log of routes](#define-format-for-the-log-of-routes)
- [Set and get a cookie](#set-and-get-a-cookie) - [Set and get a cookie](#set-and-get-a-cookie)
@ -1155,7 +1156,7 @@ func main() {
router.StaticFS("/more_static", http.Dir("my_file_system")) router.StaticFS("/more_static", http.Dir("my_file_system"))
router.StaticFile("/favicon.ico", "./resources/favicon.ico") router.StaticFile("/favicon.ico", "./resources/favicon.ico")
router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system")) router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system"))
// Listen and serve on 0.0.0.0:8080 // Listen and serve on 0.0.0.0:8080
router.Run(":8080") router.Run(":8080")
} }
@ -2002,6 +2003,59 @@ func ListHandler(s *Service) func(ctx *gin.Context) {
} }
``` ```
### Bind Query with custom unmarshalers
Any structure that has custom `UnmarshalJSON` or `UnmarshalText` or `UnmarshalBinary` can be used to parse input as necessary
```go
package main
import (
"fmt"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// Booking contains data binded using custom unmarshaler.
type Payload struct {
Email EmailDetails `form:"email"`
}
// this structure has special json unmarshaller, in order to parse email (as an example) as specific structure
type EmailDetails struct {
Name string
Host string
}
func (o *EmailDetails) UnmarshalJSON(data []byte) error {
elems := strings.Split(string(data), "@")
if len(elems) != 2 {
return fmt.Errorf("cannot parse %q as email", string(data))
}
o.Name = elems[0]
o.Host = elems[1]
return nil
}
func main() {
route := gin.Default()
route.GET("/email", getEmail)
route.Run(":8085")
}
func getEmail(c *gin.Context) {
var p Payload
if err := c.ShouldBindQuery(&p); err == nil {
c.JSON(http.StatusOK, gin.H{"message": "Email information is correct"})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
}
```
```console
$ curl "localhost:8085/email?email=test@example.org"
{"message":"Email information is correct"}
$ curl "localhost:8085/email?email=test-something-else"
{"error":"cannot parse \"test-something-else\" as email"}
```
### http2 server push ### http2 server push
http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information. http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information.
@ -2134,7 +2188,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 IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
IPv6 CIDRs. IPv6 CIDRs.
**Attention:** Gin trust all proxies by default if you don't specify a trusted **Attention:** Gin trust 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 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)`, 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 then `Context.ClientIP()` will return the remote address directly to avoid some
@ -2163,7 +2217,7 @@ func main() {
``` ```
**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform` **Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform`
to skip TrustedProxies check, it has a higher priority than TrustedProxies. to skip TrustedProxies check, it has a higher priority than TrustedProxies.
Look at the example below: Look at the example below:
```go ```go