Merge 66b55b8f04117325a9c42f0e3bbb2b98c04f5173 into 0808f8a824cfb9aef6ea4fd664af238544b66fc1

This commit is contained in:
Michael 2014-09-13 20:28:59 +00:00
commit 4145617ff0
4 changed files with 714 additions and 55 deletions

View File

@ -3,3 +3,6 @@ language: go
go: go:
- 1.3 - 1.3
- tip - tip
script:
- go test github.com/gin-gonic/gin/binding

View File

@ -7,11 +7,9 @@ package binding
import ( import (
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"errors"
"net/http" "net/http"
"reflect" "reflect"
"strconv" "strconv"
"strings"
) )
type ( type (
@ -154,56 +152,3 @@ func ensureNotPointer(obj interface{}) {
panic("Pointers are not accepted as binding models") panic("Pointers are not accepted as binding models")
} }
} }
func Validate(obj interface{}) error {
typ := reflect.TypeOf(obj)
val := reflect.ValueOf(obj)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
}
switch typ.Kind() {
case reflect.Struct:
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
// Allow ignored fields in the struct
if field.Tag.Get("form") == "-" {
continue
}
fieldValue := val.Field(i).Interface()
zero := reflect.Zero(field.Type).Interface()
if strings.Index(field.Tag.Get("binding"), "required") > -1 {
fieldType := field.Type.Kind()
if fieldType == reflect.Struct {
err := Validate(fieldValue)
if err != nil {
return err
}
} else if reflect.DeepEqual(zero, fieldValue) {
return errors.New("Required " + field.Name)
} else if fieldType == reflect.Slice && field.Type.Elem().Kind() == reflect.Struct {
err := Validate(fieldValue)
if err != nil {
return err
}
}
}
}
case reflect.Slice:
for i := 0; i < val.Len(); i++ {
fieldValue := val.Index(i).Interface()
err := Validate(fieldValue)
if err != nil {
return err
}
}
default:
return nil
}
return nil
}

281
binding/validate.go Normal file
View File

