New bindings for JSON, XML and form parsing and validation
This commit is contained in:
		| @ -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(r io.Reader, obj interface{}) error { | ||||
| 	decoder := xml.NewDecoder(r) | ||||
| 	return decoder.Decode(&obj) | ||||
| 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 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) | ||||
| } | ||||
							
								
								
									
										49
									
								
								gin.go
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								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 | ||||
| 	} | ||||
|  | ||||
| @ -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