Browse Source

New bindings for JSON, XML and form parsing and validation

Manu Mtz-Almeida 11 years ago
parent
commit
9634a38704
4 changed files with 107 additions and 79 deletions
  1. 66 9
      binding/binding.go
  2. 17 0
      deprecated.go
  3. 24 25
      gin.go
  4. 0 45
      validation.go

+ 66 - 9
binding/binding.go

@@ -3,32 +3,89 @@ package binding
 import (
 	"encoding/json"
 	"encoding/xml"
-	"io"
+	"errors"
+	"net/http"
+	"reflect"
+	"strings"
 )
 
 type (
 	Binding interface {
-		Bind(io.Reader, interface{}) error
+		Bind(*http.Request, interface{}) error
 	}
 
 	// JSON binding
 	jsonBinding struct{}
 
-	// JSON binding
+	// XML binding
 	xmlBinding struct{}
+
+	// // form binding
+	formBinding struct{}
 )
 
 var (
 	JSON = jsonBinding{}
 	XML  = xmlBinding{}
+	Form = formBinding{} // todo
 )
 
-func (_ jsonBinding) Bind(r io.Reader, obj interface{}) error {
-	decoder := json.NewDecoder(r)
-	return decoder.Decode(&obj)
+func (_ jsonBinding) Bind(req *http.Request, obj interface{}) error {
+	decoder := json.NewDecoder(req.Body)
+	if err := decoder.Decode(obj); err == nil {
+		return Validate(obj)
+	} else {
+		return err
+	}
+}
+
+func (_ xmlBinding) Bind(req *http.Request, obj interface{}) error {
+	decoder := xml.NewDecoder(req.Body)
+	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 (_ xmlBinding) Bind(r io.Reader, obj interface{}) error {
-	decoder := xml.NewDecoder(r)
-	return decoder.Decode(&obj)
+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 - 0
deprecated.go

@@ -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)
+}

+ 24 - 25
gin.go

@@ -17,6 +17,11 @@ import (
 
 const (
 	AbortIndex = math.MaxInt8 / 2
+	MIMEJSON   = "application/json"
+	MIMEHTML   = "text/html"
+	MIMEXML    = "application/xml"
+	MIMEXML2   = "text/xml"
+	MIMEPlain  = "text/plain"
 )
 
 type (
@@ -371,16 +376,13 @@ func (c *Context) Get(key string) interface{} {
 /******** ENCOGING MANAGEMENT********/
 /************************************/
 
-// Like ParseBody() but this method also writes a 400 error if the json is not valid.
-// DEPRECATED, use Bind() instead.
-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.Body, item)
+func filterFlags(content string) string {
+	for i, a := range content {
+		if a == ' ' || a == ';' {
+			return content[:i]
+		}
+	}
+	return content
 }
 
 // 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
 // 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 {
-	var err error
-	switch c.Req.Header.Get("Content-Type") {
-	case "application/json":
-		err = binding.JSON.Bind(c.Req.Body, obj)
-	case "application/xml":
-		err = binding.XML.Bind(c.Req.Body, obj)
+	var b binding.Binding
+	ctype := filterFlags(c.Req.Header.Get("Content-Type"))
+	switch {
+	case c.Req.Method == "GET":
+		b = binding.Form
+	case ctype == MIMEJSON:
+		b = binding.JSON
+	case ctype == MIMEXML || ctype == MIMEXML2:
+		b = binding.XML
 	default:
-		err = errors.New("unknown content-type: " + c.Req.Header.Get("Content-Type"))
-	}
-	if err == nil {
-		err = Validate(c, obj)
-	}
-	if err != nil {
-		c.Fail(400, err)
+		c.Fail(400, errors.New("unknown content-type: "+ctype))
 		return false
 	}
-	return true
+	return c.BindWith(obj, b)
 }
 
 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)
 		return false
 	}

+ 0 - 45
validation.go

@@ -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
-}