@ -0,0 +1,281 @@
package binding
import (
"errors"
"reflect"
"regexp"
"strconv"
"strings"
)
func Validate(obj interface{}) error {
typ := reflect.TypeOf(obj)
value := reflect.ValueOf(obj)
// Check to ensure we are getting a valid
// pointer for manipulation.
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
value = value.Elem()
}
// Kill process if obj did not pass in a scruct.
// This happens when a pointer passed in.
if value.Kind() != reflect.Struct {
return nil
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fieldValue := value.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 field.Tag.Get("validate") != "" || field.Tag.Get("binding") != "" {
// Break validate field into array
array := strings.Split(field.Tag.Get("validate"), "|")
// Legacy Support for binding.
if array[0] == "" {
array = strings.Split(field.Tag.Get("binding"), "|")
}
// Do the hard work of checking all assertions
for setting := range array {
match := array[setting]
//Check that value was passed in and is not required.
if match != "required" && null(fieldValue) == true {
return nil
}
switch {
case "required" == match:
if err := required(field, fieldValue, zero); err != nil {
return err
}
case "email" == match:
if err := regex(`regex:^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$`, fieldValue); err != nil {
return err
}
case strings.HasPrefix(match, "min:"):
if err := min(match, fieldValue); err != nil {
return err
}
case strings.HasPrefix(match, "max:"):
if err := max(match, fieldValue); err != nil {
return err
}
case strings.HasPrefix(match, "in:"):
if err := in(match, fieldValue); err != nil {
return err
}
case strings.HasPrefix(match, "regex:"):
if err := regex(match, fieldValue); err != nil {
return err
}
case strings.HasPrefix(match, "length:"):
if err := length(match, fieldValue); err != nil {
return err
}
case strings.HasPrefix(match, "length_between:"):
if err := length_between(match, fieldValue); err != nil {
return err
}
default:
panic("The field " + match + " is not a valid validation check.")
}
}
}
}
return nil
}
// Ensure that the value being passed in is not of type nil.
func null(value interface{}) bool {
if reflect.ValueOf(value).IsNil() {
return true
}
return false
}
// Check that the following function features
// the required field. May need to check for
// more special cases like since passing in null
// is the same as 0 for int type checking.
func required(field reflect.StructField, value, zero interface{}) error {
if reflect.DeepEqual(zero, value) {
if _, ok := value.(int); !ok {
return errors.New("The required field " + field.Name + " was not submitted.")
}
}
return nil
}
// Check that the passed in field is a valid email
// Need to improve error logging for this method
// Currently only supports strings, ints
func in(field string, value interface{}) error {
if data, ok := value.(*string); ok {
if len(*data) == 0 {
return nil
}
valid := strings.Split(field[3:], ",")
for option := range valid {
if valid[option] == *data {
return nil
}
}
} else {
return errors.New("The value passed in for IN could not be converted to a string.")
}
return errors.New("In did not match any of the expected values.")
}
func min(field string, value interface{}) error {
if data, ok := value.(*int); ok {
min := field[strings.Index(field, ":")+1:]
if minNum, ok := strconv.ParseInt(min, 0, 64); ok == nil {
if int64(*data) >= minNum {
return nil
} else {
return errors.New("The data you passed in was smaller then the allowed minimum.")
}
}
}
return errors.New("The value passed in for MIN could not be converted to an int.")
}
func max(field string, value interface{}) error {
if data, ok := value.(*int); ok {
max := field[strings.Index(field, ":")+1:]
if maxNum, ok := strconv.ParseInt(max, 0, 64); ok == nil {
if int64(*data) <= maxNum {
return nil
} else {
return errors.New("The data you passed in was larger than the maximum.")
}
}
}
return errors.New("The value passed in for MAX could not be converted to an int.")
}
// Regex handles the general regex call and also handles
// the regex email.
func regex(field string, value interface{}) error {
reg := field[strings.Index(field, ":")+1:]
if data, ok := value.(*string); ok {
if len(*data) == 0 {
return nil
} else if err := match_regex(reg, []byte(*data)); err != nil {
return err
}
} else if data, ok := value.(*int); ok {
if err := match_regex(reg, []byte(strconv.Itoa(*data))); err != nil {
return err
}
} else {
return errors.New("The value passed in for REGEX could not be converted to a string or int.")
}
return nil
}
// Helper function for regex.
func match_regex(reg string, data []byte) error {
if match, err := regexp.Match(reg, []byte(data)); err == nil && match {
return nil
} else {
return errors.New("Your regex did not match or was not valid.")
}
}
// Check passed in json length string is exact value passed in.
// Also checks if passed in values is between two different ones.
func length(field string, value interface{}) error {
length := field[strings.Index(field, ":")+1:]
if data, ok := value.(*string); ok {
if intdata, intok := strconv.Atoi(length); intok == nil {
if len(*data) == intdata {
return nil
} else {
return errors.New("The data passed in was not equal to the expected length.")
}
} else {
return errors.New("The value passed in for LENGTH could not be converted to an int.")
}
} else {
return errors.New("The value passed in for LENGTH could not be converted to a string.")
}
}
// Check if the strings length is between high,low.
func length_between(field string, value interface{}) error {
length := field[strings.Index(field, ":")+1:]
vals := strings.Split(length, ",")
if len(vals) == 2 {
if data, ok := value.(*string); ok {
if lowerbound, lowok := strconv.Atoi(vals[0]); lowok == nil {
if upperbound, upok := strconv.Atoi(vals[1]); upok == nil {
if lowerbound <= len(*data) && upperbound >= len(*data) {
return nil
} else {
return errors.New("The value passed in for LENGTH BETWEEN was not in bounds.")
}
} else {
return errors.New("The value passed in for LENGTH BETWEEN could not be converted to an int.")
}
} else {
return errors.New("The value passed in for LENGTH BETWEEN could not be converted to an int.")
}
} else {
return errors.New("The value passed in for LENGTH BETWEEN could not be converted to a string.")
}
} else {
return errors.New("LENGTH BETWEEN requires exactly two paramaters.")
}
}

430
binding/validate_test.go Normal file
View File

