Merge pull request #84 from bluesuncorp/v5

Merge latest changes from v5
pull/115/head
Dean Karn 10 years ago
commit 7ce6ffcb4d
  1. 135
      baked_in.go
  2. 163
      benchmarks_test.go
  3. 81
      doc.go
  4. 95
      examples_test.go
  5. 38
      regexes.go
  6. 282
      validator.go
  7. 1197
      validator_test.go

@ -50,6 +50,141 @@ var BakedInValidators = map[string]Func{
"excludes": excludes, "excludes": excludes,
"excludesall": excludesAll, "excludesall": excludesAll,
"excludesrune": excludesRune, "excludesrune": excludesRune,
"isbn": isISBN,
"isbn10": isISBN10,
"isbn13": isISBN13,
"uuid": isUUID,
"uuid3": isUUID3,
"uuid4": isUUID4,
"uuid5": isUUID5,
"ascii": isASCII,
"printascii": isPrintableASCII,
"multibyte": hasMultiByteCharacter,
"datauri": isDataURI,
"latitude": isLatitude,
"longitude": isLongitude,
"ssn": isSSN,
}
func isSSN(top interface{}, current interface{}, field interface{}, param string) bool {
if len(field.(string)) != 11 {
return false
}
return matchesRegex(sSNRegex, field)
}
func isLongitude(top interface{}, current interface{}, field interface{}, param string) bool {
return matchesRegex(longitudeRegex, field)
}
func isLatitude(top interface{}, current interface{}, field interface{}, param string) bool {
return matchesRegex(latitudeRegex, field)
}
func isDataURI(top interface{}, current interface{}, field interface{}, param string) bool {
uri := strings.SplitN(field.(string), ",", 2)
if len(uri) != 2 {
return false
}
if !matchesRegex(dataURIRegex, uri[0]) {
return false
}
return isBase64(top, current, uri[1], param)
}
func hasMultiByteCharacter(top interface{}, current interface{}, field interface{}, param string) bool {
if len(field.(string)) == 0 {
return true
}
return matchesRegex(multibyteRegex, field)
}
func isPrintableASCII(top interface{}, current interface{}, field interface{}, param string) bool {
return matchesRegex(printableASCIIRegex, field)
}
func isASCII(top interface{}, current interface{}, field interface{}, param string) bool {
return matchesRegex(aSCIIRegex, field)
}
func isUUID5(top interface{}, current interface{}, field interface{}, param string) bool {
return matchesRegex(uUID5Regex, field)
}
func isUUID4(top interface{}, current interface{}, field interface{}, param string) bool {
return matchesRegex(uUID4Regex, field)
}
func isUUID3(top interface{}, current interface{}, field interface{}, param string) bool {
return matchesRegex(uUID3Regex, field)
}
func isUUID(top interface{}, current interface{}, field interface{}, param string) bool {
return matchesRegex(uUIDRegex, field)
}
func isISBN(top interface{}, current interface{}, field interface{}, param string) bool {
return isISBN10(top, current, field, param) || isISBN13(top, current, field, param)
}
func isISBN13(top interface{}, current interface{}, field interface{}, param string) bool {
s := strings.Replace(strings.Replace(field.(string), "-", "", 4), " ", "", 4)
if !matchesRegex(iSBN13Regex, s) {
return false
}
var checksum int32
var i int32
factor := []int32{1, 3}
for i = 0; i < 12; i++ {
checksum += factor[i%2] * int32(s[i]-'0')
}
if (int32(s[12]-'0'))-((10-(checksum%10))%10) == 0 {
return true
}
return false
}
func isISBN10(top interface{}, current interface{}, field interface{}, param string) bool {
s := strings.Replace(strings.Replace(field.(string), "-", "", 3), " ", "", 3)
if !matchesRegex(iSBN10Regex, s) {
return false
}
var checksum int32
var i int32
for i = 0; i < 9; i++ {
checksum += (i + 1) * int32(s[i]-'0')
}
if s[9] == 'X' {
checksum += 10 * 10
} else {
checksum += 10 * int32(s[9]-'0')
}
if checksum%11 == 0 {
return true
}
return false
} }
func excludesRune(top interface{}, current interface{}, field interface{}, param string) bool { func excludesRune(top interface{}, current interface{}, field interface{}, param string) bool {

@ -0,0 +1,163 @@
package validator
import "testing"
func BenchmarkValidateField(b *testing.B) {
for n := 0; n < b.N; n++ {
validate.Field("1", "len=1")
}
}
func BenchmarkValidateStructSimple(b *testing.B) {
type Foo struct {
StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"`
}
validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
for n := 0; n < b.N; n++ {
validate.Struct(validFoo)
validate.Struct(invalidFoo)
}
}
// func BenchmarkTemplateParallelSimple(b *testing.B) {
// type Foo struct {
// StringValue string `validate:"min=5,max=10"`
// IntValue int `validate:"min=5,max=10"`
// }
// validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
// invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// validate.Struct(validFoo)
// validate.Struct(invalidFoo)
// }
// })
// }
func BenchmarkValidateStructLarge(b *testing.B) {
tFail := &TestString{
Required: "",
Len: "",
Min: "",
Max: "12345678901",
MinMax: "",
Lt: "0123456789",
Lte: "01234567890",
Gt: "1",
Gte: "1",
OmitEmpty: "12345678901",
Sub: &SubTest{
Test: "",
},
Anonymous: struct {
A string `validate:"required"`
}{
A: "",
},
Iface: &Impl{
F: "12",
},
}
tSuccess := &TestString{
Required: "Required",
Len: "length==10",
Min: "min=1",
Max: "1234567890",
MinMax: "12345",
Lt: "012345678",
Lte: "0123456789",
Gt: "01234567890",
Gte: "0123456789",
OmitEmpty: "",
Sub: &SubTest{
Test: "1",
},
SubIgnore: &SubTest{
Test: "",
},
Anonymous: struct {
A string `validate:"required"`
}{
A: "1",
},
Iface: &Impl{
F: "123",
},
}
for n := 0; n < b.N; n++ {
validate.Struct(tSuccess)
validate.Struct(tFail)
}
}
// func BenchmarkTemplateParallelLarge(b *testing.B) {
// tFail := &TestString{
// Required: "",
// Len: "",
// Min: "",
// Max: "12345678901",
// MinMax: "",
// Lt: "0123456789",
// Lte: "01234567890",
// Gt: "1",
// Gte: "1",
// OmitEmpty: "12345678901",
// Sub: &SubTest{
// Test: "",
// },
// Anonymous: struct {
// A string `validate:"required"`
// }{
// A: "",
// },
// Iface: &Impl{
// F: "12",
// },
// }
// tSuccess := &TestString{
// Required: "Required",
// Len: "length==10",
// Min: "min=1",
// Max: "1234567890",
// MinMax: "12345",
// Lt: "012345678",
// Lte: "0123456789",
// Gt: "01234567890",
// Gte: "0123456789",
// OmitEmpty: "",
// Sub: &SubTest{
// Test: "1",
// },
// SubIgnore: &SubTest{
// Test: "",
// },
// Anonymous: struct {
// A string `validate:"required"`
// }{
// A: "1",
// },
// Iface: &Impl{
// F: "123",
// },
// }
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// validate.Struct(tSuccess)
// validate.Struct(tFail)
// }
// })
// }

@ -168,11 +168,30 @@ Here is a list of the current built in validators:
verify it has been assigned. verify it has been assigned.
omitempty omitempty
Allows conitional validation, for example if a field is not set with Allows conditional validation, for example if a field is not set with
a value (Determined by the required validator) then other validation a value (Determined by the required validator) then other validation
such as min or max won't run, but if a value is set validation will run. such as min or max won't run, but if a value is set validation will run.
(Usage: omitempty) (Usage: omitempty)
dive
This tells the validator to dive into a slice, array or map and validate that
level of the slice, array or map with the validation tags that follow.
Multidimensional nesting is also supported, each level you with to dive will
require another dive tag. (Usage: dive)
Example: [][]string with validation tag "gt=0,dive,len=1,dive,required"
gt=0 will be applied to []
len=1 will be applied to []string
required will be applied to string
Example2: [][]string with validation tag "gt=0,dive,dive,required"
gt=0 will be applied to []
[]string will be spared validation
required will be applied to string
NOTE: in Example2 if the required validation failed, but all others passed
the hierarchy of FieldError's in the middle with have their IsPlaceHolder field
set to true. If a FieldError has IsSliceOrMap=true or IsMap=true then the
FieldError is a Slice or Map field and if IsPlaceHolder=true then contains errors
within its SliceOrArrayErrs or MapErrs fields.
required required
This validates that the value is not the data types default value. This validates that the value is not the data types default value.
For numbers ensures value is not zero. For strings ensures value is For numbers ensures value is not zero. For strings ensures value is
@ -362,6 +381,66 @@ Here is a list of the current built in validators:
This validates that a string value does not contain the supplied rune value. This validates that a string value does not contain the supplied rune value.
(Usage: excludesrune=@) (Usage: excludesrune=@)
isbn
This validates that a string value contains a valid isbn10 or isbn13 value.
(Usage: isbn)
isbn10
This validates that a string value contains a valid isbn10 value.
(Usage: isbn10)
isbn13
This validates that a string value contains a valid isbn13 value.
(Usage: isbn13)
uuid
This validates that a string value contains a valid UUID.
(Usage: uuid)
uuid3
This validates that a string value contains a valid version 3 UUID.
(Usage: uuid3)
uuid4
This validates that a string value contains a valid version 4 UUID.
(Usage: uuid4)
uuid5
This validates that a string value contains a valid version 5 UUID.
(Usage: uuid5)
ascii
This validates that a string value contains only ASCII characters.
NOTE: if the string is blank, this validates as true.
(Usage: ascii)
asciiprint
This validates that a string value contains only printable ASCII characters.
NOTE: if the string is blank, this validates as true.
(Usage: asciiprint)
multibyte
This validates that a string value contains one or more multibyte characters.
NOTE: if the string is blank, this validates as true.
(Usage: multibyte)
datauri
This validates that a string value contains a valid DataURI.
NOTE: this will also validate that the data portion is valid base64
(Usage: datauri)
latitude
This validates that a string value contains a valid latitude.
(Usage: latitude)
longitude
This validates that a string value contains a valid longitude.
(Usage: longitude)
ssn
This validates that a string value contains a valid U.S. Social Security Number.
(Usage: ssn)
Validator notes: Validator notes:
regex regex

@ -0,0 +1,95 @@
package validator_test
import (
"fmt"
"../validator"
)
func ExampleValidate_new() {
validator.New("validate", validator.BakedInValidators)
}
func ExampleValidate_addFunction() {
// This should be stored somewhere globally
var validate *validator.Validate
validate = validator.New("validate", validator.BakedInValidators)
fn := func(top interface{}, current interface{}, field interface{}, param string) bool {
return field.(string) == "hello"
}
validate.AddFunction("valueishello", fn)
message := "hello"
err := validate.Field(message, "valueishello")
fmt.Println(err)
//Output:
//<nil>
}
func ExampleValidate_field() {
// This should be stored somewhere globally
var validate *validator.Validate
validate = validator.New("validate", validator.BakedInValidators)
i := 0
err := validate.Field(i, "gt=1,lte=10")
fmt.Println(err.Field)
fmt.Println(err.Tag)
fmt.Println(err.Kind) // NOTE: Kind and Type can be different i.e. time Kind=struct and Type=time.Time
fmt.Println(err.Type)
fmt.Println(err.Param)
fmt.Println(err.Value)
//Output:
//
//gt
//int
//int
//1
//0
}
func ExampleValidate_struct() {
// This should be stored somewhere globally
var validate *validator.Validate
validate = validator.New("validate", validator.BakedInValidators)
type ContactInformation struct {
Phone string `validate:"required"`
Street string `validate:"required"`
City string `validate:"required"`
}
type User struct {
Name string `validate:"required,excludesall=!@#$%^&*()_+-=:;?/0x2C"` // 0x2C = comma (,)
Age int8 `validate:"required,gt=0,lt=150"`
Email string `validate:"email"`
ContactInformation []*ContactInformation
}
contactInfo := &ContactInformation{
Street: "26 Here Blvd.",
City: "Paradeso",
}
user := &User{
Name: "Joey Bloggs",
Age: 31,
Email: "joeybloggs@gmail.com",
ContactInformation: []*ContactInformation{contactInfo},
}
structError := validate.Struct(user)
for _, fieldError := range structError.Errors {
fmt.Println(fieldError.Field) // Phone
fmt.Println(fieldError.Tag) // required
//... and so forth
//Output:
//Phone
//required
}
}

@ -9,12 +9,25 @@ const (
numberRegexString = "^[0-9]+$" numberRegexString = "^[0-9]+$"
hexadecimalRegexString = "^[0-9a-fA-F]+$" hexadecimalRegexString = "^[0-9a-fA-F]+$"
hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
rgbRegexString = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$" rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$"
rgbaRegexString = "^rgba\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*((0.[1-9]*)|[01])\\s*\\)$" rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
hslRegexString = "^hsl\\(\\s*(0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*\\)$" hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$"
hslaRegexString = "^hsla\\(\\s*(0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*,\\s*((0.[1-9]*)|[01])\\s*\\)$" hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
emailRegexString = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
base64RegexString = "(?:^(?:[A-Za-z0-9+\\/]{4}\\n?)*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=)$)" base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$"
iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$"
uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
aSCIIRegexString = "^[\x00-\x7F]*$"
printableASCIIRegexString = "^[\x20-\x7E]*$"
multibyteRegexString = "[^\x00-\x7F]"
dataURIRegexString = "^data:.+\\/(.+);base64$"
latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
) )
var ( var (
@ -30,6 +43,19 @@ var (
hslaRegex = regexp.MustCompile(hslaRegexString) hslaRegex = regexp.MustCompile(hslaRegexString)
emailRegex = regexp.MustCompile(emailRegexString) emailRegex = regexp.MustCompile(emailRegexString)
base64Regex = regexp.MustCompile(base64RegexString) base64Regex = regexp.MustCompile(base64RegexString)
iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
uUID3Regex = regexp.MustCompile(uUID3RegexString)
uUID4Regex = regexp.MustCompile(uUID4RegexString)
uUID5Regex = regexp.MustCompile(uUID5RegexString)
uUIDRegex = regexp.MustCompile(uUIDRegexString)
aSCIIRegex = regexp.MustCompile(aSCIIRegexString)
printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString)
multibyteRegex = regexp.MustCompile(multibyteRegexString)
dataURIRegex = regexp.MustCompile(dataURIRegexString)
latitudeRegex = regexp.MustCompile(latitudeRegexString)
longitudeRegex = regexp.MustCompile(longitudeRegexString)
sSNRegex = regexp.MustCompile(sSNRegexString)
) )
func matchesRegex(regex *regexp.Regexp, field interface{}) bool { func matchesRegex(regex *regexp.Regexp, field interface{}) bool {

@ -27,8 +27,15 @@ const (
tagKeySeparator = "=" tagKeySeparator = "="
structOnlyTag = "structonly" structOnlyTag = "structonly"
omitempty = "omitempty" omitempty = "omitempty"
required = "required"
fieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag" fieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag"
sliceErrMsg = "Field validation for \"%s\" failed at index \"%d\" with error(s): %s"
mapErrMsg = "Field validation for \"%s\" failed on key \"%v\" with error(s): %s"
structErrMsg = "Struct:%s\n" structErrMsg = "Struct:%s\n"
diveTag = "dive"
// diveSplit = "," + diveTag
arrayIndexFieldName = "%s[%d]"
mapIndexFieldName = "%s[%v]"
) )
var structPool *pool var structPool *pool
@ -64,8 +71,6 @@ func (p *pool) Borrow() *StructErrors {
// Return returns a StructErrors to the pool. // Return returns a StructErrors to the pool.
func (p *pool) Return(c *StructErrors) { func (p *pool) Return(c *StructErrors) {
// c.Struct = ""
select { select {
case p.pool <- c: case p.pool <- c:
default: default:
@ -86,6 +91,15 @@ type cachedField struct {
kind reflect.Kind kind reflect.Kind
typ reflect.Type typ reflect.Type
isTime bool isTime bool
isSliceOrArray bool
isMap bool
isTimeSubtype bool
sliceSubtype reflect.Type
mapSubtype reflect.Type
sliceSubKind reflect.Kind
mapSubKind reflect.Kind
dive bool
diveTag string
} }
type cachedStruct struct { type cachedStruct struct {
@ -144,11 +158,38 @@ type FieldError struct {
Type reflect.Type Type reflect.Type
Param string Param string
Value interface{} Value interface{}
IsPlaceholderErr bool
IsSliceOrArray bool
IsMap bool
SliceOrArrayErrs map[int]error // counld be FieldError, StructErrors
MapErrs map[interface{}]error // counld be FieldError, StructErrors
} }
// This is intended for use in development + debugging and not intended to be a production error message. // This is intended for use in development + debugging and not intended to be a production error message.
// it also allows FieldError to be used as an Error interface // it also allows FieldError to be used as an Error interface
func (e *FieldError) Error() string { func (e *FieldError) Error() string {
if e.IsPlaceholderErr {
buff := bytes.NewBufferString("")
if e.IsSliceOrArray {
for j, err := range e.SliceOrArrayErrs {
buff.WriteString("\n")
buff.WriteString(fmt.Sprintf(sliceErrMsg, e.Field, j, "\n"+err.Error()))
}
} else if e.IsMap {
for key, err := range e.MapErrs {
buff.WriteString(fmt.Sprintf(mapErrMsg, e.Field, key, "\n"+err.Error()))
}
}
return strings.TrimSpace(buff.String())
}
return fmt.Sprintf(fieldErrMsg, e.Field, e.Tag) return fmt.Sprintf(fieldErrMsg, e.Field, e.Tag)
} }
@ -178,7 +219,7 @@ func (e *StructErrors) Error() string {
buff.WriteString(err.Error()) buff.WriteString(err.Error())
} }
return buff.String() return strings.TrimSpace(buff.String())
} }
// Flatten flattens the StructErrors hierarchical structure into a flat namespace style field name // Flatten flattens the StructErrors hierarchical structure into a flat namespace style field name
@ -246,7 +287,7 @@ func (v *Validate) SetTag(tagName string) {
v.tagName = tagName v.tagName = tagName
} }
// SetStructPoolMax sets the struct pools max size. this may be usefull for fine grained // SetMaxStructPoolSize sets the struct pools max size. this may be usefull for fine grained
// performance tuning towards your application, however, the default should be fine for // performance tuning towards your application, however, the default should be fine for
// nearly all cases. only increase if you have a deeply nested struct structure. // nearly all cases. only increase if you have a deeply nested struct structure.
// NOTE: this method is not thread-safe // NOTE: this method is not thread-safe
@ -340,7 +381,7 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
typeField = structType.Field(i) typeField = structType.Field(i)
cField = &cachedField{index: i, tag: typeField.Tag.Get(v.tagName)} cField = &cachedField{index: i, tag: typeField.Tag.Get(v.tagName), isTime: (valueField.Type() == reflect.TypeOf(time.Time{}) || valueField.Type() == reflect.TypeOf(&time.Time{}))}
if cField.tag == noValidationTag { if cField.tag == noValidationTag {
cs.children-- cs.children--
@ -373,9 +414,7 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
continue continue
} }
if cField.isTime || valueField.Type() == reflect.TypeOf(time.Time{}) { if cField.isTime {
cField.isTime = true
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError validationErrors.Errors[fieldError.Field] = fieldError
@ -390,6 +429,24 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
continue continue
} }
if valueField.Kind() == reflect.Ptr && valueField.IsNil() {
if strings.Contains(cField.tag, omitempty) {
continue
}
if strings.Contains(cField.tag, required) {
validationErrors.Errors[cField.name] = &FieldError{
Field: cField.name,
Tag: required,
Value: valueField.Interface(),
}
continue
}
}
if structErrors := v.structRecursive(top, valueField.Interface(), valueField.Interface()); structErrors != nil { if structErrors := v.structRecursive(top, valueField.Interface(), valueField.Interface()); structErrors != nil {
validationErrors.StructErrors[cField.name] = structErrors validationErrors.StructErrors[cField.name] = structErrors
// free up memory map no longer needed // free up memory map no longer needed
@ -397,13 +454,36 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
} }
} }
default: case reflect.Slice, reflect.Array:
cField.isSliceOrArray = true
cField.sliceSubtype = cField.typ.Elem()
cField.isTimeSubtype = (cField.sliceSubtype == reflect.TypeOf(time.Time{}) || cField.sliceSubtype == reflect.TypeOf(&time.Time{}))
cField.sliceSubKind = cField.sliceSubtype.Kind()
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference // free up memory reference
fieldError = nil fieldError = nil
} }
case reflect.Map:
cField.isMap = true
cField.mapSubtype = cField.typ.Elem()
cField.isTimeSubtype = (cField.mapSubtype == reflect.TypeOf(time.Time{}) || cField.mapSubtype == reflect.TypeOf(&time.Time{}))
cField.mapSubKind = cField.mapSubtype.Kind()
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference
fieldError = nil
}
default:
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference
fieldError = nil
}
} }
if !isCached { if !isCached {
@ -421,13 +501,11 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
// Field allows validation of a single field, still using tag style validation to check multiple errors // Field allows validation of a single field, still using tag style validation to check multiple errors
func (v *Validate) Field(f interface{}, tag string) *FieldError { func (v *Validate) Field(f interface{}, tag string) *FieldError {
return v.FieldWithValue(nil, f, tag) return v.FieldWithValue(nil, f, tag)
} }
// FieldWithValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors // FieldWithValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors
func (v *Validate) FieldWithValue(val interface{}, f interface{}, tag string) *FieldError { func (v *Validate) FieldWithValue(val interface{}, f interface{}, tag string) *FieldError {
return v.fieldWithNameAndValue(nil, val, f, tag, "", true, nil) return v.fieldWithNameAndValue(nil, val, f, tag, "", true, nil)
} }
@ -435,6 +513,7 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
var cField *cachedField var cField *cachedField
var isCached bool var isCached bool
var valueField reflect.Value
// This is a double check if coming from validate.Struct but need to be here in case function is called directly // This is a double check if coming from validate.Struct but need to be here in case function is called directly
if tag == noValidationTag { if tag == noValidationTag {
@ -445,8 +524,9 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
return nil return nil
} }
valueField = reflect.ValueOf(f)
if cacheField == nil { if cacheField == nil {
valueField := reflect.ValueOf(f)
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
valueField = valueField.Elem() valueField = valueField.Elem()
@ -454,6 +534,21 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
} }
cField = &cachedField{name: name, kind: valueField.Kind(), tag: tag, typ: valueField.Type()} cField = &cachedField{name: name, kind: valueField.Kind(), tag: tag, typ: valueField.Type()}
switch cField.kind {
case reflect.Slice, reflect.Array:
isSingleField = false // cached tags mean nothing because it will be split up while diving
cField.isSliceOrArray = true
cField.sliceSubtype = cField.typ.Elem()
cField.isTimeSubtype = (cField.sliceSubtype == reflect.TypeOf(time.Time{}) || cField.sliceSubtype == reflect.TypeOf(&time.Time{}))
cField.sliceSubKind = cField.sliceSubtype.Kind()
case reflect.Map:
isSingleField = false // cached tags mean nothing because it will be split up while diving
cField.isMap = true
cField.mapSubtype = cField.typ.Elem()
cField.isTimeSubtype = (cField.mapSubtype == reflect.TypeOf(time.Time{}) || cField.mapSubtype == reflect.TypeOf(&time.Time{}))
cField.mapSubKind = cField.mapSubtype.Kind()
}
} else { } else {
cField = cacheField cField = cacheField
} }
@ -463,7 +558,7 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
case reflect.Struct, reflect.Interface, reflect.Invalid: case reflect.Struct, reflect.Interface, reflect.Invalid:
if cField.typ != reflect.TypeOf(time.Time{}) { if cField.typ != reflect.TypeOf(time.Time{}) {
panic("Invalid field passed to ValidateFieldWithTag") panic("Invalid field passed to fieldWithNameAndValue")
} }
} }
@ -477,6 +572,13 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
for _, t := range strings.Split(tag, tagSeparator) { for _, t := range strings.Split(tag, tagSeparator) {
if t == diveTag {
cField.dive = true
cField.diveTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",")
break
}
orVals := strings.Split(t, orSeparator) orVals := strings.Split(t, orSeparator)
cTag := &cachedTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))} cTag := &cachedTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))}
cField.tags = append(cField.tags, cTag) cField.tags = append(cField.tags, cTag)
@ -543,9 +645,163 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
} }
} }
if cField.dive {
if cField.isSliceOrArray {
if errs := v.traverseSliceOrArray(val, current, valueField, cField); errs != nil && len(errs) > 0 {
return &FieldError{
Field: cField.name,
Kind: cField.kind,
Type: cField.typ,
Value: f,
IsPlaceholderErr: true,
IsSliceOrArray: true,
SliceOrArrayErrs: errs,
}
}
} else if cField.isMap {
if errs := v.traverseMap(val, current, valueField, cField); errs != nil && len(errs) > 0 {
return &FieldError{
Field: cField.name,
Kind: cField.kind,
Type: cField.typ,
Value: f,
IsPlaceholderErr: true,
IsMap: true,
MapErrs: errs,
}
}
} else {
// throw error, if not a slice or map then should not have gotten here
panic("dive error! can't dive on a non slice or map")
}
}
return nil return nil
} }
func (v *Validate) traverseMap(val interface{}, current interface{}, valueField reflect.Value, cField *cachedField) map[interface{}]error {
errs := map[interface{}]error{}
for _, key := range valueField.MapKeys() {
idxField := valueField.MapIndex(key)
if cField.mapSubKind == reflect.Ptr && !idxField.IsNil() {
idxField = idxField.Elem()
cField.mapSubKind = idxField.Kind()
}
switch cField.mapSubKind {
case reflect.Struct, reflect.Interface:
if cField.isTimeSubtype {
if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(mapIndexFieldName, cField.name, key.Interface()), false, nil); fieldError != nil {
errs[key.Interface()] = fieldError
}
continue
}
if idxField.Kind() == reflect.Ptr && idxField.IsNil() {
if strings.Contains(cField.tag, omitempty) {
continue
}
if strings.Contains(cField.tag, required) {
errs[key.Interface()] = &FieldError{
Field: fmt.Sprintf(mapIndexFieldName, cField.name, key.Interface()),
Tag: required,
Value: idxField.Interface(),
Kind: reflect.Ptr,
Type: cField.mapSubtype,
}
}
continue
}
if structErrors := v.structRecursive(val, current, idxField.Interface()); structErrors != nil {
errs[key.Interface()] = structErrors
}
default:
if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(mapIndexFieldName, cField.name, key.Interface()), false, nil); fieldError != nil {
errs[key.Interface()] = fieldError
}
}
}
return errs
}
func (v *Validate) traverseSliceOrArray(val interface{}, current interface{}, valueField reflect.Value, cField *cachedField) map[int]error {
errs := map[int]error{}
for i := 0; i < valueField.Len(); i++ {
idxField := valueField.Index(i)
if cField.sliceSubKind == reflect.Ptr && !idxField.IsNil() {
idxField = idxField.Elem()
cField.sliceSubKind = idxField.Kind()
}
switch cField.sliceSubKind {
case reflect.Struct, reflect.Interface:
if cField.isTimeSubtype {
if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(arrayIndexFieldName, cField.name, i), false, nil); fieldError != nil {
errs[i] = fieldError
}
continue
}
if idxField.Kind() == reflect.Ptr && idxField.IsNil() {
if strings.Contains(cField.tag, omitempty) {
continue
}
if strings.Contains(cField.tag, required) {
errs[i] = &FieldError{
Field: fmt.Sprintf(arrayIndexFieldName, cField.name, i),
Tag: required,
Value: idxField.Interface(),
Kind: reflect.Ptr,
Type: cField.sliceSubtype,
}
}
continue
}
if structErrors := v.structRecursive(val, current, idxField.Interface()); structErrors != nil {
errs[i] = structErrors
}
default:
if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(arrayIndexFieldName, cField.name, i), false, nil); fieldError != nil {
errs[i] = fieldError
}
}
}
return errs
}
func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, key string, param string, name string) (*FieldError, error) { func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, key string, param string, name string) (*FieldError, error) {
// OK to continue because we checked it's existance before getting into this loop // OK to continue because we checked it's existance before getting into this loop

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save