New bindings for JSON, XML and form parsing and validation
This commit is contained in:
		| @ -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
									
								
							
							
						
						
									
										17
									
								
								deprecated.go
									
									
									
									
									
										Normal 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
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								gin.go
									
									
									
									
									
								
							| @ -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 | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -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 |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user