mirror of
https://github.com/labstack/echo.git
synced 2024-12-28 21:08:39 +02:00
Add a BindUnmarshaler interface (#764)
This is inspired by interfaces like json.Unmarshaler, and can be used to unmarshal custom data types from form/query data.
This commit is contained in:
parent
874d336c7d
commit
a66875f17d
31
binder.go
31
binder.go
@ -19,6 +19,12 @@ type (
|
||||
|
||||
// DefaultBinder is the default implementation of the Binder interface.
|
||||
DefaultBinder struct{}
|
||||
|
||||
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
|
||||
BindUnmarshaler interface {
|
||||
// UnmarshalParam decodes and assigns a value from an HTML form.
|
||||
UnmarshalParam(src string) error
|
||||
}
|
||||
)
|
||||
|
||||
// Bind implements the `Binder#Bind` function.
|
||||
@ -151,12 +157,35 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
|
||||
return setFloatField(val, 64, structField)
|
||||
case reflect.String:
|
||||
structField.SetString(val)
|
||||
case reflect.Ptr:
|
||||
return unmarshalFieldPtr(val, structField)
|
||||
default:
|
||||
return errors.New("unknown type")
|
||||
return unmarshalField(val, structField)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalField(value string, field reflect.Value) error {
|
||||
ptr := reflect.New(field.Type())
|
||||
if ptr.CanInterface() {
|
||||
iface := ptr.Interface()
|
||||
if unmarshaler, ok := iface.(BindUnmarshaler); ok {
|
||||
err := unmarshaler.UnmarshalParam(value)
|
||||
field.Set(ptr.Elem())
|
||||
return err
|
||||
}
|
||||
}
|
||||
return errors.New("unknown type")
|
||||
}
|
||||
|
||||
func unmarshalFieldPtr(value string, field reflect.Value) error {
|
||||
if field.IsNil() {
|
||||
// Initialize the pointer to a nil value
|
||||
field.Set(reflect.New(field.Type().Elem()))
|
||||
}
|
||||
return unmarshalField(value, field.Elem())
|
||||
}
|
||||
|
||||
func setIntField(value string, bitSize int, field reflect.Value) error {
|
||||
if value == "" {
|
||||
value = "0"
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -31,9 +32,22 @@ type (
|
||||
S string
|
||||
cantSet string
|
||||
DoesntExist string
|
||||
T Timestamp
|
||||
Tptr *Timestamp
|
||||
}
|
||||
|
||||
// Timestamp is a simple datatype which implements the Scanner interface
|
||||
// used for testing.
|
||||
Timestamp time.Time
|
||||
)
|
||||
|
||||
// UnmarshalParam unmarshals a value from an HTML form into a Timestamp value
|
||||
func (t *Timestamp) UnmarshalParam(src string) error {
|
||||
ts, err := time.Parse(time.RFC3339, src)
|
||||
*t = Timestamp(ts)
|
||||
return err
|
||||
}
|
||||
|
||||
func (t binderTestStruct) GetCantSet() string {
|
||||
return t.cantSet
|
||||
}
|
||||
@ -54,6 +68,8 @@ var values = map[string][]string{
|
||||
"F64": {"64.5"},
|
||||
"S": {"test"},
|
||||
"cantSet": {"test"},
|
||||
"T": {"2016-12-06T19:09:05+01:00"},
|
||||
"Tptr": {"2016-12-06T19:09:05+01:00"},
|
||||
}
|
||||
|
||||
func TestBinderJSON(t *testing.T) {
|
||||
@ -92,6 +108,35 @@ func TestBinderQueryParams(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinderScanner(t *testing.T) {
|
||||
e := New()
|
||||
req, _ := http.NewRequest(GET, "/?ts=2016-12-06T19:09:05Z", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
var result struct {
|
||||
T Timestamp `query:"ts"`
|
||||
}
|
||||
err := c.Bind(&result)
|
||||
if assert.NoError(t, err) {
|
||||
// assert.Equal(t, Timestamp(reflect.TypeOf(&Timestamp{}), time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC)), result.T)
|
||||
assert.Equal(t, Timestamp(time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC)), result.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinderScannerPtr(t *testing.T) {
|
||||
e := New()
|
||||
req, _ := http.NewRequest(GET, "/?ts=2016-12-06T19:09:05Z", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
var result struct {
|
||||
Tptr *Timestamp `query:"ts"`
|
||||
}
|
||||
err := c.Bind(&result)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, Timestamp(time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC)), *result.Tptr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinderMultipartForm(t *testing.T) {
|
||||
body := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(body)
|
||||
@ -174,6 +219,10 @@ func TestBinderSetFields(t *testing.T) {
|
||||
if assert.NoError(t, setBoolField("", val.FieldByName("B"))) {
|
||||
assert.Equal(t, false, ts.B)
|
||||
}
|
||||
|
||||
if assert.NoError(t, unmarshalField("2016-12-06T19:09:05Z", val.FieldByName("T"))) {
|
||||
assert.Equal(t, Timestamp(time.Date(2016, 12, 6, 19, 9, 5, 0, time.UTC)), ts.T)
|
||||
}
|
||||
}
|
||||
|
||||
func assertBinderTestStruct(t *testing.T, ts *binderTestStruct) {
|
||||
|
@ -172,13 +172,14 @@ ls avatar.png
|
||||
|
||||
### Handling Request
|
||||
|
||||
- Bind `JSON` or `XML` or `form` payload into Go struct based on `Content-Type` request header.
|
||||
- Bind `JSON`, `XML`, `form` or `query` payload into Go struct based on `Content-Type` request header.
|
||||
- Render response as `JSON` or `XML` with status code.
|
||||
- Use the [BindUnmarshaler](https://godoc.org/github.com/labstack/echo#ParamUnmarshaler) interface to bind custom data types for `form` or `query` payloads. The standard [json.Unmarshaler](https://golang.org/pkg/encoding/json/#Unmarshaler) and [xml.Unmarshaler](https://golang.org/pkg/encoding/xml/#Unmarshaler) can of course be used for JSON and XML payloads, respectively.
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
Name string `json:"name" xml:"name" form:"name"`
|
||||
Email string `json:"email" xml:"email" form:"email"`
|
||||
Name string `json:"name" xml:"name" form:"name" query:"name"`
|
||||
Email string `json:"email" xml:"email" form:"email" query:"name"`
|
||||
}
|
||||
|
||||
e.POST("/users", func(c echo.Context) error {
|
||||
|
Loading…
Reference in New Issue
Block a user