Repository: go-playground/validator
Pull request #560 (2019-12-25 04:09:36)

cherry picking v9


Patch:
diff --git a/_examples/simple/main.go b/_examples/simple/main.go
index eb9af79..0d3ca51 100644
--- a/_examples/simple/main.go
+++ b/_examples/simple/main.go
@@ -68,8 +68,8 @@ func validateStruct() {
 
 			fmt.Println(err.Namespace())
 			fmt.Println(err.Field())
-			fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or
-			fmt.Println(err.StructField())     // by passing alt name to ReportError like below
+			fmt.Println(err.StructNamespace())
+			fmt.Println(err.StructField())
 			fmt.Println(err.Tag())
 			fmt.Println(err.ActualTag())
 			fmt.Println(err.Kind())
diff --git a/_examples/struct-level/main.go b/_examples/struct-level/main.go
index 5e69f6b..3231a81 100644
--- a/_examples/struct-level/main.go
+++ b/_examples/struct-level/main.go
@@ -2,6 +2,8 @@ package main
 
 import (
 	"fmt"
+	"reflect"
+	"strings"
 
 	"github.com/go-playground/validator/v10"
 )
@@ -11,7 +13,7 @@ type User struct {
 	FirstName      string     `json:"fname"`
 	LastName       string     `json:"lname"`
 	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"`
 	Addresses      []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
 }
@@ -31,6 +33,15 @@ func main() {
 
 	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'
 	// NOTE: only have to register a non-pointer type for 'User', validator
 	// interanlly dereferences during it's type checks.
@@ -48,7 +59,7 @@ func main() {
 		FirstName:      "",
 		LastName:       "",
 		Age:            45,
-		Email:          "Badger.Smith@gmail.com",
+		Email:          "Badger.Smith@gmail",
 		FavouriteColor: "#000",
 		Addresses:      []*Address{address},
 	}
@@ -67,10 +78,10 @@ func main() {
 
 		for _, err := range err.(validator.ValidationErrors) {
 
-			fmt.Println(err.Namespace())
-			fmt.Println(err.Field())
-			fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or
-			fmt.Println(err.StructField())     // by passing alt name to ReportError like below
+			fmt.Println(err.Namespace()) // can differ when a custom TagNameFunc is registered or
+			fmt.Println(err.Field())     // by passing alt name to ReportError like below
+			fmt.Println(err.StructNamespace())
+			fmt.Println(err.StructField())
 			fmt.Println(err.Tag())
 			fmt.Println(err.ActualTag())
 			fmt.Println(err.Kind())
@@ -101,8 +112,8 @@ func UserStructLevelValidation(sl validator.StructLevel) {
 	user := sl.Current().Interface().(User)
 
 	if len(user.FirstName) == 0 && len(user.LastName) == 0 {
-		sl.ReportError(user.FirstName, "FirstName", "fname", "fnameorlname", "")
-		sl.ReportError(user.LastName, "LastName", "lname", "fnameorlname", "")
+		sl.ReportError(user.FirstName, "fname", "FirstName", "fnameorlname", "")
+		sl.ReportError(user.LastName, "lname", "LastName", "fnameorlname", "")
 	}
 
 	// plus can do more, even with different tag than "fnameorlname"
diff --git a/baked_in.go b/baked_in.go
index 04cf57d..9eff0e4 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -103,6 +103,7 @@ var (
 		"rgba":                 isRGBA,
 		"hsl":                  isHSL,
 		"hsla":                 isHSLA,
+		"e164":                 isE164,
 		"email":                isEmail,
 		"url":                  isURL,
 		"uri":                  isURI,
@@ -227,14 +228,28 @@ func isOneOf(fl FieldLevel) bool {
 func isUnique(fl FieldLevel) bool {
 
 	field := fl.Field()
+	param := fl.Param()
 	v := reflect.ValueOf(struct{}{})
 
 	switch field.Kind() {
 	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++ {
-			m.SetMapIndex(field.Index(i), v)
+			m.SetMapIndex(field.Index(i).FieldByName(param), v)
 		}
 		return field.Len() == m.Len()
 	case reflect.Map:
@@ -1227,6 +1242,11 @@ func isFile(fl FieldLevel) bool {
 	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.
 func isEmail(fl FieldLevel) bool {
 	return emailRegex.MatchString(fl.Field().String())
diff --git a/doc.go b/doc.go
index cdfb759..799882c 100644
--- a/doc.go
+++ b/doc.go
@@ -587,9 +587,15 @@ Unique
 
 For arrays & slices, unique will ensure that there are no duplicates.
 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
 
+	// For slices of struct:
+	Usage: unique=field
+
 Alpha 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
 		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
 
 A collection of validation rules that are frequently needed but are more
 complex than the ones found in the baked in validators.
-A non standard validator must be registered manually using any tag you like.
-See below examples of registration and use.
+A non standard validator must be registered manually like you would
+with your own custom validation functions.
+
+Example of registration and use:
 
 	type Test struct {
 		TestField string `validate:"yourtag"`
@@ -1091,7 +1084,9 @@ See below examples of registration and use.
 	}
 
 	validate := validator.New()
-	validate.RegisterValidation("yourtag", validations.ValidatorName)
+	validate.RegisterValidation("yourtag", validators.NotBlank)
+
+Here is a list of the current non standard validators:
 
 	NotBlank
 		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.
 
 		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
diff --git a/field_level.go b/field_level.go
index 7a13f33..f0e2a9a 100644
--- a/field_level.go
+++ b/field_level.go
@@ -5,7 +5,6 @@ import "reflect"
 // FieldLevel contains all the information and helper functions
 // to validate a field
 type FieldLevel interface {
-
 	// returns the top level struct, if any
 	Top() reflect.Value
 
@@ -26,6 +25,9 @@ type FieldLevel interface {
 	// returns param for validation against current field
 	Param() string
 
+	// GetTag returns the current validations tag name
+	GetTag() string
+
 	// 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.
@@ -73,6 +75,11 @@ func (v *validate) FieldName() string {
 	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
 func (v *validate) StructFieldName() string {
 	return v.cf.name
diff --git a/regexes.go b/regexes.go
index 6343abe..63fdc1d 100644
--- a/regexes.go
+++ b/regexes.go
@@ -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*\\)$"
 	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}])))\\.?$"
+	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})$"
 	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})$"
@@ -62,6 +63,7 @@ var (
 	rgbaRegex                  = regexp.MustCompile(rgbaRegexString)
 	hslRegex                   = regexp.MustCompile(hslRegexString)
 	hslaRegex                  = regexp.MustCompile(hslaRegexString)
+	e164Regex                  = regexp.MustCompile(e164RegexString)
 	emailRegex                 = regexp.MustCompile(emailRegexString)
 	base64Regex                = regexp.MustCompile(base64RegexString)
 	base64URLRegex             = regexp.MustCompile(base64URLRegexString)
diff --git a/translations/en/en.go b/translations/en/en.go
index fd43f38..3b1058b 100644
--- a/translations/en/en.go
+++ b/translations/en/en.go
@@ -1043,6 +1043,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
 			translation: "{0} must be a valid HSLA color",
 			override:    false,
 		},
+		{
+			tag:         "e164",
+			translation: "{0} must be a valid E.164 formatted phone number",
+			override:    false,
+		},
 		{
 			tag:         "email",
 			translation: "{0} must be a valid email address",


Test patch:
diff --git a/validator_test.go b/validator_test.go
index 968503e..2c8b96b 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -4917,6 +4917,7 @@ func TestStructOnlyValidation(t *testing.T) {
 		FirstName      string     `json:"fname"`
 		LastName       string     `json:"lname"`
 		Age            uint8      `validate:"gte=0,lte=130"`
+		Number         string     `validate:"required,e164"`
 		Email          string     `validate:"required,email"`
 		FavouriteColor string     `validate:"hexcolor|rgb|rgba"`
 		Addresses      []*Address `validate:"required"`   // a person can have a home and cottage...
@@ -4934,6 +4935,7 @@ func TestStructOnlyValidation(t *testing.T) {
 		FirstName:      "",
 		LastName:       "",
 		Age:            45,
+		Number:         "+1123456789",
 		Email:          "Badger.Smith@gmail.com",
 		FavouriteColor: "#000",
 		Addresses:      []*Address{address},
@@ -8183,6 +8185,49 @@ func TestUniqueValidation(t *testing.T) {
 	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) {
 	tests := []struct {
 		param    string
@@ -8939,3 +8984,22 @@ func TestRequiredWithoutAllPointers(t *testing.T) {
 	errs = val.Struct(lookup)
 	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")
+}