New bindings for JSON, XML and form parsing and validation

This commit is contained in:
Manu Mtz-Almeida 2014-07-04 23:28:50 +02:00
parent 1aa3216303
commit 9634a38704
4 changed files with 107 additions and 79 deletions

View File

@ -3,32 +3,89 @@ package binding
import ( import (
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"io" "errors"
"net/http"
"reflect"
"strings"
) )
type ( type (
Binding interface { Binding interface {
Bind(io.Reader, interface{}) error Bind(*http.Request, interface{}) error
} }
// JSON binding // JSON binding
jsonBinding struct{} jsonBinding struct{}
// JSON binding // XML binding
xmlBinding struct{} xmlBinding struct{}
// // form binding
formBinding struct{}
) )
var ( var (
JSON = jsonBinding{} JSON = jsonBinding{}
XML = xmlBinding{} XML = xmlBinding{}
Form = formBinding{} // todo
) )
func (_ jsonBinding) Bind(r io.Reader, obj interface{}) error { func (_ jsonBinding) Bind(req *http.Request, obj interface{}) error {
decoder := json.NewDecoder(r) decoder := json.NewDecoder(req.Body)
return decoder.Decode(&obj) if err := decoder.Decode(obj); err == nil {
return Validate(obj)
} else {
return err
}
} }
func (_ xmlBinding) Bind(r io.Reader, obj interface{}) error { func (_ xmlBinding) Bind(req *http.Request, obj interface{}) error {
decoder := xml.NewDecoder(r) decoder := xml.NewDecoder(req.Body)
return decoder.Decode(&obj) if err := decoder.Decode(obj); err == nil {
return Validate(obj)
} else {
return err
}
}
func (_ formBinding) Bind(req *http.Request, obj interface{}) error {
return nil
}
func Validate(obj interface{}) error {
typ := reflect.TypeOf(obj)
val := reflect.ValueOf(obj)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fieldValue := val.Field(i).Interface()
zero := reflect.Zero(field.Type).Interface()
// Validate nested and embedded structs (if pointer, only do so if not nil)
if field.Type.Kind() == reflect.Struct ||
(field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue)) {
if err := Validate(fieldValue); err != nil {
return err
}
}
if strings.Index(field.Tag.Get("binding"), "required") > -1 {
if reflect.DeepEqual(zero, fieldValue) {
name := field.Name
if j := field.Tag.Get("json"); j != "" {
name = j
} else if f := field.Tag.Get("form"); f != "" {
name = f
}
return errors.New("Required " + name)
}
}
}
return nil
} }

17
deprecated.go Normal file
View File

@ -0,0 +1,17 @@
package gin
import (
"github.com/gin-gonic/gin/binding"
)
// DEPRECATED, use Bind() instead.
// Like ParseBody() but this method also writes a 400 error if the json is not valid.
func (c *Context) EnsureBody(item interface{}) bool {
return c.Bind(item)
}
// DEPRECATED use bindings directly
// Parses the body content as a JSON input. It decodes the json payload into the struct specified as a pointer.
func (c *Context) ParseBody(item interface{}) error {
return binding.JSON.Bind(c.Req, item)
}

47
gin.go
View File

@ -17,6 +17,11 @@ import (
const ( const (
AbortIndex = math.MaxInt8 / 2 AbortIndex = math.MaxInt8 / 2
MIMEJSON = "application/json"
MIMEHTML = "text/html"
MIMEXML = "application/xml"
MIMEXML2 = "text/xml"
MIMEPlain = "text/plain"
) )
type ( type (
@ -371,16 +376,13 @@ func (c *Context) Get(key string) interface{} {
/******** ENCOGING MANAGEMENT********/ /******** ENCOGING MANAGEMENT********/
/************************************/ /************************************/
// Like ParseBody() but this method also writes a 400 error if the json is not valid. func filterFlags(content string) string {
// DEPRECATED, use Bind() instead. for i, a := range content {
func (c *Context) EnsureBody(item interface{}) bool { if a == ' ' || a == ';' {
return c.Bind(item) return content[:i]
} }
}
// DEPRECATED use bindings directly return content
// Parses the body content as a JSON input. It decodes the json payload into the struct specified as a pointer.
func (c *Context) ParseBody(item interface{}) error {
return binding.JSON.Bind(c.Req.Body, item)
} }
// This function checks the Content-Type to select a binding engine automatically, // This function checks the Content-Type to select a binding engine automatically,
@ -390,27 +392,24 @@ func (c *Context) ParseBody(item interface{}) error {
// else --> returns an error // else --> returns an error
// if Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer.Like ParseBody() but this method also writes a 400 error if the json is not valid. // if Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer.Like ParseBody() but this method also writes a 400 error if the json is not valid.
func (c *Context) Bind(obj interface{}) bool { func (c *Context) Bind(obj interface{}) bool {
var err error var b binding.Binding
switch c.Req.Header.Get("Content-Type") { ctype := filterFlags(c.Req.Header.Get("Content-Type"))
case "application/json": switch {
err = binding.JSON.Bind(c.Req.Body, obj) case c.Req.Method == "GET":
case "application/xml": b = binding.Form
err = binding.XML.Bind(c.Req.Body, obj) case ctype == MIMEJSON:
b = binding.JSON
case ctype == MIMEXML || ctype == MIMEXML2:
b = binding.XML
default: default:
err = errors.New("unknown content-type: " + c.Req.Header.Get("Content-Type")) c.Fail(400, errors.New("unknown content-type: "+ctype))
}
if err == nil {
err = Validate(c, obj)
}
if err != nil {
c.Fail(400, err)
return false return false
} }
return true return c.BindWith(obj, b)
} }
func (c *Context) BindWith(obj interface{}, b binding.Binding) bool { func (c *Context) BindWith(obj interface{}, b binding.Binding) bool {
if err := b.Bind(c.Req.Body, obj); err != nil { if err := b.Bind(c.Req, obj); err != nil {
c.Fail(400, err) c.Fail(400, err)
return false return false
} }

View File

@ -1,45 +0,0 @@
package gin
import (
"errors"
"reflect"
"strings"
)
func Validate(c *Context, obj interface{}) error {
var err error
typ := reflect.TypeOf(obj)
val := reflect.ValueOf(obj)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fieldValue := val.Field(i).Interface()
zero := reflect.Zero(field.Type).Interface()
// Validate nested and embedded structs (if pointer, only do so if not nil)
if field.Type.Kind() == reflect.Struct ||
(field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue)) {
err = Validate(c, fieldValue)
}
if strings.Index(field.Tag.Get("binding"), "required") > -1 {
if reflect.DeepEqual(zero, fieldValue) {
name := field.Name
if j := field.Tag.Get("json"); j != "" {
name = j
} else if f := field.Tag.Get("form"); f != "" {
name = f
}
err = errors.New("Required " + name)
c.Error(err, "json validation")
}
}
}
return err
}