Merge pull request #560 from go-playground/cherry-pick-v9

cherry picking v9
pull/564/head
Dean Karn 5 years ago committed by GitHub
commit 5b97da4cbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      _examples/simple/main.go
  2. 27
      _examples/struct-level/main.go
  3. 24
      baked_in.go
  4. 46
      doc.go
  5. 9
      field_level.go
  6. 2
      regexes.go
  7. 5
      translations/en/en.go
  8. 64
      validator_test.go

@ -68,8 +68,8 @@ func validateStruct() {
fmt.Println(err.Namespace()) fmt.Println(err.Namespace())
fmt.Println(err.Field()) fmt.Println(err.Field())
fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or fmt.Println(err.StructNamespace())
fmt.Println(err.StructField()) // by passing alt name to ReportError like below fmt.Println(err.StructField())
fmt.Println(err.Tag()) fmt.Println(err.Tag())
fmt.Println(err.ActualTag()) fmt.Println(err.ActualTag())
fmt.Println(err.Kind()) fmt.Println(err.Kind())

@ -2,6 +2,8 @@ package main
import ( import (
"fmt" "fmt"
"reflect"
"strings"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
) )
@ -11,7 +13,7 @@ type User struct {
FirstName string `json:"fname"` FirstName string `json:"fname"`
LastName string `json:"lname"` LastName string `json:"lname"`
Age uint8 `validate:"gte=0,lte=130"` Age uint8 `validate:"gte=0,lte=130"`
Email string `validate:"required,email"` Email string `json:"e-mail" validate:"required,email"`
FavouriteColor string `validate:"hexcolor|rgb|rgba"` FavouriteColor string `validate:"hexcolor|rgb|rgba"`
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
} }
@ -31,6 +33,15 @@ func main() {
validate = validator.New() validate = validator.New()
// register function to get tag name from json tags.
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
// register validation for 'User' // register validation for 'User'
// NOTE: only have to register a non-pointer type for 'User', validator // NOTE: only have to register a non-pointer type for 'User', validator
// interanlly dereferences during it's type checks. // interanlly dereferences during it's type checks.
@ -48,7 +59,7 @@ func main() {
FirstName: "", FirstName: "",
LastName: "", LastName: "",
Age: 45, Age: 45,
Email: "Badger.Smith@gmail.com", Email: "Badger.Smith@gmail",
FavouriteColor: "#000", FavouriteColor: "#000",
Addresses: []*Address{address}, Addresses: []*Address{address},
} }
@ -67,10 +78,10 @@ func main() {
for _, err := range err.(validator.ValidationErrors) { for _, err := range err.(validator.ValidationErrors) {
fmt.Println(err.Namespace()) fmt.Println(err.Namespace()) // can differ when a custom TagNameFunc is registered or
fmt.Println(err.Field()) fmt.Println(err.Field()) // by passing alt name to ReportError like below
fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or fmt.Println(err.StructNamespace())
fmt.Println(err.StructField()) // by passing alt name to ReportError like below fmt.Println(err.StructField())
fmt.Println(err.Tag()) fmt.Println(err.Tag())
fmt.Println(err.ActualTag()) fmt.Println(err.ActualTag())
fmt.Println(err.Kind()) fmt.Println(err.Kind())
@ -101,8 +112,8 @@ func UserStructLevelValidation(sl validator.StructLevel) {
user := sl.Current().Interface().(User) user := sl.Current().Interface().(User)
if len(user.FirstName) == 0 && len(user.LastName) == 0 { if len(user.FirstName) == 0 && len(user.LastName) == 0 {
sl.ReportError(user.FirstName, "FirstName", "fname", "fnameorlname", "") sl.ReportError(user.FirstName, "fname", "FirstName", "fnameorlname", "")
sl.ReportError(user.LastName, "LastName", "lname", "fnameorlname", "") sl.ReportError(user.LastName, "lname", "LastName", "fnameorlname", "")
} }
// plus can do more, even with different tag than "fnameorlname" // plus can do more, even with different tag than "fnameorlname"

@ -103,6 +103,7 @@ var (
"rgba": isRGBA, "rgba": isRGBA,
"hsl": isHSL, "hsl": isHSL,
"hsla": isHSLA, "hsla": isHSLA,
"e164": isE164,
"email": isEmail, "email": isEmail,
"url": isURL, "url": isURL,
"uri": isURI, "uri": isURI,
@ -227,14 +228,28 @@ func isOneOf(fl FieldLevel) bool {
func isUnique(fl FieldLevel) bool { func isUnique(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
param := fl.Param()
v := reflect.ValueOf(struct{}{}) v := reflect.ValueOf(struct{}{})
switch field.Kind() { switch field.Kind() {
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type())) if param == "" {
m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
for i := 0; i < field.Len(); i++ {
m.SetMapIndex(field.Index(i), v)
}
return field.Len() == m.Len()
}
sf, ok := field.Type().Elem().FieldByName(param)
if !ok {
panic(fmt.Sprintf("Bad field name %s", param))
}
m := reflect.MakeMap(reflect.MapOf(sf.Type, v.Type()))
for i := 0; i < field.Len(); i++ { for i := 0; i < field.Len(); i++ {
m.SetMapIndex(field.Index(i), v) m.SetMapIndex(field.Index(i).FieldByName(param), v)
} }
return field.Len() == m.Len() return field.Len() == m.Len()
case reflect.Map: case reflect.Map:
@ -1227,6 +1242,11 @@ func isFile(fl FieldLevel) bool {
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %T", field.Interface()))
} }
// IsE164 is the validation function for validating if the current field's value is a valid e.164 formatted phone number.
func isE164(fl FieldLevel) bool {
return e164Regex.MatchString(fl.Field().String())
}
// IsEmail is the validation function for validating if the current field's value is a valid email address. // IsEmail is the validation function for validating if the current field's value is a valid email address.
func isEmail(fl FieldLevel) bool { func isEmail(fl FieldLevel) bool {
return emailRegex.MatchString(fl.Field().String()) return emailRegex.MatchString(fl.Field().String())

@ -587,9 +587,15 @@ Unique
For arrays & slices, unique will ensure that there are no duplicates. For arrays & slices, unique will ensure that there are no duplicates.
For maps, unique will ensure that there are no duplicate values. For maps, unique will ensure that there are no duplicate values.
For slices of struct, unique will ensure that there are no duplicate values
in a field of the struct specified via a parameter.
// For arrays, slices, and maps:
Usage: unique Usage: unique
// For slices of struct:
Usage: unique=field
Alpha Only Alpha Only
This validates that a string value contains ASCII alpha characters only This validates that a string value contains ASCII alpha characters only
@ -1060,27 +1066,14 @@ Validator notes:
And the best reason, you can submit a pull request and we can keep on And the best reason, you can submit a pull request and we can keep on
adding to the validation library of this package! 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
Non standard validators Non standard validators
A collection of validation rules that are frequently needed but are more A collection of validation rules that are frequently needed but are more
complex than the ones found in the baked in validators. complex than the ones found in the baked in validators.
A non standard validator must be registered manually using any tag you like. A non standard validator must be registered manually like you would
See below examples of registration and use. with your own custom validation functions.
Example of registration and use:
type Test struct { type Test struct {
TestField string `validate:"yourtag"` TestField string `validate:"yourtag"`
@ -1091,7 +1084,9 @@ See below examples of registration and use.
} }
validate := validator.New() validate := validator.New()
validate.RegisterValidation("yourtag", validations.ValidatorName) validate.RegisterValidation("yourtag", validators.NotBlank)
Here is a list of the current non standard validators:
NotBlank NotBlank
This validates that the value is not blank or with length zero. This validates that the value is not blank or with length zero.
@ -1099,5 +1094,20 @@ See below examples of registration and use.
ensures they don't have zero length. For others, a non empty value is required. ensures they don't have zero length. For others, a non empty value is required.
Usage: notblank Usage: notblank
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 package validator

@ -5,7 +5,6 @@ import "reflect"
// FieldLevel contains all the information and helper functions // FieldLevel contains all the information and helper functions
// to validate a field // to validate a field
type FieldLevel interface { type FieldLevel interface {
// returns the top level struct, if any // returns the top level struct, if any
Top() reflect.Value Top() reflect.Value
@ -26,6 +25,9 @@ type FieldLevel interface {
// returns param for validation against current field // returns param for validation against current field
Param() string Param() string
// GetTag returns the current validations tag name
GetTag() string
// ExtractType gets the actual underlying type of field value. // ExtractType gets the actual underlying type of field value.
// It will dive into pointers, customTypes and return you the // It will dive into pointers, customTypes and return you the
// underlying value and it's kind. // underlying value and it's kind.
@ -73,6 +75,11 @@ func (v *validate) FieldName() string {
return v.cf.altName return v.cf.altName
} }
// GetTag returns the current validations tag name
func (v *validate) GetTag() string {
return v.ct.tag
}
// StructFieldName returns the struct field's name // StructFieldName returns the struct field's name
func (v *validate) StructFieldName() string { func (v *validate) StructFieldName() string {
return v.cf.name return v.cf.name

@ -16,6 +16,7 @@ const (
hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$" hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$"
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
e164RegexString = "^\\+[1-9]?[0-9]{7,14}$"
base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$" base64URLRegexString = "^(?:[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})$" iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$"
@ -62,6 +63,7 @@ var (
rgbaRegex = regexp.MustCompile(rgbaRegexString) rgbaRegex = regexp.MustCompile(rgbaRegexString)
hslRegex = regexp.MustCompile(hslRegexString) hslRegex = regexp.MustCompile(hslRegexString)
hslaRegex = regexp.MustCompile(hslaRegexString) hslaRegex = regexp.MustCompile(hslaRegexString)
e164Regex = regexp.MustCompile(e164RegexString)
emailRegex = regexp.MustCompile(emailRegexString) emailRegex = regexp.MustCompile(emailRegexString)
base64Regex = regexp.MustCompile(base64RegexString) base64Regex = regexp.MustCompile(base64RegexString)
base64URLRegex = regexp.MustCompile(base64URLRegexString) base64URLRegex = regexp.MustCompile(base64URLRegexString)

@ -1043,6 +1043,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0} must be a valid HSLA color", translation: "{0} must be a valid HSLA color",
override: false, override: false,
}, },
{
tag: "e164",
translation: "{0} must be a valid E.164 formatted phone number",
override: false,
},
{ {
tag: "email", tag: "email",
translation: "{0} must be a valid email address", translation: "{0} must be a valid email address",

@ -4917,6 +4917,7 @@ func TestStructOnlyValidation(t *testing.T) {
FirstName string `json:"fname"` FirstName string `json:"fname"`
LastName string `json:"lname"` LastName string `json:"lname"`
Age uint8 `validate:"gte=0,lte=130"` Age uint8 `validate:"gte=0,lte=130"`
Number string `validate:"required,e164"`
Email string `validate:"required,email"` Email string `validate:"required,email"`
FavouriteColor string `validate:"hexcolor|rgb|rgba"` FavouriteColor string `validate:"hexcolor|rgb|rgba"`
Addresses []*Address `validate:"required"` // a person can have a home and cottage... Addresses []*Address `validate:"required"` // a person can have a home and cottage...
@ -4934,6 +4935,7 @@ func TestStructOnlyValidation(t *testing.T) {
FirstName: "", FirstName: "",
LastName: "", LastName: "",
Age: 45, Age: 45,
Number: "+1123456789",
Email: "Badger.Smith@gmail.com", Email: "Badger.Smith@gmail.com",
FavouriteColor: "#000", FavouriteColor: "#000",
Addresses: []*Address{address}, Addresses: []*Address{address},
@ -8183,6 +8185,49 @@ func TestUniqueValidation(t *testing.T) {
PanicMatches(t, func() { _ = validate.Var(1.0, "unique") }, "Bad field type float64") PanicMatches(t, func() { _ = validate.Var(1.0, "unique") }, "Bad field type float64")
} }
func TestUniqueValidationStructSlice(t *testing.T) {
testStructs := []struct {
A string
B string
}{
{A: "one", B: "two"},
{A: "one", B: "three"},
}
tests := []struct {
target interface{}
param string
expected bool
}{
{testStructs, "unique", true},
{testStructs, "unique=A", false},
{testStructs, "unique=B", true},
}
validate := New()
for i, test := range tests {
errs := validate.Var(test.target, test.param)
if test.expected {
if !IsEqual(errs, nil) {
t.Fatalf("Index: %d unique failed Error: %v", i, errs)
}
} else {
if IsEqual(errs, nil) {
t.Fatalf("Index: %d unique failed Error: %v", i, errs)
} else {
val := getError(errs, "", "")
if val.Tag() != "unique" {
t.Fatalf("Index: %d unique failed Error: %v", i, errs)
}
}
}
}
PanicMatches(t, func() { validate.Var(testStructs, "unique=C") }, "Bad field name C")
}
func TestHTMLValidation(t *testing.T) { func TestHTMLValidation(t *testing.T) {
tests := []struct { tests := []struct {
param string param string
@ -8939,3 +8984,22 @@ func TestRequiredWithoutAllPointers(t *testing.T) {
errs = val.Struct(lookup) errs = val.Struct(lookup)
Equal(t, errs, nil) Equal(t, errs, nil)
} }
func TestGetTag(t *testing.T) {
var tag string
type Test struct {
String string `validate:"mytag"`
}
val := New()
val.RegisterValidation("mytag", func(fl FieldLevel) bool {
tag = fl.GetTag()
return true
})
var test Test
errs := val.Struct(test)
Equal(t, errs, nil)
Equal(t, tag, "mytag")
}

Loading…
Cancel
Save