@ -0,0 +1,430 @@
package binding
import (
"io"
"io/ioutil"
"net/http"
"strings"
"testing"
)
// Make string into io.ReadCloser
// this is just for convinience
func jsonFactory(s string) io.ReadCloser {
return ioutil.NopCloser(strings.NewReader(s))
}
// Ensure all required fields are matching
func TestRequired(t *testing.T) {
// // Test if STRING required is valid
var testString struct {
Test *string `json:"something" validate:"required" `
}
req, _ := http.NewRequest("POST", "/", jsonFactory(`{"something": "hello"}`))
if err := JSON.Bind(req, &testString); err != nil {
t.Error(err)
}
var testString2 struct {
Test *string `json:"something" validate:"required" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{}`))
if err := JSON.Bind(req, &testString2); err == nil {
t.Error("Required string, empty JSON object should return error but did not.")
}
// Test if INT require is valid
var testInt struct {
Test *int `json:"something" validate:"required" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"something": 2}`))
if err := JSON.Bind(req, &testInt); err != nil {
t.Error(err)
}
// Test if BOOL required is valid
var testBool struct {
Test *bool `json:"something" validate:"required" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"something": true}`))
if err := JSON.Bind(req, &testBool); err != nil {
t.Error(err)
}
var testBool2 struct {
Test *string `json:"something" validate:"required" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{}`))
if err := JSON.Bind(req, &testBool2); err == nil {
t.Error("Required bool, empty JSON object should return error but did not.")
}
// Test if ARRAY required is valid
var testArray struct {
Test *[]string `json:"something" validate:"required" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"something": ["test", "data"]}`))
if err := JSON.Bind(req, &testArray); err != nil {
t.Error(err)
}
// Test is OBJECT required is valid
type testObjectTP struct {
Name string `json:"name" validate:"required" `
}
var testObject struct {
Test testObjectTP `json:"something" validate:"required" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"something": {"name": "test"}}`))
if err := JSON.Bind(req, &testObject); err != nil {
t.Error(err)
}
}
func TestEmail(t *testing.T) {
var testValEmail struct {
Test *string `json:"email" validate:"email" `
}
req, _ := http.NewRequest("POST", "/", jsonFactory(`{"email": "michaeljs@gmail.com"}`))
if err := JSON.Bind(req, &testValEmail); err != nil {
t.Error(err)
}
var testValEmail2 struct {
Test *string `json:"email" validate:"email" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"email": "michaeljs@gail.edu"}`))
if err := JSON.Bind(req, &testValEmail2); err != nil {
t.Error(err)
}
var testValEmail3 struct {
Test *string `json:"email" validate:"email" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"email": "michaeljs.edu"}`))
if err := JSON.Bind(req, &testValEmail3); err == nil {
t.Error("Email test failed, michaeljs.edu passed as a valid email.")
}
// This should not return an error since email is not required.
var testValEmail4 struct {
Test *string `json:"email" validate:"email" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"jeff": "really"}`))
if err := JSON.Bind(req, &testValEmail4); err != nil {
t.Error(err)
}
}
// Ensure In is matching properly
// Supporting string and int currently
func TestIn(t *testing.T) {
var testValIn struct {
Test *string `json:"special" validate:"in:admin,user,other" `
}
req, _ := http.NewRequest("POST", "/", jsonFactory(`{"special": "admin"}`))
if err := JSON.Bind(req, &testValIn); err != nil {
t.Error(err)
}
var testValIn2 struct {
Test *string `json:"special" validate:"in:1,3,2" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"special": "3"}`))
if err := JSON.Bind(req, &testValIn2); err != nil {
t.Error(err)
}
var testValIn3 struct {
Test *int `json:"special" validate:"in:1,3,2" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"special": 6}`))
if err := JSON.Bind(req, &testValIn3); err == nil {
t.Error("6 is not in validate in call, err should not have been nil.")
}
var testValIn4 struct {
Test2 *string `json:"what" validate:in:this,that`
Test *string `json:"special" validate:"in:1,3,2" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"special": "3","what": "this"}`))
if err := JSON.Bind(req, &testValIn4); err != nil {
t.Error(err)
}
var testValIn5 struct {
Test2 *string `json:"what" validate:in:this,that`
Test *string `json:"special" validate:"in:1,3,2" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"special": "3"}`))
if err := JSON.Bind(req, &testValIn5); err != nil {
t.Error(err)
}
var testValIn6 struct {
Test2 *string `json:"what" validate:"in:this,that"`
Test3 *string `json:"what1" validate:"in:this,then"`
Test4 *string `json:"what2" validate:"in:this,that"`
Test5 *string `json:"what3" validate:"in:this,that"`
Test *string `json:"special" validate:"in:1,3,2"`
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"sa": 34, "what":"this", "what1":"then", "what2":"this"}`))
if err := JSON.Bind(req, &testValIn6); err != nil {
t.Error(err)
}
}
// Check if the entered JSON is a data matching the one in a string.
func TestMin(t *testing.T) {
var testValMin struct {
Test *int `json:"digit" validate:"min:23" `
}
req, _ := http.NewRequest("POST", "/", jsonFactory(`{"digit": 24}`))
if err := JSON.Bind(req, &testValMin); err != nil {
t.Error(err)
}
var testValMin2 struct {
Test *int `json:"digit" validate:"min:20" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": 19}`))
if err := JSON.Bind(req, &testValMin2); err == nil {
t.Error("Min was 20 digit of 19 should not have validated properly.")
}
var testValMin3 struct {
Test *int `json:"digit" validate:"min:20" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"jeff":"greg"}`))
if err := JSON.Bind(req, &testValMin3); err != nil {
t.Error("Nothing was entered but min was not required. No error should be thrown.")
}
}
func TestMax(t *testing.T) {
var testValMin struct {
Test *int `json:"digit" validate:"max:23" `
}
req, _ := http.NewRequest("POST", "/", jsonFactory(`{"digit": 23}`))
if err := JSON.Bind(req, &testValMin); err != nil {
t.Error(err)
}
var testValMin2 struct {
Test *int `json:"digit" validate:"max:20" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": 21}`))
if err := JSON.Bind(req, &testValMin2); err == nil {
t.Error("Max was 20 digit of 21 should not have validated properly.")
}
var testValMin3 struct {
Test *int `json:"digit" validate:"max:20" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"jeff":"greg"}`))
if err := JSON.Bind(req, &testValMin3); err != nil {
t.Error("Nothing was entered but max was not required. No error should be thrown.")
}
}
func TestRegex(t *testing.T) {
var testValDigit struct {
Test *int `json:"digit" validate:"regex:\\d+" `
}
req, _ := http.NewRequest("POST", "/", jsonFactory(`{"digit": 23}`))
if err := JSON.Bind(req, &testValDigit); err != nil {
t.Error(err)
}
var testValDigit2 struct {
Test *int `json:"digit" validate:"regex:\\d+" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": 2dsa3}`))
if err := JSON.Bind(req, &testValDigit2); err == nil {
t.Error("\\d+ regex should not match the string 2dsa3.")
}
}
func TestMultiple(t *testing.T) {
var testValMulti struct {
Test *int `json:"digit" validate:"regex:\\d+|required|max:23" `
}
req, _ := http.NewRequest("POST", "/", jsonFactory(`{"digit": 23}`))
if err := JSON.Bind(req, &testValMulti); err != nil {
t.Error(err)
}
var testValMulti2 struct {
Test *string `json:"digit" validate:"email|required|regex:\\d+" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": "m@g.com"}`))
if err := JSON.Bind(req, &testValMulti2); err == nil {
t.Error("Should have returned error but did not.")
}
}
func TestPointers(t *testing.T) {
var testValMulti struct {
Test *string `json:"digit" validate:"in:3,4,5" `
}
req, _ := http.NewRequest("POST", "/", jsonFactory(`{"invalid": "23"}`))
if err := JSON.Bind(req, &testValMulti); err != nil {
t.Error(err)
}
var testValMulti2 struct {
Test *string `json:"digit" validate:"in:3,4,5" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": "23"}`))
if err := JSON.Bind(req, &testValMulti2); err == nil {
t.Error("Value was passed in but did not match in:3,4,5 error should have been returned.")
}
var testValMulti3 struct {
Test *string `json:"digit" validate:"in:3,4,5" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": "4"}`))
if err := JSON.Bind(req, &testValMulti3); err != nil {
t.Error(err)
}
}
func TestLength(t *testing.T) {
var testValLength struct {
Test *string `json:"username" validate:"length:5" `
}
req, _ := http.NewRequest("POST", "/", jsonFactory(`{"username": "aaaaa"}`))
if err := JSON.Bind(req, &testValLength); err != nil {
t.Error(err)
}
var testValLength2 struct {
Test *string `json:"username" validate:"length:4" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"username": "aaa"}`))
if err := JSON.Bind(req, &testValLength2); err == nil {
t.Error("Value was passed in but did not match length of 4 error should have been returned.")
}
var testValLength3 struct {
Test *string `json:"username" validate:"length:23" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": "4"}`))
if err := JSON.Bind(req, &testValLength3); err != nil {
t.Error(err)
}
}
func TestLengthBetween(t *testing.T) {
var testValLength struct {
Test *string `json:"username" validate:"length_between:5,10" `
}
req, _ := http.NewRequest("POST", "/", jsonFactory(`{"username": "aaaaaa"}`))
if err := JSON.Bind(req, &testValLength); err != nil {
t.Error(err)
}
var testValLength2 struct {
Test *string `json:"username" validate:"length_between:4,5" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"username": "aaa"}`))
if err := JSON.Bind(req, &testValLength2); err == nil {
t.Error("Value was passed in but was not inbetween 4,5 should have returned error.")
}
var testValLength3 struct {
Test *string `json:"username" validate:"length_between:2,3" `
}
req, _ = http.NewRequest("POST", "/", jsonFactory(`{"digit": "4"}`))
if err := JSON.Bind(req, &testValLength3); err != nil {
t.Error(err)
}
}