mirror of
https://github.com/Fishwaldo/validator.git
synced 2025-03-15 19:51:38 +00:00
Merge branch 'v5-development' of https://gopkg.in/bluesuncorp/validator.v5 into v5-development
This commit is contained in:
commit
3bece518fa
11 changed files with 2952 additions and 184 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -23,4 +23,6 @@ _testmain.go
|
|||
*.test
|
||||
*.prof
|
||||
*.test
|
||||
*.out
|
||||
*.out
|
||||
cover.html
|
||||
README.html
|
11
.travis.yml
11
.travis.yml
|
@ -7,7 +7,14 @@ notificaitons:
|
|||
on_failure: always
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- tip
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
- go test -v -covermode=count -coverprofile=cover.out
|
||||
|
||||
after_success:
|
||||
- goveralls -coverprofile=cover.out -service=travis-ci -repotoken I6M8FiXZzErImgwMotJ7fwFlHOX8Hqdq1
|
122
README.md
122
README.md
|
@ -1,17 +1,25 @@
|
|||
Package validator
|
||||
================
|
||||
|
||||
[](https://gitter.im/bluesuncorp/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://travis-ci.org/bluesuncorp/validator)
|
||||
[](https://coveralls.io/r/bluesuncorp/validator?branch=v5)
|
||||
[](https://godoc.org/gopkg.in/bluesuncorp/validator.v5)
|
||||
|
||||
Package validator implements value validations for structs and individual fields based on tags.
|
||||
It is also capable of Cross Field and Cross Struct validations.
|
||||
|
||||
It has the following **unique** features:
|
||||
|
||||
- Cross Field and Cross Struct validations.
|
||||
- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
|
||||
- Handles type interface by determining it's underlying type prior to validation.
|
||||
|
||||
Installation
|
||||
============
|
||||
------------
|
||||
|
||||
Use go get.
|
||||
|
||||
go get -u gopkg.in/bluesuncorp/validator.v5
|
||||
go get gopkg.in/bluesuncorp/validator.v5
|
||||
|
||||
or to update
|
||||
|
||||
|
@ -22,12 +30,114 @@ Then import the validator package into your own code.
|
|||
import "gopkg.in/bluesuncorp/validator.v5"
|
||||
|
||||
Usage and documentation
|
||||
=======================
|
||||
------
|
||||
|
||||
Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v5 for detailed usage docs.
|
||||
|
||||
##### Example:
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/bluesuncorp/validator.v5"
|
||||
)
|
||||
|
||||
// User contains user information
|
||||
type User struct {
|
||||
FirstName string `validate:"required"`
|
||||
LastName string `validate:"required"`
|
||||
Age uint8 `validate:"gte=0,lte=130"`
|
||||
Email string `validate:"required,email"`
|
||||
FavouriteColor string `validate:"hexcolor|rgb|rgba"`
|
||||
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
|
||||
}
|
||||
|
||||
// Address houses a users address information
|
||||
type Address struct {
|
||||
Street string `validate:"required"`
|
||||
City string `validate:"required"`
|
||||
Planet string `validate:"required"`
|
||||
Phone string `validate:"required"`
|
||||
}
|
||||
|
||||
var validate *validator.Validate
|
||||
|
||||
func main() {
|
||||
|
||||
validate = validator.New("validate", validator.BakedInValidators)
|
||||
|
||||
address := &Address{
|
||||
Street: "Eavesdown Docks",
|
||||
Planet: "Persphone",
|
||||
Phone: "none",
|
||||
}
|
||||
|
||||
user := &User{
|
||||
FirstName: "Badger",
|
||||
LastName: "Smith",
|
||||
Age: 135,
|
||||
Email: "Badger.Smith@gmail.com",
|
||||
FavouriteColor: "#000",
|
||||
Addresses: []*Address{address},
|
||||
}
|
||||
|
||||
// returns nil or *StructErrors
|
||||
errs := validate.Struct(user)
|
||||
|
||||
if errs != nil {
|
||||
|
||||
// err will be of type *FieldError
|
||||
err := errs.Errors["Age"]
|
||||
fmt.Println(err.Error()) // output: Field validation for "Age" failed on the "lte" tag
|
||||
fmt.Println(err.Field) // output: Age
|
||||
fmt.Println(err.Tag) // output: lte
|
||||
fmt.Println(err.Kind) // output: uint8
|
||||
fmt.Println(err.Type) // output: uint8
|
||||
fmt.Println(err.Param) // output: 130
|
||||
fmt.Println(err.Value) // output: 135
|
||||
|
||||
// or if you prefer you can use the Flatten function
|
||||
// NOTE: I find this usefull when using a more hard static approach of checking field errors.
|
||||
// The above, is best for passing to some generic code to say parse the errors. i.e. I pass errs
|
||||
// to a routine which loops through the errors, creates and translates the error message into the
|
||||
// users locale and returns a map of map[string]string // field and error which I then use
|
||||
// within the HTML rendering.
|
||||
|
||||
flat := errs.Flatten()
|
||||
fmt.Println(flat) // output: map[Age:Field validation for "Age" failed on the "lte" tag Addresses[0].Address.City:Field validation for "City" failed on the "required" tag]
|
||||
err = flat["Addresses[0].Address.City"]
|
||||
fmt.Println(err.Field) // output: City
|
||||
fmt.Println(err.Tag) // output: required
|
||||
fmt.Println(err.Kind) // output: string
|
||||
fmt.Println(err.Type) // output: string
|
||||
fmt.Println(err.Param) // output:
|
||||
fmt.Println(err.Value) // output:
|
||||
|
||||
// from here you can create your own error messages in whatever language you wish
|
||||
return
|
||||
}
|
||||
|
||||
// save user to database
|
||||
}
|
||||
```
|
||||
|
||||
Benchmarks
|
||||
------
|
||||
###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3
|
||||
```go
|
||||
$ go test -cpu=4 -bench=. -benchmem=true
|
||||
PASS
|
||||
BenchmarkValidateField-4 3000000 429 ns/op 192 B/op 2 allocs/op
|
||||
BenchmarkValidateStructSimple-4 500000 2877 ns/op 657 B/op 10 allocs/op
|
||||
BenchmarkTemplateParallelSimple-4 500000 3097 ns/op 657 B/op 10 allocs/op
|
||||
BenchmarkValidateStructLarge-4 100000 15228 ns/op 4350 B/op 62 allocs/op
|
||||
BenchmarkTemplateParallelLarge-4 100000 14257 ns/op 4354 B/op 62 allocs/op
|
||||
```
|
||||
|
||||
How to Contribute
|
||||
=================
|
||||
------
|
||||
|
||||
There will always be a development branch for each version i.e. `v1-development`. In order to contribute,
|
||||
please make your pull requests against those branches.
|
||||
|
@ -40,5 +150,5 @@ I strongly encourage everyone whom creates a custom validation function to contr
|
|||
help make this package even better.
|
||||
|
||||
License
|
||||
=======
|
||||
------
|
||||
Distributed under MIT License, please see license file in code for more details.
|
||||
|
|
135
baked_in.go
135
baked_in.go
|
@ -50,6 +50,141 @@ var BakedInValidators = map[string]Func{
|
|||
"excludes": excludes,
|
||||
"excludesall": excludesAll,
|
||||
"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 {
|
||||
|
|
163
benchmarks_test.go
Normal file
163
benchmarks_test.go
Normal file
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
88
doc.go
88
doc.go
|
@ -10,7 +10,7 @@ Validate
|
|||
|
||||
A simple example usage:
|
||||
|
||||
type UserDetail {
|
||||
type UserDetail struct {
|
||||
Details string `validate:"-"`
|
||||
}
|
||||
|
||||
|
@ -143,6 +143,11 @@ NOTE: Baked In Cross field validation only compares fields on the same struct,
|
|||
if cross field + cross struct validation is needed your own custom validator
|
||||
should be implemented.
|
||||
|
||||
NOTE2: comma is the default separator of validation tags, if you wish to have a comma
|
||||
included within the parameter i.e. excludesall=, you will need to use the UTF-8 hex
|
||||
representation 0x2C, which is replaced in the code as a comma, so the above will
|
||||
become excludesall=0x2C
|
||||
|
||||
Here is a list of the current built in validators:
|
||||
|
||||
-
|
||||
|
@ -163,11 +168,30 @@ Here is a list of the current built in validators:
|
|||
verify it has been assigned.
|
||||
|
||||
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
|
||||
such as min or max won't run, but if a value is set validation will run.
|
||||
(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 wish 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
|
||||
This validates that the value is not the data types default value.
|
||||
For numbers ensures value is not zero. For strings ensures value is
|
||||
|
@ -357,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.
|
||||
(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:
|
||||
|
||||
regex
|
||||
|
|
85
examples/simple.go
Normal file
85
examples/simple.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/bluesuncorp/validator.v5"
|
||||
)
|
||||
|
||||
// User contains user information
|
||||
type User struct {
|
||||
FirstName string `validate:"required"`
|
||||
LastName string `validate:"required"`
|
||||
Age uint8 `validate:"gte=0,lte=130"`
|
||||
Email string `validate:"required,email"`
|
||||
FavouriteColor string `validate:"hexcolor|rgb|rgba"`
|
||||
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
|
||||
}
|
||||
|
||||
// Address houses a users address information
|
||||
type Address struct {
|
||||
Street string `validate:"required"`
|
||||
City string `validate:"required"`
|
||||
Planet string `validate:"required"`
|
||||
Phone string `validate:"required"`
|
||||
}
|
||||
|
||||
var validate *validator.Validate
|
||||
|
||||
func main() {
|
||||
|
||||
validate = validator.New("validate", validator.BakedInValidators)
|
||||
|
||||
address := &Address{
|
||||
Street: "Eavesdown Docks",
|
||||
Planet: "Persphone",
|
||||
Phone: "none",
|
||||
}
|
||||
|
||||
user := &User{
|
||||
FirstName: "Badger",
|
||||
LastName: "Smith",
|
||||
Age: 135,
|
||||
Email: "Badger.Smith@gmail.com",
|
||||
FavouriteColor: "#000",
|
||||
Addresses: []*Address{address},
|
||||
}
|
||||
|
||||
// returns nil or *StructErrors
|
||||
errs := validate.Struct(user)
|
||||
|
||||
if errs != nil {
|
||||
|
||||
// err will be of type *FieldError
|
||||
err := errs.Errors["Age"]
|
||||
fmt.Println(err.Error()) // output: Field validation for "Age" failed on the "lte" tag
|
||||
fmt.Println(err.Field) // output: Age
|
||||
fmt.Println(err.Tag) // output: lte
|
||||
fmt.Println(err.Kind) // output: uint8
|
||||
fmt.Println(err.Type) // output: uint8
|
||||
fmt.Println(err.Param) // output: 130
|
||||
fmt.Println(err.Value) // output: 135
|
||||
|
||||
// or if you prefer you can use the Flatten function
|
||||
// NOTE: I find this usefull when using a more hard static approach of checking field errors.
|
||||
// The above, is best for passing to some generic code to say parse the errors. i.e. I pass errs
|
||||
// to a routine which loops through the errors, creates and translates the error message into the
|
||||
// users locale and returns a map of map[string]string // field and error which I then use
|
||||
// within the HTML rendering.
|
||||
|
||||
flat := errs.Flatten()
|
||||
fmt.Println(flat) // output: map[Age:Field validation for "Age" failed on the "lte" tag Addresses[0].Address.City:Field validation for "City" failed on the "required" tag]
|
||||
err = flat["Addresses[0].Address.City"]
|
||||
fmt.Println(err.Field) // output: City
|
||||
fmt.Println(err.Tag) // output: required
|
||||
fmt.Println(err.Kind) // output: string
|
||||
fmt.Println(err.Type) // output: string
|
||||
fmt.Println(err.Param) // output:
|
||||
fmt.Println(err.Value) // output:
|
||||
|
||||
// from here you can create your own error messages in whatever language you wish
|
||||
return
|
||||
}
|
||||
|
||||
// save user to database
|
||||
}
|
95
examples_test.go
Normal file
95
examples_test.go
Normal file
|
@ -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
|
||||
}
|
||||
}
|
74
regexes.go
74
regexes.go
|
@ -3,33 +3,59 @@ package validator
|
|||
import "regexp"
|
||||
|
||||
const (
|
||||
alphaRegexString = "^[a-zA-Z]+$"
|
||||
alphaNumericRegexString = "^[a-zA-Z0-9]+$"
|
||||
numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$"
|
||||
numberRegexString = "^[0-9]+$"
|
||||
hexadecimalRegexString = "^[0-9a-fA-F]+$"
|
||||
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*\\)$"
|
||||
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*\\)$"
|
||||
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*\\)$"
|
||||
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}=)$)"
|
||||
alphaRegexString = "^[a-zA-Z]+$"
|
||||
alphaNumericRegexString = "^[a-zA-Z0-9]+$"
|
||||
numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$"
|
||||
numberRegexString = "^[0-9]+$"
|
||||
hexadecimalRegexString = "^[0-9a-fA-F]+$"
|
||||
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])|(?: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])|(?: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*\\)$"
|
||||
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}])))\\.?$"
|
||||
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 (
|
||||
alphaRegex = regexp.MustCompile(alphaRegexString)
|
||||
alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString)
|
||||
numericRegex = regexp.MustCompile(numericRegexString)
|
||||
numberRegex = regexp.MustCompile(numberRegexString)
|
||||
hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString)
|
||||
hexcolorRegex = regexp.MustCompile(hexcolorRegexString)
|
||||
rgbRegex = regexp.MustCompile(rgbRegexString)
|
||||
rgbaRegex = regexp.MustCompile(rgbaRegexString)
|
||||
hslRegex = regexp.MustCompile(hslRegexString)
|
||||
hslaRegex = regexp.MustCompile(hslaRegexString)
|
||||
emailRegex = regexp.MustCompile(emailRegexString)
|
||||
base64Regex = regexp.MustCompile(base64RegexString)
|
||||
alphaRegex = regexp.MustCompile(alphaRegexString)
|
||||
alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString)
|
||||
numericRegex = regexp.MustCompile(numericRegexString)
|
||||
numberRegex = regexp.MustCompile(numberRegexString)
|
||||
hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString)
|
||||
hexcolorRegex = regexp.MustCompile(hexcolorRegexString)
|
||||
rgbRegex = regexp.MustCompile(rgbRegexString)
|
||||
rgbaRegex = regexp.MustCompile(rgbaRegexString)
|
||||
hslRegex = regexp.MustCompile(hslRegexString)
|
||||
hslaRegex = regexp.MustCompile(hslaRegexString)
|
||||
emailRegex = regexp.MustCompile(emailRegexString)
|
||||
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 {
|
||||
|
|
804
validator.go
804
validator.go
|
@ -14,38 +14,230 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
tagSeparator = ","
|
||||
orSeparator = "|"
|
||||
noValidationTag = "-"
|
||||
tagKeySeparator = "="
|
||||
structOnlyTag = "structonly"
|
||||
omitempty = "omitempty"
|
||||
fieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag"
|
||||
structErrMsg = "Struct:%s\n"
|
||||
utf8HexComma = "0x2C"
|
||||
tagSeparator = ","
|
||||
orSeparator = "|"
|
||||
noValidationTag = "-"
|
||||
tagKeySeparator = "="
|
||||
structOnlyTag = "structonly"
|
||||
omitempty = "omitempty"
|
||||
required = "required"
|
||||
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"
|
||||
diveTag = "dive"
|
||||
arrayIndexFieldName = "%s[%d]"
|
||||
mapIndexFieldName = "%s[%v]"
|
||||
)
|
||||
|
||||
var structPool *sync.Pool
|
||||
|
||||
// returns new *StructErrors to the pool
|
||||
func newStructErrors() interface{} {
|
||||
return &StructErrors{
|
||||
Errors: map[string]*FieldError{},
|
||||
StructErrors: map[string]*StructErrors{},
|
||||
}
|
||||
}
|
||||
|
||||
type cachedTags struct {
|
||||
keyVals [][]string
|
||||
isOrVal bool
|
||||
}
|
||||
|
||||
type cachedField struct {
|
||||
index int
|
||||
name string
|
||||
tags []*cachedTags
|
||||
tag string
|
||||
kind reflect.Kind
|
||||
typ reflect.Type
|
||||
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 {
|
||||
children int
|
||||
name string
|
||||
kind reflect.Kind
|
||||
fields []*cachedField
|
||||
}
|
||||
|
||||
type structsCacheMap struct {
|
||||
lock sync.RWMutex
|
||||
m map[reflect.Type]*cachedStruct
|
||||
}
|
||||
|
||||
func (s *structsCacheMap) Get(key reflect.Type) (*cachedStruct, bool) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
value, ok := s.m[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (s *structsCacheMap) Set(key reflect.Type, value *cachedStruct) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.m[key] = value
|
||||
}
|
||||
|
||||
var structCache = &structsCacheMap{m: map[reflect.Type]*cachedStruct{}}
|
||||
|
||||
type fieldsCacheMap struct {
|
||||
lock sync.RWMutex
|
||||
m map[string][]*cachedTags
|
||||
}
|
||||
|
||||
func (s *fieldsCacheMap) Get(key string) ([]*cachedTags, bool) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
value, ok := s.m[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (s *fieldsCacheMap) Set(key string, value []*cachedTags) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.m[key] = value
|
||||
}
|
||||
|
||||
var fieldsCache = &fieldsCacheMap{m: map[string][]*cachedTags{}}
|
||||
|
||||
// FieldError contains a single field's validation error along
|
||||
// with other properties that may be needed for error message creation
|
||||
type FieldError struct {
|
||||
Field string
|
||||
Tag string
|
||||
Kind reflect.Kind
|
||||
Type reflect.Type
|
||||
Param string
|
||||
Value interface{}
|
||||
Field string
|
||||
Tag string
|
||||
Kind reflect.Kind
|
||||
Type reflect.Type
|
||||
Param string
|
||||
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.
|
||||
// it also allows FieldError to be used as an Error interface
|
||||
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)
|
||||
}
|
||||
|
||||
// Flatten flattens the FieldError hierarchical structure into a flat namespace style field name
|
||||
// for those that want/need it.
|
||||
// This is now needed because of the new dive functionality
|
||||
func (e *FieldError) Flatten() map[string]*FieldError {
|
||||
|
||||
errs := map[string]*FieldError{}
|
||||
|
||||
if e.IsPlaceholderErr {
|
||||
|
||||
if e.IsSliceOrArray {
|
||||
for key, err := range e.SliceOrArrayErrs {
|
||||
|
||||
fe, ok := err.(*FieldError)
|
||||
|
||||
if ok {
|
||||
|
||||
if flat := fe.Flatten(); flat != nil && len(flat) > 0 {
|
||||
for k, v := range flat {
|
||||
if fe.IsPlaceholderErr {
|
||||
errs[fmt.Sprintf("[%#v]%s", key, k)] = v
|
||||
} else {
|
||||
errs[fmt.Sprintf("[%#v]", key)] = v
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
se := err.(*StructErrors)
|
||||
|
||||
if flat := se.Flatten(); flat != nil && len(flat) > 0 {
|
||||
for k, v := range flat {
|
||||
errs[fmt.Sprintf("[%#v].%s.%s", key, se.Struct, k)] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if e.IsMap {
|
||||
for key, err := range e.MapErrs {
|
||||
|
||||
fe, ok := err.(*FieldError)
|
||||
|
||||
if ok {
|
||||
|
||||
if flat := fe.Flatten(); flat != nil && len(flat) > 0 {
|
||||
for k, v := range flat {
|
||||
if fe.IsPlaceholderErr {
|
||||
errs[fmt.Sprintf("[%#v]%s", key, k)] = v
|
||||
} else {
|
||||
errs[fmt.Sprintf("[%#v]", key)] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
se := err.(*StructErrors)
|
||||
|
||||
if flat := se.Flatten(); flat != nil && len(flat) > 0 {
|
||||
for k, v := range flat {
|
||||
errs[fmt.Sprintf("[%#v].%s.%s", key, se.Struct, k)] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
errs[e.Field] = e
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// StructErrors is hierarchical list of field and struct validation errors
|
||||
// for a non hierarchical representation please see the Flatten method for StructErrors
|
||||
type StructErrors struct {
|
||||
|
@ -72,7 +264,7 @@ func (e *StructErrors) Error() string {
|
|||
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
|
||||
|
@ -87,7 +279,17 @@ func (e *StructErrors) Flatten() map[string]*FieldError {
|
|||
|
||||
for _, f := range e.Errors {
|
||||
|
||||
errs[f.Field] = f
|
||||
if flat := f.Flatten(); flat != nil && len(flat) > 0 {
|
||||
|
||||
for k, fe := range flat {
|
||||
|
||||
if f.IsPlaceholderErr {
|
||||
errs[f.Field+k] = fe
|
||||
} else {
|
||||
errs[k] = fe
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, val := range e.StructErrors {
|
||||
|
@ -124,6 +326,9 @@ type Validate struct {
|
|||
|
||||
// New creates a new Validate instance for use.
|
||||
func New(tagName string, funcs map[string]Func) *Validate {
|
||||
|
||||
structPool = &sync.Pool{New: newStructErrors}
|
||||
|
||||
return &Validate{
|
||||
tagName: tagName,
|
||||
validationFuncs: funcs,
|
||||
|
@ -132,12 +337,23 @@ func New(tagName string, funcs map[string]Func) *Validate {
|
|||
|
||||
// SetTag sets tagName of the Validator to one of your choosing after creation
|
||||
// perhaps to dodge a tag name conflict in a specific section of code
|
||||
// NOTE: this method is not thread-safe
|
||||
func (v *Validate) SetTag(tagName string) {
|
||||
v.tagName = tagName
|
||||
}
|
||||
|
||||
// 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
|
||||
// nearly all cases. only increase if you have a deeply nested struct structure.
|
||||
// NOTE: this method is not thread-safe
|
||||
// NOTE: this is only here to keep compatibility with v5, in v6 the method will be removed
|
||||
func (v *Validate) SetMaxStructPoolSize(max int) {
|
||||
structPool = &sync.Pool{New: newStructErrors}
|
||||
}
|
||||
|
||||
// AddFunction adds a validation Func to a Validate's map of validators denoted by the key
|
||||
// NOTE: if the key already exists, it will get replaced.
|
||||
// NOTE: this method is not thread-safe
|
||||
func (v *Validate) AddFunction(key string, f Func) error {
|
||||
|
||||
if len(key) == 0 {
|
||||
|
@ -176,47 +392,84 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
|
|||
}
|
||||
|
||||
structType := reflect.TypeOf(s)
|
||||
structName := structType.Name()
|
||||
|
||||
validationErrors := &StructErrors{
|
||||
Struct: structName,
|
||||
Errors: map[string]*FieldError{},
|
||||
StructErrors: map[string]*StructErrors{},
|
||||
var structName string
|
||||
var numFields int
|
||||
var cs *cachedStruct
|
||||
var isCached bool
|
||||
|
||||
cs, isCached = structCache.Get(structType)
|
||||
|
||||
if isCached {
|
||||
structName = cs.name
|
||||
numFields = cs.children
|
||||
} else {
|
||||
structName = structType.Name()
|
||||
numFields = structValue.NumField()
|
||||
cs = &cachedStruct{name: structName, children: numFields}
|
||||
}
|
||||
|
||||
var numFields = structValue.NumField()
|
||||
validationErrors := structPool.Get().(*StructErrors)
|
||||
validationErrors.Struct = structName
|
||||
|
||||
for i := 0; i < numFields; i++ {
|
||||
|
||||
valueField := structValue.Field(i)
|
||||
typeField := structType.Field(i)
|
||||
var valueField reflect.Value
|
||||
var cField *cachedField
|
||||
var typeField reflect.StructField
|
||||
|
||||
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
|
||||
valueField = valueField.Elem()
|
||||
}
|
||||
if isCached {
|
||||
cField = cs.fields[i]
|
||||
valueField = structValue.Field(cField.index)
|
||||
|
||||
tag := typeField.Tag.Get(v.tagName)
|
||||
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
|
||||
valueField = valueField.Elem()
|
||||
}
|
||||
} else {
|
||||
valueField = structValue.Field(i)
|
||||
|
||||
if tag == noValidationTag {
|
||||
continue
|
||||
}
|
||||
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
|
||||
valueField = valueField.Elem()
|
||||
}
|
||||
|
||||
// if no validation and not a struct (which may containt fields for validation)
|
||||
if tag == "" && ((valueField.Kind() != reflect.Struct && valueField.Kind() != reflect.Interface) || valueField.Type() == reflect.TypeOf(time.Time{})) {
|
||||
continue
|
||||
}
|
||||
typeField = structType.Field(i)
|
||||
|
||||
switch valueField.Kind() {
|
||||
cField = &cachedField{index: i, tag: typeField.Tag.Get(v.tagName), isTime: (valueField.Type() == reflect.TypeOf(time.Time{}) || valueField.Type() == reflect.TypeOf(&time.Time{}))}
|
||||
|
||||
case reflect.Struct, reflect.Interface:
|
||||
|
||||
if !unicode.IsUpper(rune(typeField.Name[0])) {
|
||||
if cField.tag == noValidationTag {
|
||||
cs.children--
|
||||
continue
|
||||
}
|
||||
|
||||
if valueField.Type() == reflect.TypeOf(time.Time{}) {
|
||||
// if no validation and not a struct (which may containt fields for validation)
|
||||
if cField.tag == "" && ((valueField.Kind() != reflect.Struct && valueField.Kind() != reflect.Interface) || valueField.Type() == reflect.TypeOf(time.Time{})) {
|
||||
cs.children--
|
||||
continue
|
||||
}
|
||||
|
||||
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), typeField.Name, tag); fieldError != nil {
|
||||
cField.name = typeField.Name
|
||||
cField.kind = valueField.Kind()
|
||||
cField.typ = valueField.Type()
|
||||
}
|
||||
|
||||
// this can happen if the first cache value was nil
|
||||
// but the second actually has a value
|
||||
if cField.kind == reflect.Ptr {
|
||||
cField.kind = valueField.Kind()
|
||||
}
|
||||
|
||||
switch cField.kind {
|
||||
|
||||
case reflect.Struct, reflect.Interface:
|
||||
|
||||
if !unicode.IsUpper(rune(cField.name[0])) {
|
||||
cs.children--
|
||||
continue
|
||||
}
|
||||
|
||||
if cField.isTime {
|
||||
|
||||
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
|
||||
|
@ -224,28 +477,116 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
|
|||
|
||||
} else {
|
||||
|
||||
if strings.Contains(tag, structOnlyTag) {
|
||||
if strings.Contains(cField.tag, structOnlyTag) {
|
||||
cs.children--
|
||||
continue
|
||||
}
|
||||
|
||||
if (valueField.Kind() == reflect.Ptr || cField.kind == reflect.Interface) && valueField.IsNil() {
|
||||
|
||||
if strings.Contains(cField.tag, omitempty) {
|
||||
goto CACHEFIELD
|
||||
}
|
||||
|
||||
tags := strings.Split(cField.tag, tagSeparator)
|
||||
|
||||
if len(tags) > 0 {
|
||||
|
||||
var param string
|
||||
vals := strings.SplitN(tags[0], tagKeySeparator, 2)
|
||||
|
||||
if len(vals) > 1 {
|
||||
param = vals[1]
|
||||
}
|
||||
|
||||
validationErrors.Errors[cField.name] = &FieldError{
|
||||
Field: cField.name,
|
||||
Tag: vals[0],
|
||||
Param: param,
|
||||
Value: valueField.Interface(),
|
||||
Kind: valueField.Kind(),
|
||||
Type: valueField.Type(),
|
||||
}
|
||||
|
||||
goto CACHEFIELD
|
||||
}
|
||||
}
|
||||
|
||||
// if we get here, the field is interface and could be a struct or a field
|
||||
// and we need to check the inner type and validate
|
||||
if cField.kind == reflect.Interface {
|
||||
|
||||
valueField = valueField.Elem()
|
||||
|
||||
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
|
||||
valueField = valueField.Elem()
|
||||
}
|
||||
|
||||
if valueField.Kind() == reflect.Struct {
|
||||
goto VALIDATESTRUCT
|
||||
}
|
||||
|
||||
// sending nil for cField as it was type interface and could be anything
|
||||
// each time and so must be calculated each time and can't be cached reliably
|
||||
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, nil); fieldError != nil {
|
||||
validationErrors.Errors[fieldError.Field] = fieldError
|
||||
// free up memory reference
|
||||
fieldError = nil
|
||||
}
|
||||
|
||||
goto CACHEFIELD
|
||||
}
|
||||
|
||||
VALIDATESTRUCT:
|
||||
if structErrors := v.structRecursive(top, valueField.Interface(), valueField.Interface()); structErrors != nil {
|
||||
validationErrors.StructErrors[typeField.Name] = structErrors
|
||||
validationErrors.StructErrors[cField.name] = structErrors
|
||||
// free up memory map no longer needed
|
||||
structErrors = nil
|
||||
}
|
||||
}
|
||||
|
||||
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(), typeField.Name, tag); fieldError != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
CACHEFIELD:
|
||||
if !isCached {
|
||||
cs.fields = append(cs.fields, cField)
|
||||
}
|
||||
}
|
||||
|
||||
structCache.Set(structType, cs)
|
||||
|
||||
if len(validationErrors.Errors) == 0 && len(validationErrors.StructErrors) == 0 {
|
||||
structPool.Put(validationErrors)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -254,20 +595,22 @@ 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
|
||||
func (v *Validate) Field(f interface{}, tag string) *FieldError {
|
||||
|
||||
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
|
||||
func (v *Validate) FieldWithValue(val interface{}, f interface{}, tag string) *FieldError {
|
||||
|
||||
return v.fieldWithNameAndValue(nil, val, f, "", tag)
|
||||
return v.fieldWithNameAndValue(nil, val, f, tag, "", true, nil)
|
||||
}
|
||||
|
||||
func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f interface{}, name string, tag string) *FieldError {
|
||||
func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f interface{}, tag string, name string, isSingleField bool, cacheField *cachedField) *FieldError {
|
||||
|
||||
var cField *cachedField
|
||||
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
|
||||
if tag == noValidationTag {
|
||||
if tag == noValidationTag || tag == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -275,87 +618,357 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
|
|||
return nil
|
||||
}
|
||||
|
||||
valueField := reflect.ValueOf(f)
|
||||
fieldKind := valueField.Kind()
|
||||
valueField = reflect.ValueOf(f)
|
||||
|
||||
if fieldKind == reflect.Ptr && !valueField.IsNil() {
|
||||
return v.fieldWithNameAndValue(val, current, valueField.Elem().Interface(), name, tag)
|
||||
if cacheField == nil {
|
||||
|
||||
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
|
||||
valueField = valueField.Elem()
|
||||
f = valueField.Interface()
|
||||
}
|
||||
|
||||
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 {
|
||||
cField = cacheField
|
||||
}
|
||||
|
||||
fieldType := valueField.Type()
|
||||
|
||||
switch fieldKind {
|
||||
switch cField.kind {
|
||||
|
||||
case reflect.Struct, reflect.Interface, reflect.Invalid:
|
||||
|
||||
if fieldType != reflect.TypeOf(time.Time{}) {
|
||||
panic("Invalid field passed to ValidateFieldWithTag")
|
||||
if cField.typ != reflect.TypeOf(time.Time{}) {
|
||||
panic("Invalid field passed to fieldWithNameAndValue")
|
||||
}
|
||||
}
|
||||
|
||||
var valErr *FieldError
|
||||
if len(cField.tags) == 0 {
|
||||
|
||||
if isSingleField {
|
||||
cField.tags, isCached = fieldsCache.Get(tag)
|
||||
}
|
||||
|
||||
if !isCached {
|
||||
|
||||
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)
|
||||
cTag := &cachedTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))}
|
||||
cField.tags = append(cField.tags, cTag)
|
||||
|
||||
for i, val := range orVals {
|
||||
vals := strings.SplitN(val, tagKeySeparator, 2)
|
||||
|
||||
key := strings.TrimSpace(vals[0])
|
||||
|
||||
if len(key) == 0 {
|
||||
panic(fmt.Sprintf("Invalid validation tag on field %s", name))
|
||||
}
|
||||
|
||||
param := ""
|
||||
if len(vals) > 1 {
|
||||
param = strings.Replace(vals[1], utf8HexComma, ",", -1)
|
||||
}
|
||||
|
||||
cTag.keyVals[i] = []string{key, param}
|
||||
}
|
||||
}
|
||||
|
||||
if isSingleField {
|
||||
fieldsCache.Set(cField.tag, cField.tags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fieldErr *FieldError
|
||||
var err error
|
||||
valTags := strings.Split(tag, tagSeparator)
|
||||
|
||||
for _, valTag := range valTags {
|
||||
for _, cTag := range cField.tags {
|
||||
|
||||
orVals := strings.Split(valTag, orSeparator)
|
||||
|
||||
if len(orVals) > 1 {
|
||||
if cTag.isOrVal {
|
||||
|
||||
errTag := ""
|
||||
|
||||
for _, val := range orVals {
|
||||
for _, val := range cTag.keyVals {
|
||||
|
||||
valErr, err = v.fieldWithNameAndSingleTag(val, current, f, name, val)
|
||||
fieldErr, err = v.fieldWithNameAndSingleTag(val, current, f, val[0], val[1], name)
|
||||
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
errTag += orSeparator + valErr.Tag
|
||||
|
||||
errTag += orSeparator + fieldErr.Tag
|
||||
}
|
||||
|
||||
errTag = strings.TrimLeft(errTag, orSeparator)
|
||||
|
||||
valErr.Tag = errTag
|
||||
valErr.Kind = fieldKind
|
||||
fieldErr.Tag = errTag
|
||||
fieldErr.Kind = cField.kind
|
||||
fieldErr.Type = cField.typ
|
||||
|
||||
return valErr
|
||||
return fieldErr
|
||||
}
|
||||
|
||||
if valErr, err = v.fieldWithNameAndSingleTag(val, current, f, name, valTag); err != nil {
|
||||
if fieldErr, err = v.fieldWithNameAndSingleTag(val, current, f, cTag.keyVals[0][0], cTag.keyVals[0][1], name); err != nil {
|
||||
|
||||
valErr.Kind = valueField.Kind()
|
||||
valErr.Type = fieldType
|
||||
fieldErr.Kind = cField.kind
|
||||
fieldErr.Type = cField.typ
|
||||
|
||||
return valErr
|
||||
return fieldErr
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, name string, valTag string) (*FieldError, error) {
|
||||
func (v *Validate) traverseMap(val interface{}, current interface{}, valueField reflect.Value, cField *cachedField) map[interface{}]error {
|
||||
|
||||
vals := strings.Split(valTag, tagKeySeparator)
|
||||
key := strings.TrimSpace(vals[0])
|
||||
errs := map[interface{}]error{}
|
||||
|
||||
if len(key) == 0 {
|
||||
panic(fmt.Sprintf("Invalid validation tag on field %s", name))
|
||||
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.Kind() == reflect.Interface) && idxField.IsNil() {
|
||||
|
||||
if strings.Contains(cField.diveTag, omitempty) {
|
||||
continue
|
||||
}
|
||||
|
||||
tags := strings.Split(cField.diveTag, tagSeparator)
|
||||
|
||||
if len(tags) > 0 {
|
||||
|
||||
var param string
|
||||
vals := strings.SplitN(tags[0], tagKeySeparator, 2)
|
||||
|
||||
if len(vals) > 1 {
|
||||
param = vals[1]
|
||||
}
|
||||
|
||||
errs[key.Interface()] = &FieldError{
|
||||
Field: fmt.Sprintf(mapIndexFieldName, cField.name, key.Interface()),
|
||||
Tag: vals[0],
|
||||
Param: param,
|
||||
Value: idxField.Interface(),
|
||||
Kind: idxField.Kind(),
|
||||
Type: cField.mapSubtype,
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// if we get here, the field is interface and could be a struct or a field
|
||||
// and we need to check the inner type and validate
|
||||
if idxField.Kind() == reflect.Interface {
|
||||
|
||||
idxField = idxField.Elem()
|
||||
|
||||
if idxField.Kind() == reflect.Ptr && !idxField.IsNil() {
|
||||
idxField = idxField.Elem()
|
||||
}
|
||||
|
||||
if idxField.Kind() == reflect.Struct {
|
||||
goto VALIDATESTRUCT
|
||||
}
|
||||
|
||||
// sending nil for cField as it was type interface and could be anything
|
||||
// each time and so must be calculated each time and can't be cached reliably
|
||||
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
|
||||
}
|
||||
|
||||
VALIDATESTRUCT:
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
valErr := &FieldError{
|
||||
Field: name,
|
||||
Tag: key,
|
||||
Value: f,
|
||||
Param: "",
|
||||
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.Kind() == reflect.Interface) && idxField.IsNil() {
|
||||
|
||||
if strings.Contains(cField.diveTag, omitempty) {
|
||||
continue
|
||||
}
|
||||
|
||||
tags := strings.Split(cField.diveTag, tagSeparator)
|
||||
|
||||
if len(tags) > 0 {
|
||||
|
||||
var param string
|
||||
vals := strings.SplitN(tags[0], tagKeySeparator, 2)
|
||||
|
||||
if len(vals) > 1 {
|
||||
param = vals[1]
|
||||
}
|
||||
|
||||
errs[i] = &FieldError{
|
||||
Field: fmt.Sprintf(arrayIndexFieldName, cField.name, i),
|
||||
Tag: vals[0],
|
||||
Param: param,
|
||||
Value: idxField.Interface(),
|
||||
Kind: idxField.Kind(),
|
||||
Type: cField.sliceSubtype,
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// if we get here, the field is interface and could be a struct or a field
|
||||
// and we need to check the inner type and validate
|
||||
if idxField.Kind() == reflect.Interface {
|
||||
|
||||
idxField = idxField.Elem()
|
||||
|
||||
if idxField.Kind() == reflect.Ptr && !idxField.IsNil() {
|
||||
idxField = idxField.Elem()
|
||||
}
|
||||
|
||||
if idxField.Kind() == reflect.Struct {
|
||||
goto VALIDATESTRUCT
|
||||
}
|
||||
|
||||
// sending nil for cField as it was type interface and could be anything
|
||||
// each time and so must be calculated each time and can't be cached reliably
|
||||
if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(arrayIndexFieldName, cField.name, i), false, nil); fieldError != nil {
|
||||
errs[i] = fieldError
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
VALIDATESTRUCT:
|
||||
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) {
|
||||
|
||||
// OK to continue because we checked it's existance before getting into this loop
|
||||
if key == omitempty {
|
||||
return valErr, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
valFunc, ok := v.validationFuncs[key]
|
||||
|
@ -363,15 +976,14 @@ func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{
|
|||
panic(fmt.Sprintf("Undefined validation function on field %s", name))
|
||||
}
|
||||
|
||||
param := ""
|
||||
if len(vals) > 1 {
|
||||
param = strings.TrimSpace(vals[1])
|
||||
if err := valFunc(val, current, f, param); err {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := valFunc(val, current, f, param); !err {
|
||||
valErr.Param = param
|
||||
return valErr, errors.New(key)
|
||||
}
|
||||
|
||||
return valErr, nil
|
||||
return &FieldError{
|
||||
Field: name,
|
||||
Tag: key,
|
||||
Value: f,
|
||||
Param: param,
|
||||
}, errors.New(key)
|
||||
}
|
||||
|
|
1555
validator_test.go
1555
validator_test.go
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue