mirror of
https://github.com/eiblog/eiblog.git
synced 2026-03-01 00:34:58 +08:00
add vendor
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
*.test
|
||||
*.out
|
||||
*.txt
|
||||
cover.html
|
||||
README.html
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Dean Karn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
+368
@@ -0,0 +1,368 @@
|
||||
Package validator
|
||||
================
|
||||
|
||||

|
||||
|
||||
[](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://semaphoreci.com/joeybloggs/validator)
|
||||
[](https://coveralls.io/github/go-playground/validator?branch=v8)
|
||||
[](http://goreportcard.com/report/go-playground/validator)
|
||||
[](https://godoc.org/gopkg.in/go-playground/validator.v8)
|
||||
|
||||
Package validator implements value validations for structs and individual fields based on tags.
|
||||
|
||||
It has the following **unique** features:
|
||||
|
||||
- Cross Field and Cross Struct validations by using validation tags or custom validators.
|
||||
- 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.
|
||||
- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29)
|
||||
- Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs
|
||||
- Extraction of custom defined Field Name e.g. can specify to extract the JSON name while validating and have it available in the resulting FieldError
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Use go get.
|
||||
|
||||
go get gopkg.in/go-playground/validator.v8
|
||||
|
||||
or to update
|
||||
|
||||
go get -u gopkg.in/go-playground/validator.v8
|
||||
|
||||
Then import the validator package into your own code.
|
||||
|
||||
import "gopkg.in/go-playground/validator.v8"
|
||||
|
||||
Error Return Value
|
||||
-------
|
||||
|
||||
Validation functions return type error
|
||||
|
||||
They return type error to avoid the issue discussed in the following, where err is always != nil:
|
||||
|
||||
* http://stackoverflow.com/a/29138676/3158232
|
||||
* https://github.com/go-playground/validator/issues/134
|
||||
|
||||
validator only returns nil or ValidationErrors as type error; so in you code all you need to do
|
||||
is check if the error returned is not nil, and if it's not type cast it to type ValidationErrors
|
||||
like so:
|
||||
|
||||
```go
|
||||
err := validate.Struct(mystruct)
|
||||
validationErrors := err.(validator.ValidationErrors)
|
||||
```
|
||||
|
||||
Usage and documentation
|
||||
------
|
||||
|
||||
Please see http://godoc.org/gopkg.in/go-playground/validator.v8 for detailed usage docs.
|
||||
|
||||
##### Examples:
|
||||
|
||||
Struct & Field validation
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// 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() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate = validator.New(config)
|
||||
|
||||
validateStruct()
|
||||
validateField()
|
||||
}
|
||||
|
||||
func validateStruct() {
|
||||
|
||||
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 ValidationErrors ( map[string]*FieldError )
|
||||
errs := validate.Struct(user)
|
||||
|
||||
if errs != nil {
|
||||
|
||||
fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag
|
||||
// Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag
|
||||
err := errs.(validator.ValidationErrors)["User.Addresses[0].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
|
||||
}
|
||||
|
||||
func validateField() {
|
||||
myEmail := "joeybloggs.gmail.com"
|
||||
|
||||
errs := validate.Field(myEmail, "required,email")
|
||||
|
||||
if errs != nil {
|
||||
fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag
|
||||
return
|
||||
}
|
||||
|
||||
// email ok, move on
|
||||
}
|
||||
```
|
||||
|
||||
Custom Field Type
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// DbBackedUser User struct
|
||||
type DbBackedUser struct {
|
||||
Name sql.NullString `validate:"required"`
|
||||
Age sql.NullInt64 `validate:"required"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate := validator.New(config)
|
||||
|
||||
// register all sql.Null* types to use the ValidateValuer CustomTypeFunc
|
||||
validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{})
|
||||
|
||||
x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}}
|
||||
errs := validate.Struct(x)
|
||||
|
||||
if len(errs.(validator.ValidationErrors)) > 0 {
|
||||
fmt.Printf("Errs:\n%+v\n", errs)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateValuer implements validator.CustomTypeFunc
|
||||
func ValidateValuer(field reflect.Value) interface{} {
|
||||
if valuer, ok := field.Interface().(driver.Valuer); ok {
|
||||
val, err := valuer.Value()
|
||||
if err == nil {
|
||||
return val
|
||||
}
|
||||
// handle the error how you want
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Struct Level Validation
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// User contains user information
|
||||
type User struct {
|
||||
FirstName string `json:"fname"`
|
||||
LastName string `json:"lname"`
|
||||
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() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate = validator.New(config)
|
||||
validate.RegisterStructValidation(UserStructLevelValidation, User{})
|
||||
|
||||
validateStruct()
|
||||
}
|
||||
|
||||
// UserStructLevelValidation contains custom struct level validations that don't always
|
||||
// make sense at the field validation level. For Example this function validates that either
|
||||
// FirstName or LastName exist; could have done that with a custom field validation but then
|
||||
// would have had to add it to both fields duplicating the logic + overhead, this way it's
|
||||
// only validated once.
|
||||
//
|
||||
// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way
|
||||
// hooks right into validator and you can combine with validation tags and still have a
|
||||
// common error output format.
|
||||
func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) {
|
||||
|
||||
user := structLevel.CurrentStruct.Interface().(User)
|
||||
|
||||
if len(user.FirstName) == 0 && len(user.LastName) == 0 {
|
||||
structLevel.ReportError(reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname")
|
||||
structLevel.ReportError(reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname")
|
||||
}
|
||||
|
||||
// plus can to more, even with different tag than "fnameorlname"
|
||||
}
|
||||
|
||||
func validateStruct() {
|
||||
|
||||
address := &Address{
|
||||
Street: "Eavesdown Docks",
|
||||
Planet: "Persphone",
|
||||
Phone: "none",
|
||||
City: "Unknown",
|
||||
}
|
||||
|
||||
user := &User{
|
||||
FirstName: "",
|
||||
LastName: "",
|
||||
Age: 45,
|
||||
Email: "Badger.Smith@gmail.com",
|
||||
FavouriteColor: "#000",
|
||||
Addresses: []*Address{address},
|
||||
}
|
||||
|
||||
// returns nil or ValidationErrors ( map[string]*FieldError )
|
||||
errs := validate.Struct(user)
|
||||
|
||||
if errs != nil {
|
||||
|
||||
fmt.Println(errs) // output: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag
|
||||
// Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag
|
||||
err := errs.(validator.ValidationErrors)["User.FirstName"]
|
||||
fmt.Println(err.Field) // output: FirstName
|
||||
fmt.Println(err.Tag) // output: fnameorlname
|
||||
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 using Go version go1.5.2 darwin/amd64
|
||||
```go
|
||||
go test -cpu=4 -bench=. -benchmem=true
|
||||
PASS
|
||||
BenchmarkFieldSuccess-4 10000000 176 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFieldFailure-4 2000000 727 ns/op 432 B/op 4 allocs/op
|
||||
BenchmarkFieldDiveSuccess-4 500000 3220 ns/op 480 B/op 27 allocs/op
|
||||
BenchmarkFieldDiveFailure-4 500000 3823 ns/op 912 B/op 31 allocs/op
|
||||
BenchmarkFieldCustomTypeSuccess-4 5000000 368 ns/op 32 B/op 2 allocs/op
|
||||
BenchmarkFieldCustomTypeFailure-4 2000000 699 ns/op 432 B/op 4 allocs/op
|
||||
BenchmarkFieldOrTagSuccess-4 1000000 1265 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkFieldOrTagFailure-4 1000000 1182 ns/op 464 B/op 6 allocs/op
|
||||
BenchmarkStructLevelValidationSuccess-4 2000000 739 ns/op 176 B/op 6 allocs/op
|
||||
BenchmarkStructLevelValidationFailure-4 1000000 1368 ns/op 640 B/op 11 allocs/op
|
||||
BenchmarkStructSimpleCustomTypeSuccess-4 2000000 965 ns/op 80 B/op 5 allocs/op
|
||||
BenchmarkStructSimpleCustomTypeFailure-4 1000000 1561 ns/op 688 B/op 11 allocs/op
|
||||
BenchmarkStructPartialSuccess-4 1000000 1285 ns/op 384 B/op 10 allocs/op
|
||||
BenchmarkStructPartialFailure-4 1000000 1879 ns/op 832 B/op 15 allocs/op
|
||||
BenchmarkStructExceptSuccess-4 2000000 1038 ns/op 336 B/op 7 allocs/op
|
||||
BenchmarkStructExceptFailure-4 1000000 1330 ns/op 384 B/op 10 allocs/op
|
||||
BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1081 ns/op 128 B/op 6 allocs/op
|
||||
BenchmarkStructSimpleCrossFieldFailure-4 1000000 1737 ns/op 592 B/op 11 allocs/op
|
||||
BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1790 ns/op 192 B/op 10 allocs/op
|
||||
BenchmarkStructSimpleCrossStructCrossFieldFailure-4 500000 2431 ns/op 656 B/op 15 allocs/op
|
||||
BenchmarkStructSimpleSuccess-4 2000000 950 ns/op 48 B/op 3 allocs/op
|
||||
BenchmarkStructSimpleFailure-4 1000000 1672 ns/op 688 B/op 11 allocs/op
|
||||
BenchmarkStructSimpleSuccessParallel-4 5000000 271 ns/op 48 B/op 3 allocs/op
|
||||
BenchmarkStructSimpleFailureParallel-4 2000000 670 ns/op 688 B/op 11 allocs/op
|
||||
BenchmarkStructComplexSuccess-4 300000 5828 ns/op 544 B/op 32 allocs/op
|
||||
BenchmarkStructComplexFailure-4 200000 11382 ns/op 3912 B/op 77 allocs/op
|
||||
BenchmarkStructComplexSuccessParallel-4 1000000 1739 ns/op 544 B/op 32 allocs/op
|
||||
BenchmarkStructComplexFailureParallel-4 300000 4682 ns/op 3912 B/op 77 allocs/op
|
||||
```
|
||||
|
||||
Complimentary Software
|
||||
----------------------
|
||||
|
||||
Here is a list of software that compliments using this library either pre or post validation.
|
||||
|
||||
* [Gorilla Schema](https://github.com/gorilla/schema) - Package gorilla/schema fills a struct with form values.
|
||||
* [Conform](https://github.com/leebenson/conform) - Trims, sanitizes & scrubs data based on struct tags.
|
||||
|
||||
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.
|
||||
|
||||
If the changes being proposed or requested are breaking changes, please create an issue, for discussion
|
||||
or create a pull request against the highest development branch for example this package has a
|
||||
v1 and v1-development branch however, there will also be a v2-development branch even though v2 doesn't exist yet.
|
||||
|
||||
I strongly encourage everyone whom creates a custom validation function to contribute them and
|
||||
help make this package even better.
|
||||
|
||||
License
|
||||
------
|
||||
Distributed under MIT License, please see license file in code for more details.
|
||||
+1238
File diff suppressed because it is too large
Load Diff
+524
@@ -0,0 +1,524 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
sql "database/sql/driver"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkFieldSuccess(b *testing.B) {
|
||||
|
||||
var s *string
|
||||
tmp := "1"
|
||||
s = &tmp
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Field(s, "len=1")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFieldFailure(b *testing.B) {
|
||||
|
||||
var s *string
|
||||
tmp := "12"
|
||||
s = &tmp
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Field(s, "len=1")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFieldDiveSuccess(b *testing.B) {
|
||||
|
||||
m := make([]*string, 3)
|
||||
t1 := "val1"
|
||||
t2 := "val2"
|
||||
t3 := "val3"
|
||||
|
||||
m[0] = &t1
|
||||
m[1] = &t2
|
||||
m[2] = &t3
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Field(m, "required,dive,required")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFieldDiveFailure(b *testing.B) {
|
||||
|
||||
m := make([]*string, 3)
|
||||
t1 := "val1"
|
||||
t2 := ""
|
||||
t3 := "val3"
|
||||
|
||||
m[0] = &t1
|
||||
m[1] = &t2
|
||||
m[2] = &t3
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Field(m, "required,dive,required")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFieldCustomTypeSuccess(b *testing.B) {
|
||||
|
||||
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
|
||||
|
||||
val := valuer{
|
||||
Name: "1",
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Field(val, "len=1")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFieldCustomTypeFailure(b *testing.B) {
|
||||
|
||||
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
|
||||
|
||||
val := valuer{}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Field(val, "len=1")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFieldOrTagSuccess(b *testing.B) {
|
||||
|
||||
var s *string
|
||||
tmp := "rgba(0,0,0,1)"
|
||||
s = &tmp
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Field(s, "rgb|rgba")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFieldOrTagFailure(b *testing.B) {
|
||||
|
||||
var s *string
|
||||
tmp := "#000"
|
||||
s = &tmp
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Field(s, "rgb|rgba")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructLevelValidationSuccess(b *testing.B) {
|
||||
|
||||
validate.RegisterStructValidation(StructValidationTestStructSuccess, TestStruct{})
|
||||
|
||||
tst := &TestStruct{
|
||||
String: "good value",
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Struct(tst)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructLevelValidationFailure(b *testing.B) {
|
||||
|
||||
validate.RegisterStructValidation(StructValidationTestStruct, TestStruct{})
|
||||
|
||||
tst := &TestStruct{
|
||||
String: "good value",
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Struct(tst)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {
|
||||
|
||||
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
|
||||
|
||||
val := valuer{
|
||||
Name: "1",
|
||||
}
|
||||
|
||||
type Foo struct {
|
||||
Valuer valuer `validate:"len=1"`
|
||||
IntValue int `validate:"min=5,max=10"`
|
||||
}
|
||||
|
||||
validFoo := &Foo{Valuer: val, IntValue: 7}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Struct(validFoo)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) {
|
||||
|
||||
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
|
||||
|
||||
val := valuer{}
|
||||
|
||||
type Foo struct {
|
||||
Valuer valuer `validate:"len=1"`
|
||||
IntValue int `validate:"min=5,max=10"`
|
||||
}
|
||||
|
||||
validFoo := &Foo{Valuer: val, IntValue: 3}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Struct(validFoo)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructPartialSuccess(b *testing.B) {
|
||||
|
||||
type Test struct {
|
||||
Name string `validate:"required"`
|
||||
NickName string `validate:"required"`
|
||||
}
|
||||
|
||||
test := &Test{
|
||||
Name: "Joey Bloggs",
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.StructPartial(test, "Name")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructPartialFailure(b *testing.B) {
|
||||
|
||||
type Test struct {
|
||||
Name string `validate:"required"`
|
||||
NickName string `validate:"required"`
|
||||
}
|
||||
|
||||
test := &Test{
|
||||
Name: "Joey Bloggs",
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.StructPartial(test, "NickName")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructExceptSuccess(b *testing.B) {
|
||||
|
||||
type Test struct {
|
||||
Name string `validate:"required"`
|
||||
NickName string `validate:"required"`
|
||||
}
|
||||
|
||||
test := &Test{
|
||||
Name: "Joey Bloggs",
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.StructPartial(test, "Nickname")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructExceptFailure(b *testing.B) {
|
||||
|
||||
type Test struct {
|
||||
Name string `validate:"required"`
|
||||
NickName string `validate:"required"`
|
||||
}
|
||||
|
||||
test := &Test{
|
||||
Name: "Joey Bloggs",
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.StructPartial(test, "Name")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) {
|
||||
|
||||
type Test struct {
|
||||
Start time.Time
|
||||
End time.Time `validate:"gtfield=Start"`
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
then := now.Add(time.Hour * 5)
|
||||
|
||||
test := &Test{
|
||||
Start: now,
|
||||
End: then,
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Struct(test)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructSimpleCrossFieldFailure(b *testing.B) {
|
||||
|
||||
type Test struct {
|
||||
Start time.Time
|
||||
End time.Time `validate:"gtfield=Start"`
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
then := now.Add(time.Hour * -5)
|
||||
|
||||
test := &Test{
|
||||
Start: now,
|
||||
End: then,
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Struct(test)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) {
|
||||
|
||||
type Inner struct {
|
||||
Start time.Time
|
||||
}
|
||||
|
||||
type Outer struct {
|
||||
Inner *Inner
|
||||
CreatedAt time.Time `validate:"eqcsfield=Inner.Start"`
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
inner := &Inner{
|
||||
Start: now,
|
||||
}
|
||||
|
||||
outer := &Outer{
|
||||
Inner: inner,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Struct(outer)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructSimpleCrossStructCrossFieldFailure(b *testing.B) {
|
||||
|
||||
type Inner struct {
|
||||
Start time.Time
|
||||
}
|
||||
|
||||
type Outer struct {
|
||||
Inner *Inner
|
||||
CreatedAt time.Time `validate:"eqcsfield=Inner.Start"`
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
then := now.Add(time.Hour * 5)
|
||||
|
||||
inner := &Inner{
|
||||
Start: then,
|
||||
}
|
||||
|
||||
outer := &Outer{
|
||||
Inner: inner,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Struct(outer)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructSimpleSuccess(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}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Struct(validFoo)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructSimpleFailure(b *testing.B) {
|
||||
|
||||
type Foo struct {
|
||||
StringValue string `validate:"min=5,max=10"`
|
||||
IntValue int `validate:"min=5,max=10"`
|
||||
}
|
||||
|
||||
invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Struct(invalidFoo)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructSimpleSuccessParallel(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}
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
validate.Struct(validFoo)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkStructSimpleFailureParallel(b *testing.B) {
|
||||
|
||||
type Foo struct {
|
||||
StringValue string `validate:"min=5,max=10"`
|
||||
IntValue int `validate:"min=5,max=10"`
|
||||
}
|
||||
|
||||
invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
validate.Struct(invalidFoo)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkStructComplexSuccess(b *testing.B) {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructComplexFailure(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",
|
||||
},
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
validate.Struct(tFail)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStructComplexSuccessParallel(b *testing.B) {
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkStructComplexFailureParallel(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",
|
||||
},
|
||||
}
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
validate.Struct(tFail)
|
||||
}
|
||||
})
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type cachedField struct {
|
||||
Idx int
|
||||
Name string
|
||||
AltName string
|
||||
CachedTag *cachedTag
|
||||
}
|
||||
|
||||
type cachedStruct struct {
|
||||
Name string
|
||||
fields map[int]cachedField
|
||||
}
|
||||
|
||||
type structCacheMap struct {
|
||||
lock sync.RWMutex
|
||||
m map[reflect.Type]*cachedStruct
|
||||
}
|
||||
|
||||
func (s *structCacheMap) Get(key reflect.Type) (*cachedStruct, bool) {
|
||||
s.lock.RLock()
|
||||
value, ok := s.m[key]
|
||||
s.lock.RUnlock()
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (s *structCacheMap) Set(key reflect.Type, value *cachedStruct) {
|
||||
s.lock.Lock()
|
||||
s.m[key] = value
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
type cachedTag struct {
|
||||
tag string
|
||||
isOmitEmpty bool
|
||||
isNoStructLevel bool
|
||||
isStructOnly bool
|
||||
diveTag string
|
||||
tags []*tagVals
|
||||
}
|
||||
|
||||
type tagVals struct {
|
||||
tagVals [][]string
|
||||
isOrVal bool
|
||||
isAlias bool
|
||||
tag string
|
||||
}
|
||||
|
||||
type tagCacheMap struct {
|
||||
lock sync.RWMutex
|
||||
m map[string]*cachedTag
|
||||
}
|
||||
|
||||
func (s *tagCacheMap) Get(key string) (*cachedTag, bool) {
|
||||
s.lock.RLock()
|
||||
value, ok := s.m[key]
|
||||
s.lock.RUnlock()
|
||||
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (s *tagCacheMap) Set(key string, value *cachedTag) {
|
||||
s.lock.Lock()
|
||||
s.m[key] = value
|
||||
s.lock.Unlock()
|
||||
}
|
||||
+794
@@ -0,0 +1,794 @@
|
||||
/*
|
||||
Package validator implements value validations for structs and individual fields
|
||||
based on tags.
|
||||
|
||||
It can also handle Cross-Field and Cross-Struct validation for nested structs
|
||||
and has the ability to dive into arrays and maps of any type.
|
||||
|
||||
Why not a better error message?
|
||||
Because this library intends for you to handle your own error messages.
|
||||
|
||||
Why should I handle my own errors?
|
||||
Many reasons. We built an internationalized application and needed to know the
|
||||
field, and what validation failed so we could provide a localized error.
|
||||
|
||||
if fieldErr.Field == "Name" {
|
||||
switch fieldErr.ErrorTag
|
||||
case "required":
|
||||
return "Translated string based on field + error"
|
||||
default:
|
||||
return "Translated string based on field"
|
||||
}
|
||||
|
||||
|
||||
Validation Functions Return Type error
|
||||
|
||||
Doing things this way is actually the way the standard library does, see the
|
||||
file.Open method here:
|
||||
|
||||
https://golang.org/pkg/os/#Open.
|
||||
|
||||
The authors return type "error" to avoid the issue discussed in the following,
|
||||
where err is always != nil:
|
||||
|
||||
http://stackoverflow.com/a/29138676/3158232
|
||||
https://github.com/go-playground/validator/issues/134
|
||||
|
||||
Validator only returns nil or ValidationErrors as type error; so, in your code
|
||||
all you need to do is check if the error returned is not nil, and if it's not
|
||||
type cast it to type ValidationErrors like so err.(validator.ValidationErrors).
|
||||
|
||||
Custom Functions
|
||||
|
||||
Custom functions can be added. Example:
|
||||
|
||||
// Structure
|
||||
func customFunc(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
|
||||
|
||||
if whatever {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
validate.RegisterValidation("custom tag name", customFunc)
|
||||
// NOTES: using the same tag name as an existing function
|
||||
// will overwrite the existing one
|
||||
|
||||
Cross-Field Validation
|
||||
|
||||
Cross-Field Validation can be done via the following tags:
|
||||
- eqfield
|
||||
- nefield
|
||||
- gtfield
|
||||
- gtefield
|
||||
- ltfield
|
||||
- ltefield
|
||||
- eqcsfield
|
||||
- necsfield
|
||||
- gtcsfield
|
||||
- ftecsfield
|
||||
- ltcsfield
|
||||
- ltecsfield
|
||||
|
||||
If, however, some custom cross-field validation is required, it can be done
|
||||
using a custom validation.
|
||||
|
||||
Why not just have cross-fields validation tags (i.e. only eqcsfield and not
|
||||
eqfield)?
|
||||
|
||||
The reason is efficiency. If you want to check a field within the same struct
|
||||
"eqfield" only has to find the field on the same struct (1 level). But, if we
|
||||
used "eqcsfield" it could be multiple levels down. Example:
|
||||
|
||||
type Inner struct {
|
||||
StartDate time.Time
|
||||
}
|
||||
|
||||
type Outer struct {
|
||||
InnerStructField *Inner
|
||||
CreatedAt time.Time `validate:"ltecsfield=InnerStructField.StartDate"`
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
inner := &Inner{
|
||||
StartDate: now,
|
||||
}
|
||||
|
||||
outer := &Outer{
|
||||
InnerStructField: inner,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
errs := validate.Struct(outer)
|
||||
|
||||
// NOTE: when calling validate.Struct(val) topStruct will be the top level struct passed
|
||||
// into the function
|
||||
// when calling validate.FieldWithValue(val, field, tag) val will be
|
||||
// whatever you pass, struct, field...
|
||||
// when calling validate.Field(field, tag) val will be nil
|
||||
|
||||
Multiple Validators
|
||||
|
||||
Multiple validators on a field will process in the order defined. Example:
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"max=10,min=1"`
|
||||
}
|
||||
|
||||
// max will be checked then min
|
||||
|
||||
Bad Validator definitions are not handled by the library. Example:
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"min=10,max=0"`
|
||||
}
|
||||
|
||||
// this definition of min max will never succeed
|
||||
|
||||
Using Validator Tags
|
||||
|
||||
Baked In Cross-Field validation only compares fields on the same struct.
|
||||
If Cross-Field + Cross-Struct validation is needed you should implement your
|
||||
own custom validator.
|
||||
|
||||
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.
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"excludesall=,"` // BAD! Do not include a comma.
|
||||
Field `validate:"excludesall=0x2C"` // GOOD! Use the UTF-8 hex representation.
|
||||
}
|
||||
|
||||
Pipe ("|") is the default separator of validation tags. If you wish to
|
||||
have a pipe included within the parameter i.e. excludesall=| you will need to
|
||||
use the UTF-8 hex representation 0x7C, which is replaced in the code as a pipe,
|
||||
so the above will become excludesall=0x7C
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"excludesall=|"` // BAD! Do not include a a pipe!
|
||||
Field `validate:"excludesall=0x7C"` // GOOD! Use the UTF-8 hex representation.
|
||||
}
|
||||
|
||||
|
||||
Baked In Validators and Tags
|
||||
|
||||
Here is a list of the current built in validators:
|
||||
|
||||
|
||||
Skip Field
|
||||
|
||||
Tells the validation to skip this struct field; this is particularily
|
||||
handy in ignoring embedded structs from being validated. (Usage: -)
|
||||
Usage: -
|
||||
|
||||
|
||||
Or Operator
|
||||
|
||||
This is the 'or' operator allowing multiple validators to be used and
|
||||
accepted. (Usage: rbg|rgba) <-- this would allow either rgb or rgba
|
||||
colors to be accepted. This can also be combined with 'and' for example
|
||||
( Usage: omitempty,rgb|rgba)
|
||||
|
||||
Usage: |
|
||||
|
||||
StructOnly
|
||||
|
||||
When a field that is a nested struct is encountered, and contains this flag
|
||||
any validation on the nested struct will be run, but none of the nested
|
||||
struct fields will be validated. This is usefull if inside of you program
|
||||
you know the struct will be valid, but need to verify it has been assigned.
|
||||
NOTE: only "required" and "omitempty" can be used on a struct itself.
|
||||
|
||||
Usage: structonly
|
||||
|
||||
NoStructLevel
|
||||
|
||||
Same as structonly tag except that any struct level validations will not run.
|
||||
|
||||
Usage: nostructlevel
|
||||
|
||||
Exists
|
||||
|
||||
Is a special tag without a validation function attached. It is used when a field
|
||||
is a Pointer, Interface or Invalid and you wish to validate that it exists.
|
||||
Example: want to ensure a bool exists if you define the bool as a pointer and
|
||||
use exists it will ensure there is a value; couldn't use required as it would
|
||||
fail when the bool was false. exists will fail is the value is a Pointer, Interface
|
||||
or Invalid and is nil.
|
||||
|
||||
Usage: exists
|
||||
|
||||
Omit Empty
|
||||
|
||||
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 #1
|
||||
|
||||
[][]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
|
||||
|
||||
Example #2
|
||||
|
||||
[][]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
|
||||
|
||||
Required
|
||||
|
||||
This validates that the value is not the data types default zero value.
|
||||
For numbers ensures value is not zero. For strings ensures value is
|
||||
not "". For slices, maps, pointers, interfaces, channels and functions
|
||||
ensures the value is not nil.
|
||||
|
||||
Usage: required
|
||||
|
||||
Length
|
||||
|
||||
For numbers, max will ensure that the value is
|
||||
equal to the parameter given. For strings, it checks that
|
||||
the string length is exactly that number of characters. For slices,
|
||||
arrays, and maps, validates the number of items.
|
||||
|
||||
Usage: len=10
|
||||
|
||||
Maximum
|
||||
|
||||
For numbers, max will ensure that the value is
|
||||
less than or equal to the parameter given. For strings, it checks
|
||||
that the string length is at most that number of characters. For
|
||||
slices, arrays, and maps, validates the number of items.
|
||||
|
||||
Usage: max=10
|
||||
|
||||
Mininum
|
||||
|
||||
For numbers, min will ensure that the value is
|
||||
greater or equal to the parameter given. For strings, it checks that
|
||||
the string length is at least that number of characters. For slices,
|
||||
arrays, and maps, validates the number of items.
|
||||
|
||||
Usage: min=10
|
||||
|
||||
Equals
|
||||
|
||||
For strings & numbers, eq will ensure that the value is
|
||||
equal to the parameter given. For slices, arrays, and maps,
|
||||
validates the number of items.
|
||||
|
||||
Usage: eq=10
|
||||
|
||||
Not Equal
|
||||
|
||||
For strings & numbers, eq will ensure that the value is not
|
||||
equal to the parameter given. For slices, arrays, and maps,
|
||||
validates the number of items.
|
||||
|
||||
Usage: eq=10
|
||||
|
||||
Greater Than
|
||||
|
||||
For numbers, this will ensure that the value is greater than the
|
||||
parameter given. For strings, it checks that the string length
|
||||
is greater than that number of characters. For slices, arrays
|
||||
and maps it validates the number of items.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: gt=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
|
||||
For time.Time ensures the time value is greater than time.Now.UTC().
|
||||
|
||||
Usage: gt
|
||||
|
||||
Greater Than or Equal
|
||||
|
||||
Same as 'min' above. Kept both to make terminology with 'len' easier.
|
||||
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: gte=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
|
||||
For time.Time ensures the time value is greater than or equal to time.Now.UTC().
|
||||
|
||||
Usage: gte
|
||||
|
||||
Less Than
|
||||
|
||||
For numbers, this will ensure that the value is less than the parameter given.
|
||||
For strings, it checks that the string length is less than that number of
|
||||
characters. For slices, arrays, and maps it validates the number of items.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: lt=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
For time.Time ensures the time value is less than time.Now.UTC().
|
||||
|
||||
Usage: lt
|
||||
|
||||
Less Than or Equal
|
||||
|
||||
Same as 'max' above. Kept both to make terminology with 'len' easier.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: lte=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
|
||||
For time.Time ensures the time value is less than or equal to time.Now.UTC().
|
||||
|
||||
Usage: lte
|
||||
|
||||
Field Equals Another Field
|
||||
|
||||
This will validate the field value against another fields value either within
|
||||
a struct or passed in field.
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on Password field using:
|
||||
Usage: eqfield=ConfirmPassword
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(password, confirmpassword, "eqfield")
|
||||
|
||||
Field Equals Another Field (relative)
|
||||
|
||||
This does the same as eqfield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: eqcsfield=InnerStructField.Field)
|
||||
|
||||
Field Does Not Equal Another Field
|
||||
|
||||
This will validate the field value against another fields value either within
|
||||
a struct or passed in field.
|
||||
|
||||
Examples:
|
||||
|
||||
// Confirm two colors are not the same:
|
||||
//
|
||||
// Validation on Color field:
|
||||
Usage: nefield=Color2
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(color1, color2, "nefield")
|
||||
|
||||
Field Does Not Equal Another Field (relative)
|
||||
|
||||
This does the same as nefield except that it validates the field provided
|
||||
relative to the top level struct.
|
||||
|
||||
Usage: necsfield=InnerStructField.Field
|
||||
|
||||
Field Greater Than Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(gtfield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "gtfield")
|
||||
|
||||
|
||||
Field Greater Than Another Relative Field
|
||||
|
||||
This does the same as gtfield except that it validates the field provided
|
||||
relative to the top level struct.
|
||||
|
||||
Usage: gtcsfield=InnerStructField.Field
|
||||
|
||||
Field Greater Than or Equal To Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(gtefield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "gtefield")
|
||||
|
||||
Field Greater Than or Equal To Another Relative Field
|
||||
|
||||
This does the same as gtefield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: gtecsfield=InnerStructField.Field
|
||||
|
||||
Less Than Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(ltfield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "ltfield")
|
||||
|
||||
Less Than Another Relative Field
|
||||
|
||||
This does the same as ltfield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: ltcsfield=InnerStructField.Field
|
||||
|
||||
Less Than or Equal To Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(ltefield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "ltefield")
|
||||
|
||||
Less Than or Equal To Another Relative Field
|
||||
|
||||
This does the same as ltefield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: ltecsfield=InnerStructField.Field
|
||||
|
||||
Alpha Only
|
||||
|
||||
This validates that a string value contains alpha characters only
|
||||
|
||||
Usage: alpha
|
||||
|
||||
Alphanumeric
|
||||
|
||||
This validates that a string value contains alphanumeric characters only
|
||||
|
||||
Usage: alphanum
|
||||
|
||||
Numeric
|
||||
|
||||
This validates that a string value contains a basic numeric value.
|
||||
basic excludes exponents etc...
|
||||
|
||||
Usage: numeric
|
||||
|
||||
Hexadecimal String
|
||||
|
||||
This validates that a string value contains a valid hexadecimal.
|
||||
|
||||
Usage: hexadecimal
|
||||
|
||||
Hexcolor String
|
||||
|
||||
This validates that a string value contains a valid hex color including
|
||||
hashtag (#)
|
||||
|
||||
Usage: hexcolor
|
||||
|
||||
RGB String
|
||||
|
||||
This validates that a string value contains a valid rgb color
|
||||
|
||||
Usage: rgb
|
||||
|
||||
RGBA String
|
||||
|
||||
This validates that a string value contains a valid rgba color
|
||||
|
||||
Usage: rgba
|
||||
|
||||
HSL String
|
||||
|
||||
This validates that a string value contains a valid hsl color
|
||||
|
||||
Usage: hsl
|
||||
|
||||
HSLA String
|
||||
|
||||
This validates that a string value contains a valid hsla color
|
||||
|
||||
Usage: hsla
|
||||
|
||||
E-mail String
|
||||
|
||||
This validates that a string value contains a valid email
|
||||
This may not conform to all possibilities of any rfc standard, but neither
|
||||
does any email provider accept all posibilities.
|
||||
|
||||
Usage: email
|
||||
|
||||
URL String
|
||||
|
||||
This validates that a string value contains a valid url
|
||||
This will accept any url the golang request uri accepts but must contain
|
||||
a schema for example http:// or rtmp://
|
||||
|
||||
Usage: url
|
||||
|
||||
URI String
|
||||
|
||||
This validates that a string value contains a valid uri
|
||||
This will accept any uri the golang request uri accepts
|
||||
|
||||
Usage: uri
|
||||
|
||||
Base64 String
|
||||
|
||||
This validates that a string value contains a valid base64 value.
|
||||
Although an empty string is valid base64 this will report an empty string
|
||||
as an error, if you wish to accept an empty string as valid you can use
|
||||
this with the omitempty tag.
|
||||
|
||||
Usage: base64
|
||||
|
||||
Contains
|
||||
|
||||
This validates that a string value contains the substring value.
|
||||
|
||||
Usage: contains=@
|
||||
|
||||
Contains Any
|
||||
|
||||
This validates that a string value contains any Unicode code points
|
||||
in the substring value.
|
||||
|
||||
Usage: containsany=!@#?
|
||||
|
||||
Contains Rune
|
||||
|
||||
This validates that a string value contains the supplied rune value.
|
||||
|
||||
Usage: containsrune=@
|
||||
|
||||
Excludes
|
||||
|
||||
This validates that a string value does not contain the substring value.
|
||||
|
||||
Usage: excludes=@
|
||||
|
||||
Excludes All
|
||||
|
||||
This validates that a string value does not contain any Unicode code
|
||||
points in the substring value.
|
||||
|
||||
Usage: excludesall=!@#?
|
||||
|
||||
Excludes Rune
|
||||
|
||||
This validates that a string value does not contain the supplied rune value.
|
||||
|
||||
Usage: excludesrune=@
|
||||
|
||||
International Standard Book Number
|
||||
|
||||
This validates that a string value contains a valid isbn10 or isbn13 value.
|
||||
|
||||
Usage: isbn
|
||||
|
||||
International Standard Book Number 10
|
||||
|
||||
This validates that a string value contains a valid isbn10 value.
|
||||
|
||||
Usage: isbn10
|
||||
|
||||
International Standard Book Number 13
|
||||
|
||||
This validates that a string value contains a valid isbn13 value.
|
||||
|
||||
Usage: isbn13
|
||||
|
||||
|
||||
Universally Unique Identifier UUID
|
||||
|
||||
This validates that a string value contains a valid UUID.
|
||||
|
||||
Usage: uuid
|
||||
|
||||
Universally Unique Identifier UUID v3
|
||||
|
||||
This validates that a string value contains a valid version 3 UUID.
|
||||
|
||||
Usage: uuid3
|
||||
|
||||
Universally Unique Identifier UUID v4
|
||||
|
||||
This validates that a string value contains a valid version 4 UUID.
|
||||
|
||||
Usage: uuid4
|
||||
|
||||
Universally Unique Identifier UUID v5
|
||||
|
||||
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
|
||||
|
||||
Printable ASCII
|
||||
|
||||
This validates that a string value contains only printable ASCII characters.
|
||||
NOTE: if the string is blank, this validates as true.
|
||||
|
||||
Usage: asciiprint
|
||||
|
||||
Multi-Byte Characters
|
||||
|
||||
This validates that a string value contains one or more multibyte characters.
|
||||
NOTE: if the string is blank, this validates as true.
|
||||
|
||||
Usage: multibyte
|
||||
|
||||
Data URL
|
||||
|
||||
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
|
||||
|
||||
Social Security Number SSN
|
||||
|
||||
This validates that a string value contains a valid U.S. Social Security Number.
|
||||
|
||||
Usage: ssn
|
||||
|
||||
Internet Protocol Address IP
|
||||
|
||||
This validates that a string value contains a valid IP Adress.
|
||||
|
||||
Usage: ip
|
||||
|
||||
Internet Protocol Address IPv4
|
||||
|
||||
This validates that a string value contains a valid v4 IP Adress.
|
||||
|
||||
Usage: ipv4
|
||||
|
||||
Internet Protocol Address IPv6
|
||||
|
||||
This validates that a string value contains a valid v6 IP Adress.
|
||||
|
||||
Usage: ipv6
|
||||
|
||||
Classless Inter-Domain Routing CIDR
|
||||
|
||||
This validates that a string value contains a valid CIDR Adress.
|
||||
|
||||
Usage: cidr
|
||||
|
||||
Classless Inter-Domain Routing CIDRv4
|
||||
|
||||
This validates that a string value contains a valid v4 CIDR Adress.
|
||||
|
||||
Usage: cidrv4
|
||||
|
||||
Classless Inter-Domain Routing CIDRv6
|
||||
|
||||
This validates that a string value contains a valid v6 CIDR Adress.
|
||||
|
||||
Usage: cidrv6
|
||||
|
||||
Media Access Control Address MAC
|
||||
|
||||
This validates that a string value contains a valid MAC Adress.
|
||||
|
||||
Usage: mac
|
||||
|
||||
Note: See Go's ParseMAC for accepted formats and types:
|
||||
|
||||
http://golang.org/src/net/mac.go?s=866:918#L29
|
||||
|
||||
Usage: mac
|
||||
|
||||
Alias Validators and Tags
|
||||
|
||||
NOTE: When returning an error, the tag returned in "FieldError" will be
|
||||
the alias tag unless the dive tag is part of the alias. Everything after the
|
||||
dive tag is not reported as the alias tag. Also, the "ActualTag" in the before
|
||||
case will be the actual tag within the alias that failed.
|
||||
|
||||
Here is a list of the current built in alias tags:
|
||||
|
||||
"iscolor"
|
||||
alias is "hexcolor|rgb|rgba|hsl|hsla" (Usage: iscolor)
|
||||
|
||||
Validator notes:
|
||||
|
||||
regex
|
||||
a regex validator won't be added because commas and = signs can be part
|
||||
of a regex which conflict with the validation definitions. Although
|
||||
workarounds can be made, they take away from using pure regex's.
|
||||
Furthermore it's quick and dirty but the regex's become harder to
|
||||
maintain and are not reusable, so it's as much a programming philosiphy
|
||||
as anything.
|
||||
|
||||
In place of this new validator functions should be created; a regex can
|
||||
be used within the validator function and even be precompiled for better
|
||||
efficiency within regexes.go.
|
||||
|
||||
And the best reason, you can submit a pull request and we can keep on
|
||||
adding to the validation library of this package!
|
||||
|
||||
Panics
|
||||
|
||||
This package panics when bad input is provided, this is by design, bad code like
|
||||
that should not make it to production.
|
||||
|
||||
type Test struct {
|
||||
TestField string `validate:"nonexistantfunction=1"`
|
||||
}
|
||||
|
||||
t := &Test{
|
||||
TestField: "Test"
|
||||
}
|
||||
|
||||
validate.Struct(t) // this will panic
|
||||
*/
|
||||
package validator
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// DbBackedUser User struct
|
||||
type DbBackedUser struct {
|
||||
Name sql.NullString `validate:"required"`
|
||||
Age sql.NullInt64 `validate:"required"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate := validator.New(config)
|
||||
|
||||
// register all sql.Null* types to use the ValidateValuer CustomTypeFunc
|
||||
validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{})
|
||||
|
||||
x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}}
|
||||
errs := validate.Struct(x)
|
||||
|
||||
if errs != nil {
|
||||
fmt.Printf("Errs:\n%+v\n", errs)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateValuer implements validator.CustomTypeFunc
|
||||
func ValidateValuer(field reflect.Value) interface{} {
|
||||
if valuer, ok := field.Interface().(driver.Valuer); ok {
|
||||
val, err := valuer.Value()
|
||||
if err == nil {
|
||||
return val
|
||||
}
|
||||
// handle the error how you want
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
sql "database/sql/driver"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// 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() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate = validator.New(config)
|
||||
|
||||
validateStruct()
|
||||
validateField()
|
||||
}
|
||||
|
||||
func validateStruct() {
|
||||
|
||||
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 ValidationErrors ( map[string]*FieldError )
|
||||
errs := validate.Struct(user)
|
||||
|
||||
if errs != nil {
|
||||
|
||||
fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag
|
||||
// Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag
|
||||
err := errs.(validator.ValidationErrors)["User.Addresses[0].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
|
||||
}
|
||||
|
||||
func validateField() {
|
||||
myEmail := "joeybloggs.gmail.com"
|
||||
|
||||
errs := validate.Field(myEmail, "required,email")
|
||||
|
||||
if errs != nil {
|
||||
fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag
|
||||
return
|
||||
}
|
||||
|
||||
// email ok, move on
|
||||
}
|
||||
|
||||
var validate2 *validator.Validate
|
||||
|
||||
type valuer struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (v valuer) Value() (sql.Value, error) {
|
||||
|
||||
if v.Name == "errorme" {
|
||||
return nil, errors.New("some kind of error")
|
||||
}
|
||||
|
||||
if v.Name == "blankme" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(v.Name) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return v.Name, nil
|
||||
}
|
||||
|
||||
// ValidateValuerType implements validator.CustomTypeFunc
|
||||
func ValidateValuerType(field reflect.Value) interface{} {
|
||||
if valuer, ok := field.Interface().(sql.Valuer); ok {
|
||||
val, err := valuer.Value()
|
||||
if err != nil {
|
||||
// handle the error how you want
|
||||
return nil
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main2() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate2 = validator.New(config)
|
||||
validate2.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
|
||||
|
||||
validateCustomFieldType()
|
||||
}
|
||||
|
||||
func validateCustomFieldType() {
|
||||
val := valuer{
|
||||
Name: "blankme",
|
||||
}
|
||||
|
||||
errs := validate2.Field(val, "required")
|
||||
if errs != nil {
|
||||
fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag
|
||||
return
|
||||
}
|
||||
|
||||
// all ok
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// User contains user information
|
||||
type User struct {
|
||||
FirstName string `json:"fname"`
|
||||
LastName string `json:"lname"`
|
||||
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() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate = validator.New(config)
|
||||
validate.RegisterStructValidation(UserStructLevelValidation, User{})
|
||||
|
||||
validateStruct()
|
||||
}
|
||||
|
||||
// UserStructLevelValidation contains custom struct level validations that don't always
|
||||
// make sense at the field validation level. For Example this function validates that either
|
||||
// FirstName or LastName exist; could have done that with a custom field validation but then
|
||||
// would have had to add it to both fields duplicating the logic + overhead, this way it's
|
||||
// only validated once.
|
||||
//
|
||||
// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way
|
||||
// hooks right into validator and you can combine with validation tags and still have a
|
||||
// common error output format.
|
||||
func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) {
|
||||
|
||||
user := structLevel.CurrentStruct.Interface().(User)
|
||||
|
||||
if len(user.FirstName) == 0 && len(user.LastName) == 0 {
|
||||
structLevel.ReportError(reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname")
|
||||
structLevel.ReportError(reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname")
|
||||
}
|
||||
|
||||
// plus can to more, even with different tag than "fnameorlname"
|
||||
}
|
||||
|
||||
func validateStruct() {
|
||||
|
||||
address := &Address{
|
||||
Street: "Eavesdown Docks",
|
||||
Planet: "Persphone",
|
||||
Phone: "none",
|
||||
City: "Unknown",
|
||||
}
|
||||
|
||||
user := &User{
|
||||
FirstName: "",
|
||||
LastName: "",
|
||||
Age: 45,
|
||||
Email: "Badger.Smith@gmail.com",
|
||||
FavouriteColor: "#000",
|
||||
Addresses: []*Address{address},
|
||||
}
|
||||
|
||||
// returns nil or ValidationErrors ( map[string]*FieldError )
|
||||
errs := validate.Struct(user)
|
||||
|
||||
if errs != nil {
|
||||
|
||||
fmt.Println(errs) // output: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag
|
||||
// Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag
|
||||
err := errs.(validator.ValidationErrors)["User.FirstName"]
|
||||
fmt.Println(err.Field) // output: FirstName
|
||||
fmt.Println(err.Tag) // output: fnameorlname
|
||||
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
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
func ExampleValidate_new() {
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validator.New(config)
|
||||
}
|
||||
|
||||
func ExampleValidate_field() {
|
||||
// This should be stored somewhere globally
|
||||
var validate *validator.Validate
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate = validator.New(config)
|
||||
|
||||
i := 0
|
||||
errs := validate.Field(i, "gt=1,lte=10")
|
||||
err := errs.(validator.ValidationErrors)[""]
|
||||
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
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate = validator.New(config)
|
||||
|
||||
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},
|
||||
}
|
||||
|
||||
errs := validate.Struct(user)
|
||||
for _, v := range errs.(validator.ValidationErrors) {
|
||||
fmt.Println(v.Field) // Phone
|
||||
fmt.Println(v.Tag) // required
|
||||
//... and so forth
|
||||
//Output:
|
||||
//Phone
|
||||
//required
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
+59
@@ -0,0 +1,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])|(?: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)
|
||||
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)
|
||||
)
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
dash = "-"
|
||||
blank = ""
|
||||
namespaceSeparator = "."
|
||||
leftBracket = "["
|
||||
rightBracket = "]"
|
||||
restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
|
||||
restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
|
||||
restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
|
||||
)
|
||||
|
||||
var (
|
||||
restrictedTags = map[string]*struct{}{
|
||||
diveTag: emptyStructPtr,
|
||||
existsTag: emptyStructPtr,
|
||||
structOnlyTag: emptyStructPtr,
|
||||
omitempty: emptyStructPtr,
|
||||
skipValidationTag: emptyStructPtr,
|
||||
utf8HexComma: emptyStructPtr,
|
||||
utf8Pipe: emptyStructPtr,
|
||||
noStructLevelTag: emptyStructPtr,
|
||||
}
|
||||
)
|
||||
|
||||
// ExtractType gets the actual underlying type of field value.
|
||||
// It will dive into pointers, customTypes and return you the
|
||||
// underlying value and it's kind.
|
||||
// it is exposed for use within you Custom Functions
|
||||
func (v *Validate) ExtractType(current reflect.Value) (reflect.Value, reflect.Kind) {
|
||||
|
||||
switch current.Kind() {
|
||||
case reflect.Ptr:
|
||||
|
||||
if current.IsNil() {
|
||||
return current, reflect.Ptr
|
||||
}
|
||||
|
||||
return v.ExtractType(current.Elem())
|
||||
|
||||
case reflect.Interface:
|
||||
|
||||
if current.IsNil() {
|
||||
return current, reflect.Interface
|
||||
}
|
||||
|
||||
return v.ExtractType(current.Elem())
|
||||
|
||||
case reflect.Invalid:
|
||||
return current, reflect.Invalid
|
||||
|
||||
default:
|
||||
|
||||
if v.hasCustomFuncs {
|
||||
// fmt.Println("Type", current.Type())
|
||||
if fn, ok := v.customTypeFuncs[current.Type()]; ok {
|
||||
|
||||
// fmt.Println("OK")
|
||||
|
||||
return v.ExtractType(reflect.ValueOf(fn(current)))
|
||||
}
|
||||
|
||||
// fmt.Println("NOT OK")
|
||||
}
|
||||
|
||||
return current, current.Kind()
|
||||
}
|
||||
}
|
||||
|
||||
// GetStructFieldOK traverses a struct to retrieve a specific field denoted by the provided namespace and
|
||||
// returns the field, field kind and whether is was successful in retrieving the field at all.
|
||||
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
|
||||
// could not be retrived because it didnt exist.
|
||||
func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) {
|
||||
|
||||
current, kind := v.ExtractType(current)
|
||||
|
||||
if kind == reflect.Invalid {
|
||||
return current, kind, false
|
||||
}
|
||||
|
||||
if namespace == blank {
|
||||
return current, kind, true
|
||||
}
|
||||
|
||||
switch kind {
|
||||
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
|
||||
return current, kind, false
|
||||
|
||||
case reflect.Struct:
|
||||
|
||||
typ := current.Type()
|
||||
fld := namespace
|
||||
ns := namespace
|
||||
|
||||
if typ != timeType && typ != timePtrType {
|
||||
|
||||
idx := strings.Index(namespace, namespaceSeparator)
|
||||
|
||||
if idx != -1 {
|
||||
fld = namespace[:idx]
|
||||
ns = namespace[idx+1:]
|
||||
} else {
|
||||
ns = blank
|
||||
idx = len(namespace)
|
||||
}
|
||||
|
||||
bracketIdx := strings.Index(fld, leftBracket)
|
||||
if bracketIdx != -1 {
|
||||
fld = fld[:bracketIdx]
|
||||
|
||||
ns = namespace[bracketIdx:]
|
||||
}
|
||||
|
||||
current = current.FieldByName(fld)
|
||||
|
||||
return v.GetStructFieldOK(current, ns)
|
||||
}
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
idx := strings.Index(namespace, leftBracket)
|
||||
idx2 := strings.Index(namespace, rightBracket)
|
||||
|
||||
arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2])
|
||||
|
||||
if arrIdx >= current.Len() {
|
||||
return current, kind, false
|
||||
}
|
||||
|
||||
startIdx := idx2 + 1
|
||||
|
||||
if startIdx < len(namespace) {
|
||||
if namespace[startIdx:startIdx+1] == namespaceSeparator {
|
||||
startIdx++
|
||||
}
|
||||
}
|
||||
|
||||
return v.GetStructFieldOK(current.Index(arrIdx), namespace[startIdx:])
|
||||
|
||||
case reflect.Map:
|
||||
idx := strings.Index(namespace, leftBracket) + 1
|
||||
idx2 := strings.Index(namespace, rightBracket)
|
||||
|
||||
endIdx := idx2
|
||||
|
||||
if endIdx+1 < len(namespace) {
|
||||
if namespace[endIdx+1:endIdx+2] == namespaceSeparator {
|
||||
endIdx++
|
||||
}
|
||||
}
|
||||
|
||||
key := namespace[idx:idx2]
|
||||
|
||||
switch current.Type().Key().Kind() {
|
||||
case reflect.Int:
|
||||
i, _ := strconv.Atoi(key)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
|
||||
case reflect.Int8:
|
||||
i, _ := strconv.ParseInt(key, 10, 8)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int8(i))), namespace[endIdx+1:])
|
||||
case reflect.Int16:
|
||||
i, _ := strconv.ParseInt(key, 10, 16)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int16(i))), namespace[endIdx+1:])
|
||||
case reflect.Int32:
|
||||
i, _ := strconv.ParseInt(key, 10, 32)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int32(i))), namespace[endIdx+1:])
|
||||
case reflect.Int64:
|
||||
i, _ := strconv.ParseInt(key, 10, 64)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
|
||||
case reflect.Uint:
|
||||
i, _ := strconv.ParseUint(key, 10, 0)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint8:
|
||||
i, _ := strconv.ParseUint(key, 10, 8)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint8(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint16:
|
||||
i, _ := strconv.ParseUint(key, 10, 16)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint16(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint32:
|
||||
i, _ := strconv.ParseUint(key, 10, 32)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint32(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint64:
|
||||
i, _ := strconv.ParseUint(key, 10, 64)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
|
||||
case reflect.Float32:
|
||||
f, _ := strconv.ParseFloat(key, 32)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(float32(f))), namespace[endIdx+1:])
|
||||
case reflect.Float64:
|
||||
f, _ := strconv.ParseFloat(key, 64)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(f)), namespace[endIdx+1:])
|
||||
case reflect.Bool:
|
||||
b, _ := strconv.ParseBool(key)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(b)), namespace[endIdx+1:])
|
||||
|
||||
// reflect.Type = string
|
||||
default:
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(key)), namespace[endIdx+1:])
|
||||
}
|
||||
}
|
||||
|
||||
// if got here there was more namespace, cannot go any deeper
|
||||
panic("Invalid field namespace")
|
||||
}
|
||||
|
||||
// asInt retuns the parameter as a int64
|
||||
// or panics if it can't convert
|
||||
func asInt(param string) int64 {
|
||||
|
||||
i, err := strconv.ParseInt(param, 0, 64)
|
||||
panicIf(err)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// asUint returns the parameter as a uint64
|
||||
// or panics if it can't convert
|
||||
func asUint(param string) uint64 {
|
||||
|
||||
i, err := strconv.ParseUint(param, 0, 64)
|
||||
panicIf(err)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// asFloat returns the parameter as a float64
|
||||
// or panics if it can't convert
|
||||
func asFloat(param string) float64 {
|
||||
|
||||
i, err := strconv.ParseFloat(param, 64)
|
||||
panicIf(err)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func panicIf(err error) {
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validate) parseStruct(current reflect.Value, sName string) *cachedStruct {
|
||||
|
||||
typ := current.Type()
|
||||
s := &cachedStruct{Name: sName, fields: map[int]cachedField{}}
|
||||
|
||||
numFields := current.NumField()
|
||||
|
||||
var fld reflect.StructField
|
||||
var tag string
|
||||
var customName string
|
||||
|
||||
for i := 0; i < numFields; i++ {
|
||||
|
||||
fld = typ.Field(i)
|
||||
|
||||
if fld.PkgPath != blank {
|
||||
continue
|
||||
}
|
||||
|
||||
tag = fld.Tag.Get(v.tagName)
|
||||
|
||||
if tag == skipValidationTag {
|
||||
continue
|
||||
}
|
||||
|
||||
customName = fld.Name
|
||||
if v.fieldNameTag != blank {
|
||||
|
||||
name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0]
|
||||
|
||||
// dash check is for json "-" (aka skipValidationTag) means don't output in json
|
||||
if name != "" && name != skipValidationTag {
|
||||
customName = name
|
||||
}
|
||||
}
|
||||
|
||||
cTag, ok := v.tagCache.Get(tag)
|
||||
if !ok {
|
||||
cTag = v.parseTags(tag, fld.Name)
|
||||
}
|
||||
|
||||
s.fields[i] = cachedField{Idx: i, Name: fld.Name, AltName: customName, CachedTag: cTag}
|
||||
}
|
||||
|
||||
v.structCache.Set(typ, s)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (v *Validate) parseTags(tag, fieldName string) *cachedTag {
|
||||
|
||||
cTag := &cachedTag{tag: tag}
|
||||
|
||||
v.parseTagsRecursive(cTag, tag, fieldName, blank, false)
|
||||
|
||||
v.tagCache.Set(tag, cTag)
|
||||
|
||||
return cTag
|
||||
}
|
||||
|
||||
func (v *Validate) parseTagsRecursive(cTag *cachedTag, tag, fieldName, alias string, isAlias bool) bool {
|
||||
|
||||
if tag == blank {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, t := range strings.Split(tag, tagSeparator) {
|
||||
|
||||
if v.hasAliasValidators {
|
||||
// check map for alias and process new tags, otherwise process as usual
|
||||
if tagsVal, ok := v.aliasValidators[t]; ok {
|
||||
|
||||
leave := v.parseTagsRecursive(cTag, tagsVal, fieldName, t, true)
|
||||
|
||||
if leave {
|
||||
return leave
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch t {
|
||||
|
||||
case diveTag:
|
||||
cTag.diveTag = tag
|
||||
tVals := &tagVals{tagVals: [][]string{{t}}}
|
||||
cTag.tags = append(cTag.tags, tVals)
|
||||
return true
|
||||
|
||||
case omitempty:
|
||||
cTag.isOmitEmpty = true
|
||||
|
||||
case structOnlyTag:
|
||||
cTag.isStructOnly = true
|
||||
|
||||
case noStructLevelTag:
|
||||
cTag.isNoStructLevel = true
|
||||
}
|
||||
|
||||
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
|
||||
orVals := strings.Split(t, orSeparator)
|
||||
tagVal := &tagVals{isAlias: isAlias, isOrVal: len(orVals) > 1, tagVals: make([][]string, len(orVals))}
|
||||
cTag.tags = append(cTag.tags, tagVal)
|
||||
|
||||
var key string
|
||||
var param string
|
||||
|
||||
for i, val := range orVals {
|
||||
vals := strings.SplitN(val, tagKeySeparator, 2)
|
||||
key = vals[0]
|
||||
|
||||
tagVal.tag = key
|
||||
|
||||
if isAlias {
|
||||
tagVal.tag = alias
|
||||
}
|
||||
|
||||
if key == blank {
|
||||
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
|
||||
}
|
||||
|
||||
if len(vals) > 1 {
|
||||
param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
|
||||
}
|
||||
|
||||
tagVal.tagVals[i] = []string{key, param}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
+797
@@ -0,0 +1,797 @@
|
||||
/**
|
||||
* Package validator
|
||||
*
|
||||
* MISC:
|
||||
* - anonymous structs - they don't have names so expect the Struct name within StructErrors to be blank
|
||||
*
|
||||
*/
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
utf8HexComma = "0x2C"
|
||||
utf8Pipe = "0x7C"
|
||||
tagSeparator = ","
|
||||
orSeparator = "|"
|
||||
tagKeySeparator = "="
|
||||
structOnlyTag = "structonly"
|
||||
noStructLevelTag = "nostructlevel"
|
||||
omitempty = "omitempty"
|
||||
skipValidationTag = "-"
|
||||
diveTag = "dive"
|
||||
existsTag = "exists"
|
||||
fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag"
|
||||
arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket
|
||||
mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket
|
||||
invalidValidation = "Invalid validation tag on field %s"
|
||||
undefinedValidation = "Undefined validation function on field %s"
|
||||
validatorNotInitialized = "Validator instance not initialized"
|
||||
fieldNameRequired = "Field Name Required"
|
||||
tagRequired = "Tag Required"
|
||||
)
|
||||
|
||||
var (
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
timePtrType = reflect.TypeOf(&time.Time{})
|
||||
emptyStructPtr = new(struct{})
|
||||
)
|
||||
|
||||
// StructLevel contains all of the information and helper methods
|
||||
// for reporting errors during struct level validation
|
||||
type StructLevel struct {
|
||||
TopStruct reflect.Value
|
||||
CurrentStruct reflect.Value
|
||||
errPrefix string
|
||||
nsPrefix string
|
||||
errs ValidationErrors
|
||||
v *Validate
|
||||
}
|
||||
|
||||
// ReportValidationErrors accepts the key relative to the top level struct and validatin errors.
|
||||
// Example: had a triple nested struct User, ContactInfo, Country and ran errs := validate.Struct(country)
|
||||
// from within a User struct level validation would call this method like so:
|
||||
// ReportValidationErrors("ContactInfo.", errs)
|
||||
// NOTE: relativeKey can contain both the Field Relative and Custom name relative paths
|
||||
// i.e. ReportValidationErrors("ContactInfo.|cInfo", errs) where cInfo represents say the JSON name of
|
||||
// the relative path; this will be split into 2 variables in the next valiator version.
|
||||
func (sl *StructLevel) ReportValidationErrors(relativeKey string, errs ValidationErrors) {
|
||||
for _, e := range errs {
|
||||
|
||||
idx := strings.Index(relativeKey, "|")
|
||||
var rel string
|
||||
var cRel string
|
||||
|
||||
if idx != -1 {
|
||||
rel = relativeKey[:idx]
|
||||
cRel = relativeKey[idx+1:]
|
||||
} else {
|
||||
rel = relativeKey
|
||||
}
|
||||
|
||||
key := sl.errPrefix + rel + e.Field
|
||||
|
||||
e.FieldNamespace = key
|
||||
e.NameNamespace = sl.nsPrefix + cRel + e.Name
|
||||
|
||||
sl.errs[key] = e
|
||||
}
|
||||
}
|
||||
|
||||
// ReportError reports an error just by passing the field and tag information
|
||||
// NOTE: tag can be an existing validation tag or just something you make up
|
||||
// and precess on the flip side it's up to you.
|
||||
func (sl *StructLevel) ReportError(field reflect.Value, fieldName string, customName string, tag string) {
|
||||
|
||||
field, kind := sl.v.ExtractType(field)
|
||||
|
||||
if fieldName == blank {
|
||||
panic(fieldNameRequired)
|
||||
}
|
||||
|
||||
if customName == blank {
|
||||
customName = fieldName
|
||||
}
|
||||
|
||||
if tag == blank {
|
||||
panic(tagRequired)
|
||||
}
|
||||
|
||||
ns := sl.errPrefix + fieldName
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
sl.errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: sl.nsPrefix + customName,
|
||||
Name: customName,
|
||||
Field: fieldName,
|
||||
Tag: tag,
|
||||
ActualTag: tag,
|
||||
Param: blank,
|
||||
Kind: kind,
|
||||
}
|
||||
default:
|
||||
sl.errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: sl.nsPrefix + customName,
|
||||
Name: customName,
|
||||
Field: fieldName,
|
||||
Tag: tag,
|
||||
ActualTag: tag,
|
||||
Param: blank,
|
||||
Value: field.Interface(),
|
||||
Kind: kind,
|
||||
Type: field.Type(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate contains the validator settings passed in using the Config struct
|
||||
type Validate struct {
|
||||
tagName string
|
||||
fieldNameTag string
|
||||
validationFuncs map[string]Func
|
||||
structLevelFuncs map[reflect.Type]StructLevelFunc
|
||||
customTypeFuncs map[reflect.Type]CustomTypeFunc
|
||||
aliasValidators map[string]string
|
||||
hasCustomFuncs bool
|
||||
hasAliasValidators bool
|
||||
hasStructLevelFuncs bool
|
||||
tagCache *tagCacheMap
|
||||
structCache *structCacheMap
|
||||
errsPool *sync.Pool
|
||||
}
|
||||
|
||||
func (v *Validate) initCheck() {
|
||||
if v == nil {
|
||||
panic(validatorNotInitialized)
|
||||
}
|
||||
}
|
||||
|
||||
// Config contains the options that a Validator instance will use.
|
||||
// It is passed to the New() function
|
||||
type Config struct {
|
||||
TagName string
|
||||
FieldNameTag string
|
||||
}
|
||||
|
||||
// CustomTypeFunc allows for overriding or adding custom field type handler functions
|
||||
// field = field value of the type to return a value to be validated
|
||||
// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
|
||||
type CustomTypeFunc func(field reflect.Value) interface{}
|
||||
|
||||
// Func accepts all values needed for file and cross field validation
|
||||
// v = validator instance, needed but some built in functions for it's custom types
|
||||
// topStruct = top level struct when validating by struct otherwise nil
|
||||
// currentStruct = current level struct when validating by struct otherwise optional comparison value
|
||||
// field = field value for validation
|
||||
// param = parameter used in validation i.e. gt=0 param would be 0
|
||||
type Func func(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool
|
||||
|
||||
// StructLevelFunc accepts all values needed for struct level validation
|
||||
type StructLevelFunc func(v *Validate, structLevel *StructLevel)
|
||||
|
||||
// ValidationErrors is a type of map[string]*FieldError
|
||||
// it exists to allow for multiple errors to be passed from this library
|
||||
// and yet still subscribe to the error interface
|
||||
type ValidationErrors map[string]*FieldError
|
||||
|
||||
// Error is intended for use in development + debugging and not intended to be a production error message.
|
||||
// It allows ValidationErrors to subscribe to the Error interface.
|
||||
// All information to create an error message specific to your application is contained within
|
||||
// the FieldError found within the ValidationErrors map
|
||||
func (ve ValidationErrors) Error() string {
|
||||
|
||||
buff := bytes.NewBufferString(blank)
|
||||
|
||||
for key, err := range ve {
|
||||
buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag))
|
||||
buff.WriteString("\n")
|
||||
}
|
||||
|
||||
return strings.TrimSpace(buff.String())
|
||||
}
|
||||
|
||||
// FieldError contains a single field's validation error along
|
||||
// with other properties that may be needed for error message creation
|
||||
type FieldError struct {
|
||||
FieldNamespace string
|
||||
NameNamespace string
|
||||
Field string
|
||||
Name string
|
||||
Tag string
|
||||
ActualTag string
|
||||
Kind reflect.Kind
|
||||
Type reflect.Type
|
||||
Param string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// New creates a new Validate instance for use.
|
||||
func New(config *Config) *Validate {
|
||||
|
||||
v := &Validate{
|
||||
tagName: config.TagName,
|
||||
fieldNameTag: config.FieldNameTag,
|
||||
tagCache: &tagCacheMap{m: map[string]*cachedTag{}},
|
||||
structCache: &structCacheMap{m: map[reflect.Type]*cachedStruct{}},
|
||||
errsPool: &sync.Pool{New: func() interface{} {
|
||||
return ValidationErrors{}
|
||||
}}}
|
||||
|
||||
if len(v.aliasValidators) == 0 {
|
||||
// must copy alias validators for separate validations to be used in each validator instance
|
||||
v.aliasValidators = map[string]string{}
|
||||
for k, val := range bakedInAliasValidators {
|
||||
v.RegisterAliasValidation(k, val)
|
||||
}
|
||||
}
|
||||
|
||||
if len(v.validationFuncs) == 0 {
|
||||
// must copy validators for separate validations to be used in each instance
|
||||
v.validationFuncs = map[string]Func{}
|
||||
for k, val := range bakedInValidators {
|
||||
v.RegisterValidation(k, val)
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// RegisterStructValidation registers a StructLevelFunc against a number of types
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{}) {
|
||||
v.initCheck()
|
||||
|
||||
if v.structLevelFuncs == nil {
|
||||
v.structLevelFuncs = map[reflect.Type]StructLevelFunc{}
|
||||
}
|
||||
|
||||
for _, t := range types {
|
||||
v.structLevelFuncs[reflect.TypeOf(t)] = fn
|
||||
}
|
||||
|
||||
v.hasStructLevelFuncs = true
|
||||
}
|
||||
|
||||
// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
|
||||
// NOTE: if the key already exists, the previous validation function will be replaced.
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
func (v *Validate) RegisterValidation(key string, fn Func) error {
|
||||
v.initCheck()
|
||||
|
||||
if key == blank {
|
||||
return errors.New("Function Key cannot be empty")
|
||||
}
|
||||
|
||||
if fn == nil {
|
||||
return errors.New("Function cannot be empty")
|
||||
}
|
||||
|
||||
_, ok := restrictedTags[key]
|
||||
|
||||
if ok || strings.ContainsAny(key, restrictedTagChars) {
|
||||
panic(fmt.Sprintf(restrictedTagErr, key))
|
||||
}
|
||||
|
||||
v.validationFuncs[key] = fn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) {
|
||||
v.initCheck()
|
||||
|
||||
if v.customTypeFuncs == nil {
|
||||
v.customTypeFuncs = map[reflect.Type]CustomTypeFunc{}
|
||||
}
|
||||
|
||||
for _, t := range types {
|
||||
v.customTypeFuncs[reflect.TypeOf(t)] = fn
|
||||
}
|
||||
|
||||
v.hasCustomFuncs = true
|
||||
}
|
||||
|
||||
// RegisterAliasValidation registers a mapping of a single validationstag that
|
||||
// defines a common or complex set of validation(s) to simplify adding validation
|
||||
// to structs. NOTE: when returning an error the tag returned in FieldError will be
|
||||
// the alias tag unless the dive tag is part of the alias; everything after the
|
||||
// dive tag is not reported as the alias tag. Also the ActualTag in the before case
|
||||
// will be the actual tag within the alias that failed.
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
func (v *Validate) RegisterAliasValidation(alias, tags string) {
|
||||
v.initCheck()
|
||||
|
||||
_, ok := restrictedTags[alias]
|
||||
|
||||
if ok || strings.ContainsAny(alias, restrictedTagChars) {
|
||||
panic(fmt.Sprintf(restrictedAliasErr, alias))
|
||||
}
|
||||
|
||||
v.aliasValidators[alias] = tags
|
||||
v.hasAliasValidators = true
|
||||
}
|
||||
|
||||
// Field validates a single field using tag style validation and returns nil or ValidationErrors as type error.
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also
|
||||
// validate Array, Slice and maps fields which may contain more than one error
|
||||
func (v *Validate) Field(field interface{}, tag string) error {
|
||||
v.initCheck()
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
fieldVal := reflect.ValueOf(field)
|
||||
|
||||
v.traverseField(fieldVal, fieldVal, fieldVal, blank, blank, errs, false, tag, blank, blank, false, false, nil, nil)
|
||||
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// FieldWithValue validates a single field, against another fields value using tag style validation and returns nil or ValidationErrors.
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also
|
||||
// validate Array, Slice and maps fields which may contain more than one error
|
||||
func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) error {
|
||||
v.initCheck()
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
topVal := reflect.ValueOf(val)
|
||||
|
||||
v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, blank, errs, false, tag, blank, blank, false, false, nil, nil)
|
||||
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// StructPartial validates the fields passed in only, ignoring all others.
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
func (v *Validate) StructPartial(current interface{}, fields ...string) error {
|
||||
v.initCheck()
|
||||
|
||||
sv, _ := v.ExtractType(reflect.ValueOf(current))
|
||||
name := sv.Type().Name()
|
||||
m := map[string]*struct{}{}
|
||||
|
||||
if fields != nil {
|
||||
for _, k := range fields {
|
||||
|
||||
flds := strings.Split(k, namespaceSeparator)
|
||||
if len(flds) > 0 {
|
||||
|
||||
key := name + namespaceSeparator
|
||||
for _, s := range flds {
|
||||
|
||||
idx := strings.Index(s, leftBracket)
|
||||
|
||||
if idx != -1 {
|
||||
for idx != -1 {
|
||||
key += s[:idx]
|
||||
m[key] = emptyStructPtr
|
||||
|
||||
idx2 := strings.Index(s, rightBracket)
|
||||
idx2++
|
||||
key += s[idx:idx2]
|
||||
m[key] = emptyStructPtr
|
||||
s = s[idx2:]
|
||||
idx = strings.Index(s, leftBracket)
|
||||
}
|
||||
} else {
|
||||
|
||||
key += s
|
||||
m[key] = emptyStructPtr
|
||||
}
|
||||
|
||||
key += namespaceSeparator
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
|
||||
v.tranverseStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, false, m, false)
|
||||
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// StructExcept validates all fields except the ones passed in.
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
func (v *Validate) StructExcept(current interface{}, fields ...string) error {
|
||||
v.initCheck()
|
||||
|
||||
sv, _ := v.ExtractType(reflect.ValueOf(current))
|
||||
name := sv.Type().Name()
|
||||
m := map[string]*struct{}{}
|
||||
|
||||
for _, key := range fields {
|
||||
m[name+namespaceSeparator+key] = emptyStructPtr
|
||||
}
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
|
||||
v.tranverseStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, true, m, false)
|
||||
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
|
||||
// it returns nil or ValidationErrors as error.
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
func (v *Validate) Struct(current interface{}) error {
|
||||
v.initCheck()
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
sv := reflect.ValueOf(current)
|
||||
|
||||
v.tranverseStruct(sv, sv, sv, blank, blank, errs, true, false, false, nil, false)
|
||||
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// tranverseStruct traverses a structs fields and then passes them to be validated by traverseField
|
||||
func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]*struct{}, isStructOnly bool) {
|
||||
|
||||
if current.Kind() == reflect.Ptr && !current.IsNil() {
|
||||
current = current.Elem()
|
||||
}
|
||||
|
||||
if current.Kind() != reflect.Struct && current.Kind() != reflect.Interface {
|
||||
panic("value passed for validation is not a struct")
|
||||
}
|
||||
|
||||
// var ok bool
|
||||
typ := current.Type()
|
||||
|
||||
sName := typ.Name()
|
||||
|
||||
if useStructName {
|
||||
errPrefix += sName + namespaceSeparator
|
||||
|
||||
if v.fieldNameTag != blank {
|
||||
nsPrefix += sName + namespaceSeparator
|
||||
}
|
||||
}
|
||||
|
||||
// structonly tag present don't tranverseFields
|
||||
// but must still check and run below struct level validation
|
||||
// if present
|
||||
if !isStructOnly {
|
||||
|
||||
var fld reflect.StructField
|
||||
|
||||
// is anonymous struct, cannot parse or cache as
|
||||
// it has no name to index by
|
||||
if sName == blank {
|
||||
|
||||
var customName string
|
||||
var ok bool
|
||||
numFields := current.NumField()
|
||||
|
||||
for i := 0; i < numFields; i++ {
|
||||
|
||||
fld = typ.Field(i)
|
||||
|
||||
if fld.PkgPath != blank {
|
||||
continue
|
||||
}
|
||||
|
||||
if partial {
|
||||
|
||||
_, ok = includeExclude[errPrefix+fld.Name]
|
||||
|
||||
if (ok && exclude) || (!ok && !exclude) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
customName = fld.Name
|
||||
|
||||
if v.fieldNameTag != blank {
|
||||
|
||||
name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0]
|
||||
|
||||
// dash check is for json "-" means don't output in json
|
||||
if name != blank && name != dash {
|
||||
customName = name
|
||||
}
|
||||
}
|
||||
|
||||
v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, nsPrefix, errs, true, fld.Tag.Get(v.tagName), fld.Name, customName, partial, exclude, includeExclude, nil)
|
||||
}
|
||||
} else {
|
||||
s, ok := v.structCache.Get(typ)
|
||||
if !ok {
|
||||
s = v.parseStruct(current, sName)
|
||||
}
|
||||
|
||||
for i, f := range s.fields {
|
||||
|
||||
if partial {
|
||||
|
||||
_, ok = includeExclude[errPrefix+f.Name]
|
||||
|
||||
if (ok && exclude) || (!ok && !exclude) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
fld = typ.Field(i)
|
||||
|
||||
v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, nsPrefix, errs, true, f.CachedTag.tag, fld.Name, f.AltName, partial, exclude, includeExclude, f.CachedTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if any struct level validations, after all field validations already checked.
|
||||
if v.hasStructLevelFuncs {
|
||||
if fn, ok := v.structLevelFuncs[current.Type()]; ok {
|
||||
fn(v, &StructLevel{v: v, TopStruct: topStruct, CurrentStruct: current, errPrefix: errPrefix, nsPrefix: nsPrefix, errs: errs})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options
|
||||
func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, isStructField bool, tag, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) {
|
||||
|
||||
if tag == skipValidationTag {
|
||||
return
|
||||
}
|
||||
|
||||
if cTag == nil {
|
||||
var isCached bool
|
||||
cTag, isCached = v.tagCache.Get(tag)
|
||||
|
||||
if !isCached {
|
||||
cTag = v.parseTags(tag, name)
|
||||
}
|
||||
}
|
||||
|
||||
current, kind := v.ExtractType(current)
|
||||
var typ reflect.Type
|
||||
|
||||
switch kind {
|
||||
case reflect.Ptr, reflect.Interface, reflect.Invalid:
|
||||
if cTag.isOmitEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
if tag != blank {
|
||||
|
||||
ns := errPrefix + name
|
||||
|
||||
if kind == reflect.Invalid {
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + customName,
|
||||
Name: customName,
|
||||
Field: name,
|
||||
Tag: cTag.tags[0].tag,
|
||||
ActualTag: cTag.tags[0].tagVals[0][0],
|
||||
Param: cTag.tags[0].tagVals[0][1],
|
||||
Kind: kind,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + customName,
|
||||
Name: customName,
|
||||
Field: name,
|
||||
Tag: cTag.tags[0].tag,
|
||||
ActualTag: cTag.tags[0].tagVals[0][0],
|
||||
Param: cTag.tags[0].tagVals[0][1],
|
||||
Value: current.Interface(),
|
||||
Kind: kind,
|
||||
Type: current.Type(),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// if we get here tag length is zero and we can leave
|
||||
if kind == reflect.Invalid {
|
||||
return
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
typ = current.Type()
|
||||
|
||||
if typ != timeType {
|
||||
|
||||
if cTag.isNoStructLevel {
|
||||
return
|
||||
}
|
||||
|
||||
v.tranverseStruct(topStruct, current, current, errPrefix+name+namespaceSeparator, nsPrefix+customName+namespaceSeparator, errs, false, partial, exclude, includeExclude, cTag.isStructOnly)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if tag == blank {
|
||||
return
|
||||
}
|
||||
|
||||
typ = current.Type()
|
||||
|
||||
var dive bool
|
||||
var diveSubTag string
|
||||
|
||||
for _, valTag := range cTag.tags {
|
||||
|
||||
if valTag.tagVals[0][0] == existsTag {
|
||||
continue
|
||||
}
|
||||
|
||||
if valTag.tagVals[0][0] == diveTag {
|
||||
dive = true
|
||||
diveSubTag = strings.TrimLeft(strings.SplitN(cTag.diveTag, diveTag, 2)[1], ",")
|
||||
break
|
||||
}
|
||||
|
||||
if valTag.tagVals[0][0] == omitempty {
|
||||
|
||||
if !HasValue(v, topStruct, currentStruct, current, typ, kind, blank) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, nsPrefix, errs, valTag, name, customName) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if dive {
|
||||
// traverse slice or map here
|
||||
// or panic ;)
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
v.traverseSlice(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil)
|
||||
case reflect.Map:
|
||||
v.traverseMap(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, diveSubTag, name, customName, partial, exclude, includeExclude, nil)
|
||||
default:
|
||||
// throw error, if not a slice or map then should not have gotten here
|
||||
// bad dive tag
|
||||
panic("dive error! can't dive on a non slice or map")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// traverseSlice traverses a Slice or Array's elements and passes them to traverseField for validation
|
||||
func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, tag, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) {
|
||||
|
||||
for i := 0; i < current.Len(); i++ {
|
||||
v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, nsPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i), fmt.Sprintf(arrayIndexFieldName, customName, i), partial, exclude, includeExclude, cTag)
|
||||
}
|
||||
}
|
||||
|
||||
// traverseMap traverses a map's elements and passes them to traverseField for validation
|
||||
func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, tag, name, customName string, partial bool, exclude bool, includeExclude map[string]*struct{}, cTag *cachedTag) {
|
||||
|
||||
for _, key := range current.MapKeys() {
|
||||
v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, nsPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface()), fmt.Sprintf(mapIndexFieldName, customName, key.Interface()), partial, exclude, includeExclude, cTag)
|
||||
}
|
||||
}
|
||||
|
||||
// validateField validates a field based on the provided tag's key and param values and returns true if there is an error or false if all ok
|
||||
func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, nsPrefix string, errs ValidationErrors, valTag *tagVals, name, customName string) bool {
|
||||
|
||||
var valFunc Func
|
||||
var ok bool
|
||||
|
||||
if valTag.isOrVal {
|
||||
|
||||
errTag := blank
|
||||
|
||||
for _, val := range valTag.tagVals {
|
||||
|
||||
valFunc, ok = v.validationFuncs[val[0]]
|
||||
if !ok {
|
||||
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
|
||||
}
|
||||
|
||||
if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, val[1]) {
|
||||
return false
|
||||
}
|
||||
|
||||
errTag += orSeparator + val[0]
|
||||
}
|
||||
|
||||
ns := errPrefix + name
|
||||
|
||||
if valTag.isAlias {
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + customName,
|
||||
Name: customName,
|
||||
Field: name,
|
||||
Tag: valTag.tag,
|
||||
ActualTag: errTag[1:],
|
||||
Value: current.Interface(),
|
||||
Type: currentType,
|
||||
Kind: currentKind,
|
||||
}
|
||||
} else {
|
||||
errs[errPrefix+name] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + customName,
|
||||
Name: customName,
|
||||
Field: name,
|
||||
Tag: errTag[1:],
|
||||
ActualTag: errTag[1:],
|
||||
Value: current.Interface(),
|
||||
Type: currentType,
|
||||
Kind: currentKind,
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
valFunc, ok = v.validationFuncs[valTag.tagVals[0][0]]
|
||||
if !ok {
|
||||
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
|
||||
}
|
||||
|
||||
if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, valTag.tagVals[0][1]) {
|
||||
return false
|
||||
}
|
||||
|
||||
ns := errPrefix + name
|
||||
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + customName,
|
||||
Name: customName,
|
||||
Field: name,
|
||||
Tag: valTag.tag,
|
||||
ActualTag: valTag.tagVals[0][0],
|
||||
Value: current.Interface(),
|
||||
Param: valTag.tagVals[0][1],
|
||||
Type: currentType,
|
||||
Kind: currentKind,
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
+5435
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user