Repository: go-playground/validator
Pull request #1014 (2022-10-11 16:37:21)

Added a genre enum to the user model and a custom validation
Signed-off-by: Kmilo Denis Glez <kmilo.denis.glez@gmail.com>

## Fixes Or Enhances


**Make sure that you've checked the boxes below before you submit PR:**
- [x] Tests exist or have been written that cover this particular change.

@go-playground/validator-maintainers

coveralls: 
[![Coverage Status](https://coveralls.io/builds/53180435/badge)](https://coveralls.io/builds/53180435)

Coverage remained the same at 74.182% when pulling **42127e57b684d077a344608d72474067d6c8c015 on kmilodenisglez:struct-level-updated** into **c7e0172e0fd176bdc521afb5186818a7db6b77ac on go-playground:master**.

kmilodenisglez: Updated the Struct Level Example

Patch:
diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
index 8c07a64..041b304 100644
--- a/.github/workflows/workflow.yml
+++ b/.github/workflows/workflow.yml
@@ -8,7 +8,7 @@ jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.17.x, 1.18.x]
+        go-version: [1.19.x]
         os: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
@@ -32,7 +32,7 @@ jobs:
         run: go test -race -covermode=atomic -coverprofile="profile.cov" ./...
 
       - name: Send Coverage
-        if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.18.x'
+        if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.19.x'
         uses: shogo82148/actions-goveralls@v1
         with:
           path-to-profile: profile.cov
@@ -43,9 +43,9 @@ jobs:
     steps:
       - uses: actions/setup-go@v3
         with:
-          go-version: 1.18.x
+          go-version: 1.19.x
       - uses: actions/checkout@v3
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v3
         with:
-          version: v1.45.2
+          version: v1.50.1
diff --git a/.gitignore b/.gitignore
index 6e43fac..2410a91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ _testmain.go
 *.txt
 cover.html
 README.html
+.idea
diff --git a/README.md b/README.md
index 9d0a79e..9c631d6 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 Package validator
 =================
 <img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-![Project status](https://img.shields.io/badge/version-10.11.1-green.svg)
+![Project status](https://img.shields.io/badge/version-10.11.2-green.svg)
 [![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator)
 [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master)
 [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
@@ -73,8 +73,8 @@ Baked-in Validations
 | - | - |
 | eqcsfield | Field Equals Another Field (relative)|
 | eqfield | Field Equals Another Field |
-| fieldcontains | NOT DOCUMENTED IN doc.go |
-| fieldexcludes | NOT DOCUMENTED IN doc.go |
+| fieldcontains | Check the indicated characters are present in the Field |
+| fieldexcludes | Check the indicated characters are not present in the field |
 | gtcsfield | Field Greater Than Another Relative Field |
 | gtecsfield | Field Greater Than or Equal To Another Relative Field |
 | gtefield | Field Greater Than or Equal To Another Field |
@@ -114,6 +114,7 @@ Baked-in Validations
 | unix_addr | Unix domain socket end point Address |
 | uri | URI String |
 | url | URL String |
+| http_url | HTTP URL String |
 | url_encoded | URL Encoded |
 | urn_rfc2141 | Urn RFC 2141 String |
 
@@ -137,7 +138,7 @@ Baked-in Validations
 | excludesrune | Excludes Rune |
 | lowercase | Lowercase |
 | multibyte | Multi-Byte Characters |
-| number | NOT DOCUMENTED IN doc.go |
+| number | Number |
 | numeric | Numeric |
 | printascii | Printable ASCII |
 | startsnotwith | Starts Not With |
@@ -149,11 +150,14 @@ Baked-in Validations
 | - | - |
 | base64 | Base64 String |
 | base64url | Base64URL String |
+| base64rawurl | Base64RawURL String |
 | bic | Business Identifier Code (ISO 9362) |
 | bcp47_language_tag | Language tag (BCP 47) |
 | btc_addr | Bitcoin Address |
 | btc_addr_bech32 | Bitcoin Bech32 Address (segwit) |
 | credit_card | Credit Card Number |
+| mongodb | MongoDB ObjectID |
+| cron | Cron |
 | datetime | Datetime |
 | e164 | e164 formatted phone number |
 | email | E-mail String
@@ -207,17 +211,21 @@ Baked-in Validations
 | Tag | Description |
 | - | - |
 | eq | Equals |
+| eq_ignore_case | Equals ignoring case |
 | gt | Greater than|
 | gte | Greater than or equal |
 | lt | Less Than |
 | lte | Less Than or Equal |
 | ne | Not Equal |
+| ne_ignore_case | Not Equal ignoring case |
 
 ### Other:
 | Tag | Description |
 | - | - |
-| dir | Directory |
-| file | File path |
+| dir | Existing Directory |
+| dirpath | Directory Path |
+| file | Existing File |
+| filepath | File Path |
 | isdefault | Is Default |
 | len | Length |
 | max | Maximum |
diff --git a/_examples/struct-level/main.go b/_examples/struct-level/main.go
index 6002a3b..a16cf4e 100644
--- a/_examples/struct-level/main.go
+++ b/_examples/struct-level/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
 	"reflect"
 	"strings"
@@ -8,6 +9,36 @@ import (
 	"github.com/go-playground/validator/v10"
 )
 
+type validationError struct {
+	Namespace       string `json:"namespace"` // can differ when a custom TagNameFunc is registered or
+	Field           string `json:"field"`     // by passing alt name to ReportError like below
+	StructNamespace string `json:"structNamespace"`
+	StructField     string `json:"structField"`
+	Tag             string `json:"tag"`
+	ActualTag       string `json:"actualTag"`
+	Kind            string `json:"kind"`
+	Type            string `json:"type"`
+	Value           string `json:"value"`
+	Param           string `json:"param"`
+	Message         string `json:"message"`
+}
+
+type Gender uint
+
+const (
+	Male Gender = iota + 1
+	Female
+	Intersex
+)
+
+func (gender Gender) String() string {
+	terms := []string{"Male", "Female", "Intersex"}
+	if gender < Male || gender > Intersex {
+		return "unknown"
+	}
+	return terms[gender]
+}
+
 // User contains user information
 type User struct {
 	FirstName      string     `json:"fname"`
@@ -16,6 +47,7 @@ type User struct {
 	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...
+	Gender         Gender     `json:"gender" validate:"required,gender_custom_validation"`
 }
 
 // Address houses a users address information
@@ -47,6 +79,17 @@ func main() {
 	// internally dereferences during it's type checks.
 	validate.RegisterStructValidation(UserStructLevelValidation, User{})
 
+	// register a custom validation for user genre on a line
+	// validates that an enum is within the interval
+	err := validate.RegisterValidation("gender_custom_validation", func(fl validator.FieldLevel) bool {
+		value := fl.Field().Interface().(Gender)
+		return value.String() != "unknown"
+	})
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
 	// build 'User' info, normally posted data etc...
 	address := &Address{
 		Street: "Eavesdown Docks",
@@ -65,7 +108,7 @@ func main() {
 	}
 
 	// returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError )
-	err := validate.Struct(user)
+	err = validate.Struct(user)
 	if err != nil {
 
 		// this check is only needed when your code could produce
@@ -77,18 +120,27 @@ func main() {
 		}
 
 		for _, err := range err.(validator.ValidationErrors) {
-
-			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())
-			fmt.Println(err.Type())
-			fmt.Println(err.Value())
-			fmt.Println(err.Param())
-			fmt.Println()
+			e := validationError{
+				Namespace:       err.Namespace(),
+				Field:           err.Field(),
+				StructNamespace: err.StructNamespace(),
+				StructField:     err.StructField(),
+				Tag:             err.Tag(),
+				ActualTag:       err.ActualTag(),
+				Kind:            fmt.Sprintf("%v", err.Kind()),
+				Type:            fmt.Sprintf("%v", err.Type()),
+				Value:           fmt.Sprintf("%v", err.Value()),
+				Param:           err.Param(),
+				Message:         err.Error(),
+			}
+
+			indent, err := json.MarshalIndent(e, "", "  ")
+			if err != nil {
+				fmt.Println(err)
+				panic(err)
+			}
+
+			fmt.Println(string(indent))
 		}
 
 		// from here you can create your own error messages in whatever language you wish
diff --git a/baked_in.go b/baked_in.go
index c9b1db4..028bc84 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -7,6 +7,7 @@ import (
 	"encoding/hex"
 	"encoding/json"
 	"fmt"
+	"io/fs"
 	"net"
 	"net/url"
 	"os"
@@ -14,13 +15,14 @@ import (
 	"strconv"
 	"strings"
 	"sync"
+	"syscall"
 	"time"
 	"unicode/utf8"
 
 	"golang.org/x/crypto/sha3"
 	"golang.org/x/text/language"
 
-	urn "github.com/leodido/go-urn"
+	"github.com/leodido/go-urn"
 )
 
 // Func accepts a FieldLevel interface for all validation needs. The return
@@ -86,7 +88,9 @@ var (
 		"min":                           hasMinOf,
 		"max":                           hasMaxOf,
 		"eq":                            isEq,
+		"eq_ignore_case":                isEqIgnoreCase,
 		"ne":                            isNe,
+		"ne_ignore_case":                isNeIgnoreCase,
 		"lt":                            isLt,
 		"lte":                           isLte,
 		"gt":                            isGt,
@@ -121,11 +125,14 @@ var (
 		"e164":                          isE164,
 		"email":                         isEmail,
 		"url":                           isURL,
+		"http_url":                      isHttpURL,
 		"uri":                           isURI,
 		"urn_rfc2141":                   isUrnRFC2141, // RFC 2141
 		"file":                          isFile,
+		"filepath":                      isFilePath,
 		"base64":                        isBase64,
 		"base64url":                     isBase64URL,
+		"base64rawurl":                  isBase64RawURL,
 		"contains":                      contains,
 		"containsany":                   containsAny,
 		"containsrune":                  containsRune,
@@ -140,6 +147,7 @@ var (
 		"isbn10":                        isISBN10,
 		"isbn13":                        isISBN13,
 		"eth_addr":                      isEthereumAddress,
+		"eth_addr_checksum":             isEthereumAddressChecksum,
 		"btc_addr":                      isBitcoinAddress,
 		"btc_addr_bech32":               isBitcoinBech32Address,
 		"uuid":                          isUUID,
@@ -194,6 +202,7 @@ var (
 		"html_encoded":                  isHTMLEncoded,
 		"url_encoded":                   isURLEncoded,
 		"dir":                           isDir,
+		"dirpath":                       isDirPath,
 		"json":                          isJSON,
 		"jwt":                           isJWT,
 		"hostname_port":                 isHostnamePort,
@@ -214,6 +223,8 @@ var (
 		"semver":                        isSemverFormat,
 		"dns_rfc1035_label":             isDnsRFC1035LabelFormat,
 		"credit_card":                   isCreditCard,
+		"mongodb":                       isMongoDB,
+		"cron":                          isCron,
 	}
 )
 
@@ -307,18 +318,42 @@ func isUnique(fl FieldLevel) bool {
 		}
 
 		m := reflect.MakeMap(reflect.MapOf(sfTyp, v.Type()))
+		var fieldlen int
 		for i := 0; i < field.Len(); i++ {
-			m.SetMapIndex(reflect.Indirect(reflect.Indirect(field.Index(i)).FieldByName(param)), v)
+			key := reflect.Indirect(reflect.Indirect(field.Index(i)).FieldByName(param))
+			if key.IsValid() {
+				fieldlen++
+				m.SetMapIndex(key, v)
+			}
 		}
-		return field.Len() == m.Len()
+		return fieldlen == m.Len()
 	case reflect.Map:
-		m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
+		var m reflect.Value
+		if field.Type().Elem().Kind() == reflect.Ptr {
+			m = reflect.MakeMap(reflect.MapOf(field.Type().Elem().Elem(), v.Type()))
+		} else {
+			m = reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
+		}
 
 		for _, k := range field.MapKeys() {
-			m.SetMapIndex(field.MapIndex(k), v)
+			m.SetMapIndex(reflect.Indirect(field.MapIndex(k)), v)
 		}
+
 		return field.Len() == m.Len()
 	default:
+		if parent := fl.Parent(); parent.Kind() == reflect.Struct {
+			uniqueField := parent.FieldByName(param)
+			if uniqueField == reflect.ValueOf(nil) {
+				panic(fmt.Sprintf("Bad field name provided %s", param))
+			}
+
+			if uniqueField.Kind() != field.Kind() {
+				panic(fmt.Sprintf("Bad field type %T:%T", field.Interface(), uniqueField.Interface()))
+			}
+
+			return field.Interface() != uniqueField.Interface()
+		}
+
 		panic(fmt.Sprintf("Bad field type %T", field.Interface()))
 	}
 }
@@ -613,14 +648,16 @@ func isISBN10(fl FieldLevel) bool {
 func isEthereumAddress(fl FieldLevel) bool {
 	address := fl.Field().String()
 
+	return ethAddressRegex.MatchString(address)
+}
+
+// isEthereumAddressChecksum is the validation function for validating if the field's value is a valid checksumed Ethereum address.
+func isEthereumAddressChecksum(fl FieldLevel) bool {
+	address := fl.Field().String()
+
 	if !ethAddressRegex.MatchString(address) {
 		return false
 	}
-
-	if ethAddressRegexUpper.MatchString(address) || ethAddressRegexLower.MatchString(address) {
-		return true
-	}
-
 	// Checksum validation. Reference: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
 	address = address[2:] // Skip "0x" prefix.
 	h := sha3.NewLegacyKeccak256()
@@ -889,6 +926,12 @@ func isNe(fl FieldLevel) bool {
 	return !isEq(fl)
 }
 
+// isNe is the validation function for validating that the field's string value does not equal the
+// provided param value. The comparison is case-insensitive
+func isNeIgnoreCase(fl FieldLevel) bool {
+	return !isEqIgnoreCase(fl)
+}
+
 // isLteCrossStructField is the validation function for validating if the current field's value is less than or equal to the field, within a separate struct, specified by the param's value.
 func isLteCrossStructField(fl FieldLevel) bool {
 	field := fl.Field()
@@ -1260,6 +1303,22 @@ func isEq(fl FieldLevel) bool {
 	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
 }
 
+// isEqIgnoreCase is the validation function for validating if the current field's string value is
+// equal to the param's value.
+// The comparison is case-insensitive.
+func isEqIgnoreCase(fl FieldLevel) bool {
+	field := fl.Field()
+	param := fl.Param()
+
+	switch field.Kind() {
+
+	case reflect.String:
+		return strings.EqualFold(field.String(), param)
+	}
+
+	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
+}
+
 // isPostcodeByIso3166Alpha2 validates by value which is country code in iso 3166 alpha 2
 // example: `postcode_iso3166_alpha2=US`
 func isPostcodeByIso3166Alpha2(fl FieldLevel) bool {
@@ -1311,6 +1370,11 @@ func isBase64URL(fl FieldLevel) bool {
 	return base64URLRegex.MatchString(fl.Field().String())
 }
 
+// isBase64RawURL is the validation function for validating if the current field's value is a valid base64 URL safe string without '=' padding.
+func isBase64RawURL(fl FieldLevel) bool {
+	return base64RawURLRegex.MatchString(fl.Field().String())
+}
+
 // isURI is the validation function for validating if the current field's value is a valid URI.
 func isURI(fl FieldLevel) bool {
 	field := fl.Field()
@@ -1370,6 +1434,23 @@ func isURL(fl FieldLevel) bool {
 	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
 }
 
+// isHttpURL is the validation function for validating if the current field's value is a valid HTTP(s) URL.
+func isHttpURL(fl FieldLevel) bool {
+	if !isURL(fl) {
+		return false
+	}
+
+	field := fl.Field()
+	switch field.Kind() {
+	case reflect.String:
+
+		s := strings.ToLower(field.String())
+		return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")
+	}
+
+	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
+}
+
 // isUrnRFC2141 is the validation function for validating if the current field's value is a valid URN as per RFC 2141.
 func isUrnRFC2141(fl FieldLevel) bool {
 	field := fl.Field()
@@ -1387,7 +1468,7 @@ func isUrnRFC2141(fl FieldLevel) bool {
 	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
 }
 
-// isFile is the validation function for validating if the current field's value is a valid file path.
+// isFile is the validation function for validating if the current field's value is a valid existing file path.
 func isFile(fl FieldLevel) bool {
 	field := fl.Field()
 
@@ -1404,6 +1485,57 @@ func isFile(fl FieldLevel) bool {
 	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
 }
 
+// isFilePath is the validation function for validating if the current field's value is a valid file path.
+func isFilePath(fl FieldLevel) bool {
+
+	var exists bool
+	var err error
+
+	field := fl.Field()
+
+	// If it exists, it obviously is valid.
+	// This is done first to avoid code duplication and unnecessary additional logic.
+	if exists = isFile(fl); exists {
+		return true
+	}
+
+	// It does not exist but may still be a valid filepath.
+	switch field.Kind() {
+	case reflect.String:
+		// Every OS allows for whitespace, but none
+		// let you use a file with no filename (to my knowledge).
+		// Unless you're dealing with raw inodes, but I digress.
+		if strings.TrimSpace(field.String()) == "" {
+			return false
+		}
+		// We make sure it isn't a directory.
+		if strings.HasSuffix(field.String(), string(os.PathSeparator)) {
+			return false
+		}
+		if _, err = os.Stat(field.String()); err != nil {
+			switch t := err.(type) {
+			case *fs.PathError:
+				if t.Err == syscall.EINVAL {
+					// It's definitely an invalid character in the filepath.
+					return false
+				}
+				// It could be a permission error, a does-not-exist error, etc.
+				// Out-of-scope for this validation, though.
+				return true
+			default:
+				// Something went *seriously* wrong.
+				/*
+					Per https://pkg.go.dev/os#Stat:
+						"If there is an error, it will be of type *PathError."
+				*/
+				panic(err)
+			}
+		}
+	}
+
+	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())
@@ -1539,7 +1671,9 @@ func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue boo
 }
 
 // requireCheckFieldValue is a func for check field value
-func requireCheckFieldValue(fl FieldLevel, param string, value string, defaultNotFoundValue bool) bool {
+func requireCheckFieldValue(
+	fl FieldLevel, param string, value string, defaultNotFoundValue bool,
+) bool {
 	field, kind, _, found := fl.GetStructFieldOKAdvanced2(fl.Parent(), param)
 	if !found {
 		return defaultNotFoundValue
@@ -1623,10 +1757,10 @@ func excludedUnless(fl FieldLevel) bool {
 	}
 	for i := 0; i < len(params); i += 2 {
 		if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
-			return true
+			return !hasValue(fl)
 		}
 	}
-	return !hasValue(fl)
+	return true
 }
 
 // excludedWith is the validation function
@@ -2275,7 +2409,7 @@ func isFQDN(fl FieldLevel) bool {
 	return fqdnRegexRFC1123.MatchString(val)
 }
 
-// isDir is the validation function for validating if the current field's value is a valid directory.
+// isDir is the validation function for validating if the current field's value is a valid existing directory.
 func isDir(fl FieldLevel) bool {
 	field := fl.Field()
 
@@ -2291,6 +2425,64 @@ func isDir(fl FieldLevel) bool {
 	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
 }
 
+// isDirPath is the validation function for validating if the current field's value is a valid directory.
+func isDirPath(fl FieldLevel) bool {
+
+	var exists bool
+	var err error
+
+	field := fl.Field()
+
+	// If it exists, it obviously is valid.
+	// This is done first to avoid code duplication and unnecessary additional logic.
+	if exists = isDir(fl); exists {
+		return true
+	}
+
+	// It does not exist but may still be a valid path.
+	switch field.Kind() {
+	case reflect.String:
+		// Every OS allows for whitespace, but none
+		// let you use a dir with no name (to my knowledge).
+		// Unless you're dealing with raw inodes, but I digress.
+		if strings.TrimSpace(field.String()) == "" {
+			return false
+		}
+		if _, err = os.Stat(field.String()); err != nil {
+			switch t := err.(type) {
+			case *fs.PathError:
+				if t.Err == syscall.EINVAL {
+					// It's definitely an invalid character in the path.
+					return false
+				}
+				// It could be a permission error, a does-not-exist error, etc.
+				// Out-of-scope for this validation, though.
+				// Lastly, we make sure it is a directory.
+				if strings.HasSuffix(field.String(), string(os.PathSeparator)) {
+					return true
+				} else {
+					return false
+				}
+			default:
+				// Something went *seriously* wrong.
+				/*
+					Per https://pkg.go.dev/os#Stat:
+						"If there is an error, it will be of type *PathError."
+				*/
+				panic(err)
+			}
+		}
+		// We repeat the check here to make sure it is an explicit directory in case the above os.Stat didn't trigger an error.
+		if strings.HasSuffix(field.String(), string(os.PathSeparator)) {
+			return true
+		} else {
+			return false
+		}
+	}
+
+	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
+}
+
 // isJSON is the validation function for validating if the current field's value is a valid json string.
 func isJSON(fl FieldLevel) bool {
 	field := fl.Field()
@@ -2316,7 +2508,9 @@ func isHostnamePort(fl FieldLevel) bool {
 		return false
 	}
 	// Port must be a iny <= 65535.
-	if portNum, err := strconv.ParseInt(port, 10, 32); err != nil || portNum > 65535 || portNum < 1 {
+	if portNum, err := strconv.ParseInt(
+		port, 10, 32,
+	); err != nil || portNum > 65535 || portNum < 1 {
 		return false
 	}
 
@@ -2487,6 +2681,12 @@ func isDnsRFC1035LabelFormat(fl FieldLevel) bool {
 	return dnsRegexRFC1035Label.MatchString(val)
 }
 
+// isMongoDB is the validation function for validating if the current field's value is valid mongoDB objectID
+func isMongoDB(fl FieldLevel) bool {
+	val := fl.Field().String()
+	return mongodbRegex.MatchString(val)
+}
+
 // isCreditCard is the validation function for validating if the current field's value is a valid credit card number
 func isCreditCard(fl FieldLevel) bool {
 	val := fl.Field().String()
@@ -2524,3 +2724,9 @@ func isCreditCard(fl FieldLevel) bool {
 	}
 	return (sum % 10) == 0
 }
+
+// isCron is the validation function for validating if the current field's value is a valid cron expression
+func isCron(fl FieldLevel) bool {
+	cronString := fl.Field().String()
+	return cronRegex.MatchString(cronString)
+}
diff --git a/cache.go b/cache.go
index 7b84c91..bbfd2a4 100644
--- a/cache.go
+++ b/cache.go
@@ -120,7 +120,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
 	var fld reflect.StructField
 	var tag string
 	var customName string
-	
+
 	for i := 0; i < numFields; i++ {
 
 		fld = typ.Field(i)
diff --git a/doc.go b/doc.go
index 7341c67..bffabc4 100644
--- a/doc.go
+++ b/doc.go
@@ -7,7 +7,7 @@ and has the ability to dive into arrays and maps of any type.
 
 see more examples https://github.com/go-playground/validator/tree/master/_examples
 
-Singleton
+# Singleton
 
 Validator is designed to be thread-safe and used as a singleton instance.
 It caches information about your struct and validations,
@@ -15,7 +15,7 @@ in essence only parsing your validation tags once per struct type.
 Using multiple instances neglects the benefit of caching.
 The not thread-safe functions are explicitly marked as such in the documentation.
 
-Validation Functions Return Type error
+# Validation Functions Return Type error
 
 Doing things this way is actually the way the standard library does, see the
 file.Open method here:
@@ -34,7 +34,7 @@ if the error returned is not nil, and if it's not check if error is
 InvalidValidationError ( if necessary, most of the time it isn't ) type cast
 it to type ValidationErrors like so err.(validator.ValidationErrors).
 
-Custom Validation Functions
+# Custom Validation Functions
 
 Custom Validation functions can be added. Example:
 
@@ -52,21 +52,21 @@ Custom Validation functions can be added. Example:
 	// NOTES: using the same tag name as an existing function
 	//        will overwrite the existing one
 
-Cross-Field Validation
+# Cross-Field Validation
 
 Cross-Field Validation can be done via the following tags:
-	- eqfield
-	- nefield
-	- gtfield
-	- gtefield
-	- ltfield
-	- ltefield
-	- eqcsfield
-	- necsfield
-	- gtcsfield
-	- gtecsfield
-	- ltcsfield
-	- ltecsfield
+  - eqfield
+  - nefield
+  - gtfield
+  - gtefield
+  - ltfield
+  - ltefield
+  - eqcsfield
+  - necsfield
+  - gtcsfield
+  - gtecsfield
+  - ltcsfield
+  - ltecsfield
 
 If, however, some custom cross-field validation is required, it can be done
 using a custom validation.
@@ -106,7 +106,7 @@ used "eqcsfield" it could be multiple levels down. Example:
 	//       whatever you pass, struct, field...
 	//       when calling validate.Field(field, tag) val will be nil
 
-Multiple Validators
+# Multiple Validators
 
 Multiple validators on a field will process in the order defined. Example:
 
@@ -124,7 +124,7 @@ Bad Validator definitions are not handled by the library. Example:
 
 	// this definition of min max will never succeed
 
-Using Validator Tags
+# 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
@@ -150,20 +150,18 @@ so the above will become excludesall=0x7C
 		Field `validate:"excludesall=0x7C"` // GOOD! Use the UTF-8 hex representation.
 	}
 
-
-Baked In Validators and Tags
+# Baked In Validators and Tags
 
 Here is a list of the current built in validators:
 
-
-Skip Field
+# Skip Field
 
 Tells the validation to skip this struct field; this is particularly
 handy in ignoring embedded structs from being validated. (Usage: -)
-	Usage: -
 
+	Usage: -
 
-Or Operator
+# Or Operator
 
 This is the 'or' operator allowing multiple validators to be used and
 accepted. (Usage: rgb|rgba) <-- this would allow either rgb or rgba
@@ -172,7 +170,7 @@ colors to be accepted. This can also be combined with 'and' for example
 
 	Usage: |
 
-StructOnly
+# 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
@@ -182,13 +180,13 @@ NOTE: only "required" and "omitempty" can be used on a struct itself.
 
 	Usage: structonly
 
-NoStructLevel
+# NoStructLevel
 
 Same as structonly tag except that any struct level validations will not run.
 
 	Usage: nostructlevel
 
-Omit Empty
+# Omit Empty
 
 Allows conditional validation, for example if a field is not set with
 a value (Determined by the "required" validator) then other validation
@@ -196,7 +194,7 @@ such as min or max won't run, but if a value is set validation will run.
 
 	Usage: omitempty
 
-Dive
+# 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.
@@ -232,19 +230,19 @@ require another 'keys' and 'endkeys' tag. These tags are only valid for maps.
 
 Example #1
 
-	map[string]string with validation tag "gt=0,dive,keys,eg=1|eq=2,endkeys,required"
+	map[string]string with validation tag "gt=0,dive,keys,eq=1|eq=2,endkeys,required"
 	// gt=0 will be applied to the map itself
-	// eg=1|eq=2 will be applied to the map keys
+	// eq=1|eq=2 will be applied to the map keys
 	// required will be applied to map values
 
 Example #2
 
 	map[[2]string]string with validation tag "gt=0,dive,keys,dive,eq=1|eq=2,endkeys,required"
 	// gt=0 will be applied to the map itself
-	// eg=1|eq=2 will be applied to each array element in the the map keys
+	// eq=1|eq=2 will be applied to each array element in the the map keys
 	// required will be applied to map values
 
-Required
+# 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
@@ -253,7 +251,7 @@ ensures the value is not nil.
 
 	Usage: required
 
-Required If
+# Required If
 
 The field under validation must be present and not empty only if all
 the other specified fields are equal to the value following the specified
@@ -270,7 +268,7 @@ Examples:
 	// require the field if the Field1 and Field2 is equal to the value respectively:
 	Usage: required_if=Field1 foo Field2 bar
 
-Required Unless
+# Required Unless
 
 The field under validation must be present and not empty unless all
 the other specified fields are equal to the value following the specified
@@ -287,7 +285,7 @@ Examples:
 	// require the field unless the Field1 and Field2 is equal to the value respectively:
 	Usage: required_unless=Field1 foo Field2 bar
 
-Required With
+# Required With
 
 The field under validation must be present and not empty only if any
 of the other specified fields are present. For strings ensures value is
@@ -304,7 +302,7 @@ Examples:
 	// require the field if the Field1 or Field2 is present:
 	Usage: required_with=Field1 Field2
 
-Required With All
+# Required With All
 
 The field under validation must be present and not empty only if all
 of the other specified fields are present. For strings ensures value is
@@ -318,7 +316,7 @@ Example:
 	// require the field if the Field1 and Field2 is present:
 	Usage: required_with_all=Field1 Field2
 
-Required Without
+# Required Without
 
 The field under validation must be present and not empty only when any
 of the other specified fields are not present. For strings ensures value is
@@ -335,7 +333,7 @@ Examples:
 	// require the field if the Field1 or Field2 is not present:
 	Usage: required_without=Field1 Field2
 
-Required Without All
+# Required Without All
 
 The field under validation must be present and not empty only when all
 of the other specified fields are not present. For strings ensures value is
@@ -349,7 +347,7 @@ Example:
 	// require the field if the Field1 and Field2 is not present:
 	Usage: required_without_all=Field1 Field2
 
-Excluded If
+# Excluded If
 
 The field under validation must not be present or not empty only if all
 the other specified fields are equal to the value following the specified
@@ -366,7 +364,7 @@ Examples:
 	// exclude the field if the Field1 and Field2 is equal to the value respectively:
 	Usage: excluded_if=Field1 foo Field2 bar
 
-Excluded Unless
+# Excluded Unless
 
 The field under validation must not be present or empty unless all
 the other specified fields are equal to the value following the specified
@@ -383,14 +381,14 @@ Examples:
 	// exclude the field unless the Field1 and Field2 is equal to the value respectively:
 	Usage: excluded_unless=Field1 foo Field2 bar
 
-Is Default
+# Is Default
 
 This validates that the value is the default value and is almost the
 opposite of required.
 
 	Usage: isdefault
 
-Length
+# Length
 
 For numbers, length will ensure that the value is
 equal to the parameter given. For strings, it checks that
@@ -408,7 +406,7 @@ in the parameter.
 
 	Usage: len=1h30m
 
-Maximum
+# Maximum
 
 For numbers, max will ensure that the value is
 less than or equal to the parameter given. For strings, it checks
@@ -426,7 +424,7 @@ duration given in the parameter.
 
 	Usage: max=1h30m
 
-Minimum
+# Minimum
 
 For numbers, min will ensure that the value is
 greater or equal to the parameter given. For strings, it checks that
@@ -444,7 +442,7 @@ the duration given in the parameter.
 
 	Usage: min=1h30m
 
-Equals
+# Equals
 
 For strings & numbers, eq will ensure that the value is
 equal to the parameter given. For slices, arrays, and maps,
@@ -461,7 +459,7 @@ in the parameter.
 
 	Usage: eq=1h30m
 
-Not Equal
+# Not Equal
 
 For strings & numbers, ne will ensure that the value is not
 equal to the parameter given. For slices, arrays, and maps,
@@ -478,7 +476,7 @@ given in the parameter.
 
 	Usage: ne=1h30m
 
-One Of
+# One Of
 
 For strings, ints, and uints, oneof will ensure that the value
 is one of the values in the parameter.  The parameter should be
@@ -486,11 +484,11 @@ a list of values separated by whitespace. Values may be
 strings or numbers. To match strings with spaces in them, include
 the target string between single quotes.
 
-    Usage: oneof=red green
-           oneof='red green' 'blue yellow'
-           oneof=5 7 9
+	Usage: oneof=red green
+	       oneof='red green' 'blue yellow'
+	       oneof=5 7 9
 
-Greater Than
+# Greater Than
 
 For numbers, this will ensure that the value is greater than the
 parameter given. For strings, it checks that the string length
@@ -514,7 +512,7 @@ given in the parameter.
 
 	Usage: gt=1h30m
 
-Greater Than or Equal
+# Greater Than or Equal
 
 Same as 'min' above. Kept both to make terminology with 'len' easier.
 
@@ -535,7 +533,7 @@ the duration given in the parameter.
 
 	Usage: gte=1h30m
 
-Less Than
+# 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
@@ -558,7 +556,7 @@ in the parameter.
 
 	Usage: lt=1h30m
 
-Less Than or Equal
+# Less Than or Equal
 
 Same as 'max' above. Kept both to make terminology with 'len' easier.
 
@@ -579,7 +577,7 @@ duration given in the parameter.
 
 	Usage: lte=1h30m
 
-Field Equals Another Field
+# Field Equals Another Field
 
 This will validate the field value against another fields value either within
 a struct or passed in field.
@@ -601,7 +599,7 @@ to the top level struct.
 
 	Usage: eqcsfield=InnerStructField.Field)
 
-Field Does Not Equal Another 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.
@@ -623,7 +621,7 @@ relative to the top level struct.
 
 	Usage: necsfield=InnerStructField.Field
 
-Field Greater Than Another Field
+# Field Greater Than Another Field
 
 Only valid for Numbers, time.Duration and time.Time types, this will validate
 the field value against another fields value either within a struct or passed in
@@ -639,14 +637,14 @@ Example #2:
 	// Validating by field:
 	validate.VarWithValue(start, end, "gtfield")
 
-Field Greater Than Another Relative Field
+# 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
+# Field Greater Than or Equal To Another Field
 
 Only valid for Numbers, time.Duration and time.Time types, this will validate
 the field value against another fields value either within a struct or passed in
@@ -662,14 +660,14 @@ Example #2:
 	// Validating by field:
 	validate.VarWithValue(start, end, "gtefield")
 
-Field Greater Than or Equal To Another Relative Field
+# 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
+# Less Than Another Field
 
 Only valid for Numbers, time.Duration and time.Time types, this will validate
 the field value against another fields value either within a struct or passed in
@@ -685,14 +683,14 @@ Example #2:
 	// Validating by field:
 	validate.VarWithValue(start, end, "ltfield")
 
-Less Than Another Relative Field
+# 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
+# Less Than or Equal To Another Field
 
 Only valid for Numbers, time.Duration and time.Time types, this will validate
 the field value against another fields value either within a struct or passed in
@@ -708,14 +706,14 @@ Example #2:
 	// Validating by field:
 	validate.VarWithValue(start, end, "ltefield")
 
-Less Than or Equal To Another Relative Field
+# 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
 
-Field Contains Another Field
+# Field Contains Another Field
 
 This does the same as contains except for struct fields. It should only be used
 with string types. See the behavior of reflect.Value.String() for behavior on
@@ -723,7 +721,7 @@ other types.
 
 	Usage: containsfield=InnerStructField.Field
 
-Field Excludes Another Field
+# Field Excludes Another Field
 
 This does the same as excludes except for struct fields. It should only be used
 with string types. See the behavior of reflect.Value.String() for behavior on
@@ -731,7 +729,7 @@ other types.
 
 	Usage: excludesfield=InnerStructField.Field
 
-Unique
+# Unique
 
 For arrays & slices, unique will ensure that there are no duplicates.
 For maps, unique will ensure that there are no duplicate values.
@@ -744,44 +742,44 @@ in a field of the struct specified via a parameter.
 	// For slices of struct:
 	Usage: unique=field
 
-Alpha Only
+# Alpha Only
 
 This validates that a string value contains ASCII alpha characters only
 
 	Usage: alpha
 
-Alphanumeric
+# Alphanumeric
 
 This validates that a string value contains ASCII alphanumeric characters only
 
 	Usage: alphanum
 
-Alpha Unicode
+# Alpha Unicode
 
 This validates that a string value contains unicode alpha characters only
 
 	Usage: alphaunicode
 
-Alphanumeric Unicode
+# Alphanumeric Unicode
 
 This validates that a string value contains unicode alphanumeric characters only
 
 	Usage: alphanumunicode
 
-Boolean
+# Boolean
 
 This validates that a string value can successfully be parsed into a boolean with strconv.ParseBool
 
 	Usage: boolean
 
-Number
+# Number
 
 This validates that a string value contains number values only.
 For integers or float it returns true.
 
 	Usage: number
 
-Numeric
+# Numeric
 
 This validates that a string value contains a basic numeric value.
 basic excludes exponents etc...
@@ -789,63 +787,63 @@ for integers or float it returns true.
 
 	Usage: numeric
 
-Hexadecimal String
+# Hexadecimal String
 
 This validates that a string value contains a valid hexadecimal.
 
 	Usage: hexadecimal
 
-Hexcolor String
+# Hexcolor String
 
 This validates that a string value contains a valid hex color including
 hashtag (#)
 
-		Usage: hexcolor
+	Usage: hexcolor
 
-Lowercase String
+# Lowercase String
 
 This validates that a string value contains only lowercase characters. An empty string is not a valid lowercase string.
 
 	Usage: lowercase
 
-Uppercase String
+# Uppercase String
 
 This validates that a string value contains only uppercase characters. An empty string is not a valid uppercase string.
 
 	Usage: uppercase
 
-RGB String
+# RGB String
 
 This validates that a string value contains a valid rgb color
 
 	Usage: rgb
 
-RGBA String
+# RGBA String
 
 This validates that a string value contains a valid rgba color
 
 	Usage: rgba
 
-HSL String
+# HSL String
 
 This validates that a string value contains a valid hsl color
 
 	Usage: hsl
 
-HSLA String
+# HSLA String
 
 This validates that a string value contains a valid hsla color
 
 	Usage: hsla
 
-E.164 Phone Number String
+# E.164 Phone Number String
 
 This validates that a string value contains a valid E.164 Phone number
 https://en.wikipedia.org/wiki/E.164 (ex. +1123456789)
 
 	Usage: e164
 
-E-mail String
+# 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
@@ -853,19 +851,20 @@ does any email provider accept all possibilities.
 
 	Usage: email
 
-JSON String
+# JSON String
 
 This validates that a string value is valid JSON
 
 	Usage: json
 
-JWT String
+# JWT String
 
 This validates that a string value is a valid JWT
 
 	Usage: jwt
 
-File path
+
+# File
 
 This validates that a string value contains a valid file path and that
 the file exists on the machine.
@@ -873,7 +872,17 @@ This is done using os.Stat, which is a platform independent function.
 
 	Usage: file
 
-URL String
+
+# File Path
+
+This validates that a string value contains a valid file path but does not
+validate the existence of that file.
+This is done using os.Stat, which is a platform independent function.
+
+	Usage: filepath
+
+
+# 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
@@ -881,21 +890,21 @@ a schema for example http:// or rtmp://
 
 	Usage: url
 
-URI String
+# URI String
 
 This validates that a string value contains a valid uri
 This will accept any uri the golang request uri accepts
 
 	Usage: uri
 
-Urn RFC 2141 String
+# Urn RFC 2141 String
 
 This validataes that a string value contains a valid URN
 according to the RFC 2141 spec.
 
 	Usage: urn_rfc2141
 
-Base64 String
+# 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
@@ -904,7 +913,7 @@ this with the omitempty tag.
 
 	Usage: base64
 
-Base64URL String
+# Base64URL String
 
 This validates that a string value contains a valid base64 URL safe value
 according the the RFC4648 spec.
@@ -914,7 +923,19 @@ you can use this with the omitempty tag.
 
 	Usage: base64url
 
-Bitcoin Address
+
+# Base64RawURL String
+
+This validates that a string value contains a valid base64 URL safe value,
+but without = padding, according the the RFC4648 spec, section 3.2.
+Although an empty string is a valid base64 URL safe value, 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: base64url
+
+
+# Bitcoin Address
 
 This validates that a string value contains a valid bitcoin address.
 The format of the string is checked to ensure it matches one of the three formats
@@ -930,266 +951,266 @@ Special thanks to Pieter Wuille for providng reference implementations.
 
 	Usage: btc_addr_bech32
 
-Ethereum Address
+# Ethereum Address
 
 This validates that a string value contains a valid ethereum address.
 The format of the string is checked to ensure it matches the standard Ethereum address format.
 
 	Usage: eth_addr
 
-Contains
+# Contains
 
 This validates that a string value contains the substring value.
 
 	Usage: contains=@
 
-Contains Any
+# Contains Any
 
 This validates that a string value contains any Unicode code points
 in the substring value.
 
 	Usage: containsany=!@#?
 
-Contains Rune
+# Contains Rune
 
 This validates that a string value contains the supplied rune value.
 
 	Usage: containsrune=@
 
-Excludes
+# Excludes
 
 This validates that a string value does not contain the substring value.
 
 	Usage: excludes=@
 
-Excludes All
+# Excludes All
 
 This validates that a string value does not contain any Unicode code
 points in the substring value.
 
 	Usage: excludesall=!@#?
 
-Excludes Rune
+# Excludes Rune
 
 This validates that a string value does not contain the supplied rune value.
 
 	Usage: excludesrune=@
 
-Starts With
+# Starts With
 
 This validates that a string value starts with the supplied string value
 
 	Usage: startswith=hello
 
-Ends With
+# Ends With
 
 This validates that a string value ends with the supplied string value
 
 	Usage: endswith=goodbye
 
-Does Not Start With
+# Does Not Start With
 
 This validates that a string value does not start with the supplied string value
 
 	Usage: startsnotwith=hello
 
-Does Not End With
+# Does Not End With
 
 This validates that a string value does not end with the supplied string value
 
 	Usage: endsnotwith=goodbye
 
-International Standard Book Number
+# International Standard Book Number
 
 This validates that a string value contains a valid isbn10 or isbn13 value.
 
 	Usage: isbn
 
-International Standard Book Number 10
+# International Standard Book Number 10
 
 This validates that a string value contains a valid isbn10 value.
 
 	Usage: isbn10
 
-International Standard Book Number 13
+# International Standard Book Number 13
 
 This validates that a string value contains a valid isbn13 value.
 
 	Usage: isbn13
 
-Universally Unique Identifier UUID
+# Universally Unique Identifier UUID
 
 This validates that a string value contains a valid UUID. Uppercase UUID values will not pass - use `uuid_rfc4122` instead.
 
 	Usage: uuid
 
-Universally Unique Identifier UUID v3
+# Universally Unique Identifier UUID v3
 
 This validates that a string value contains a valid version 3 UUID.  Uppercase UUID values will not pass - use `uuid3_rfc4122` instead.
 
 	Usage: uuid3
 
-Universally Unique Identifier UUID v4
+# Universally Unique Identifier UUID v4
 
 This validates that a string value contains a valid version 4 UUID.  Uppercase UUID values will not pass - use `uuid4_rfc4122` instead.
 
 	Usage: uuid4
 
-Universally Unique Identifier UUID v5
+# Universally Unique Identifier UUID v5
 
 This validates that a string value contains a valid version 5 UUID.  Uppercase UUID values will not pass - use `uuid5_rfc4122` instead.
 
 	Usage: uuid5
 
-Universally Unique Lexicographically Sortable Identifier ULID
+# Universally Unique Lexicographically Sortable Identifier ULID
 
 This validates that a string value contains a valid ULID value.
 
 	Usage: ulid
 
-ASCII
+# 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
+# Printable ASCII
 
 This validates that a string value contains only printable ASCII characters.
 NOTE: if the string is blank, this validates as true.
 
 	Usage: printascii
 
-Multi-Byte Characters
+# 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
+# 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
+# Latitude
 
 This validates that a string value contains a valid latitude.
 
 	Usage: latitude
 
-Longitude
+# Longitude
 
 This validates that a string value contains a valid longitude.
 
 	Usage: longitude
 
-Social Security Number SSN
+# Social Security Number SSN
 
 This validates that a string value contains a valid U.S. Social Security Number.
 
 	Usage: ssn
 
-Internet Protocol Address IP
+# Internet Protocol Address IP
 
 This validates that a string value contains a valid IP Address.
 
 	Usage: ip
 
-Internet Protocol Address IPv4
+# Internet Protocol Address IPv4
 
 This validates that a string value contains a valid v4 IP Address.
 
 	Usage: ipv4
 
-Internet Protocol Address IPv6
+# Internet Protocol Address IPv6
 
 This validates that a string value contains a valid v6 IP Address.
 
 	Usage: ipv6
 
-Classless Inter-Domain Routing CIDR
+# Classless Inter-Domain Routing CIDR
 
 This validates that a string value contains a valid CIDR Address.
 
 	Usage: cidr
 
-Classless Inter-Domain Routing CIDRv4
+# Classless Inter-Domain Routing CIDRv4
 
 This validates that a string value contains a valid v4 CIDR Address.
 
 	Usage: cidrv4
 
-Classless Inter-Domain Routing CIDRv6
+# Classless Inter-Domain Routing CIDRv6
 
 This validates that a string value contains a valid v6 CIDR Address.
 
 	Usage: cidrv6
 
-Transmission Control Protocol Address TCP
+# Transmission Control Protocol Address TCP
 
 This validates that a string value contains a valid resolvable TCP Address.
 
 	Usage: tcp_addr
 
-Transmission Control Protocol Address TCPv4
+# Transmission Control Protocol Address TCPv4
 
 This validates that a string value contains a valid resolvable v4 TCP Address.
 
 	Usage: tcp4_addr
 
-Transmission Control Protocol Address TCPv6
+# Transmission Control Protocol Address TCPv6
 
 This validates that a string value contains a valid resolvable v6 TCP Address.
 
 	Usage: tcp6_addr
 
-User Datagram Protocol Address UDP
+# User Datagram Protocol Address UDP
 
 This validates that a string value contains a valid resolvable UDP Address.
 
 	Usage: udp_addr
 
-User Datagram Protocol Address UDPv4
+# User Datagram Protocol Address UDPv4
 
 This validates that a string value contains a valid resolvable v4 UDP Address.
 
 	Usage: udp4_addr
 
-User Datagram Protocol Address UDPv6
+# User Datagram Protocol Address UDPv6
 
 This validates that a string value contains a valid resolvable v6 UDP Address.
 
 	Usage: udp6_addr
 
-Internet Protocol Address IP
+# Internet Protocol Address IP
 
 This validates that a string value contains a valid resolvable IP Address.
 
 	Usage: ip_addr
 
-Internet Protocol Address IPv4
+# Internet Protocol Address IPv4
 
 This validates that a string value contains a valid resolvable v4 IP Address.
 
 	Usage: ip4_addr
 
-Internet Protocol Address IPv6
+# Internet Protocol Address IPv6
 
 This validates that a string value contains a valid resolvable v6 IP Address.
 
 	Usage: ip6_addr
 
-Unix domain socket end point Address
+# Unix domain socket end point Address
 
 This validates that a string value contains a valid Unix Address.
 
 	Usage: unix_addr
 
-Media Access Control Address MAC
+# Media Access Control Address MAC
 
 This validates that a string value contains a valid MAC Address.
 
@@ -1199,13 +1220,13 @@ Note: See Go's ParseMAC for accepted formats and types:
 
 	http://golang.org/src/net/mac.go?s=866:918#L29
 
-Hostname RFC 952
+# Hostname RFC 952
 
 This validates that a string value is a valid Hostname according to RFC 952 https://tools.ietf.org/html/rfc952
 
 	Usage: hostname
 
-Hostname RFC 1123
+# Hostname RFC 1123
 
 This validates that a string value is a valid Hostname according to RFC 1123 https://tools.ietf.org/html/rfc1123
 
@@ -1217,28 +1238,28 @@ This validates that a string value contains a valid FQDN.
 
 	Usage: fqdn
 
-HTML Tags
+# HTML Tags
 
 This validates that a string value appears to be an HTML element tag
 including those described at https://developer.mozilla.org/en-US/docs/Web/HTML/Element
 
 	Usage: html
 
-HTML Encoded
+# HTML Encoded
 
 This validates that a string value is a proper character reference in decimal
 or hexadecimal format
 
 	Usage: html_encoded
 
-URL Encoded
+# URL Encoded
 
 This validates that a string value is percent-encoded (URL encoded) according
 to https://tools.ietf.org/html/rfc3986#section-2.1
 
 	Usage: url_encoded
 
-Directory
+# Directory
 
 This validates that a string value contains a valid directory and that
 it exists on the machine.
@@ -1246,42 +1267,54 @@ This is done using os.Stat, which is a platform independent function.
 
 	Usage: dir
 
-HostPort
+
+# Directory Path
+
+This validates that a string value contains a valid directory but does
+not validate the existence of that directory.
+This is done using os.Stat, which is a platform independent function.
+It is safest to suffix the string with os.PathSeparator if the directory
+may not exist at the time of validation.
+
+	Usage: dirpath
+
+
+# HostPort
 
 This validates that a string value contains a valid DNS hostname and port that
 can be used to valiate fields typically passed to sockets and connections.
 
 	Usage: hostname_port
 
-Datetime
+# Datetime
 
 This validates that a string value is a valid datetime based on the supplied datetime format.
 Supplied format must match the official Go time format layout as documented in https://golang.org/pkg/time/
 
 	Usage: datetime=2006-01-02
 
-Iso3166-1 alpha-2
+# Iso3166-1 alpha-2
 
 This validates that a string value is a valid country code based on iso3166-1 alpha-2 standard.
 see: https://www.iso.org/iso-3166-country-codes.html
 
 	Usage: iso3166_1_alpha2
 
-Iso3166-1 alpha-3
+# Iso3166-1 alpha-3
 
 This validates that a string value is a valid country code based on iso3166-1 alpha-3 standard.
 see: https://www.iso.org/iso-3166-country-codes.html
 
 	Usage: iso3166_1_alpha3
 
-Iso3166-1 alpha-numeric
+# Iso3166-1 alpha-numeric
 
 This validates that a string value is a valid country code based on iso3166-1 alpha-numeric standard.
 see: https://www.iso.org/iso-3166-country-codes.html
 
 	Usage: iso3166_1_alpha3
 
-BCP 47 Language Tag
+# BCP 47 Language Tag
 
 This validates that a string value is a valid BCP 47 language tag, as parsed by language.Parse.
 More information on https://pkg.go.dev/golang.org/x/text/language
@@ -1295,14 +1328,14 @@ More information on https://www.iso.org/standard/60390.html
 
 	Usage: bic
 
-RFC 1035 label
+# RFC 1035 label
 
 This validates that a string value is a valid dns RFC 1035 label, defined in RFC 1035.
 More information on https://datatracker.ietf.org/doc/html/rfc1035
 
 	Usage: dns_rfc1035_label
 
-TimeZone
+# TimeZone
 
 This validates that a string value is a valid time zone based on the time zone database present on the system.
 Although empty value and Local value are allowed by time.LoadLocation golang function, they are not allowed by this validator.
@@ -1310,19 +1343,33 @@ More information on https://golang.org/pkg/time/#LoadLocation
 
 	Usage: timezone
 
-Semantic Version
+# Semantic Version
 
 This validates that a string value is a valid semver version, defined in Semantic Versioning 2.0.0.
 More information on https://semver.org/
 
 	Usage: semver
 
-Credit Card
+# Credit Card
 
 This validates that a string value contains a valid credit card number using Luhn algoritm.
 
 	Usage: credit_card
 
+
+#MongoDb ObjectID
+
+This validates that a string is a valid 24 character hexadecimal string.
+
+  Usage: mongodb
+
+
+# Cron
+
+This validates that a string value contains a valid cron expression.
+
+	Usage: cron
+
 Alias Validators and Tags
 
 NOTE: When returning an error, the tag returned in "FieldError" will be
@@ -1354,7 +1401,7 @@ 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!
 
-Non standard validators
+# 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.
@@ -1383,7 +1430,7 @@ Here is a list of the current non standard validators:
 
 		Usage: notblank
 
-Panics
+# Panics
 
 This package panics when bad input is provided, this is by design, bad code like
 that should not make it to production.
diff --git a/go.mod b/go.mod
index e281ec0..aa7527d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,19 +1,14 @@
 module github.com/go-playground/validator/v10
 
-go 1.13
+go 1.18
 
 require (
-	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/go-playground/assert/v2 v2.0.1
-	github.com/go-playground/locales v0.14.0
-	github.com/go-playground/universal-translator v0.18.0
-	github.com/kr/pretty v0.3.0 // indirect
-	github.com/leodido/go-urn v1.2.1
-	github.com/rogpeppe/go-internal v1.8.0 // indirect
-	github.com/stretchr/testify v1.7.0 // indirect
-	golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
-	golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
-	golang.org/x/text v0.3.7
-	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+	github.com/go-playground/assert/v2 v2.2.0
+	github.com/go-playground/locales v0.14.1
+	github.com/go-playground/universal-translator v0.18.1
+	github.com/leodido/go-urn v1.2.2
+	golang.org/x/crypto v0.7.0
+	golang.org/x/text v0.8.0
 )
+
+require golang.org/x/sys v0.6.0 // indirect
diff --git a/go.sum b/go.sum
index a1c43c3..cce9416 100644
--- a/go.sum
+++ b/go.sum
@@ -1,52 +1,31 @@
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
-github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
-github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
-github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
-github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
-github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
+github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
-github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
-golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
-golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/regexes.go b/regexes.go
index 9c1c634..a65ac6f 100644
--- a/regexes.go
+++ b/regexes.go
@@ -19,6 +19,7 @@ const (
 	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})$"
+	base64RawURLRegexString          = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2,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}$"
@@ -64,6 +65,8 @@ const (
 	bicRegexString                   = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$`
 	semverRegexString                = `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` // numbered capture groups https://semver.org/
 	dnsRegexStringRFC1035Label       = "^[a-z]([-a-z0-9]*[a-z0-9]){0,62}$"
+	mongodbRegexString               = "^[a-f\\d]{24}$"
+	cronRegexString                  = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})`
 )
 
 var (
@@ -83,6 +86,7 @@ var (
 	emailRegex                 = regexp.MustCompile(emailRegexString)
 	base64Regex                = regexp.MustCompile(base64RegexString)
 	base64URLRegex             = regexp.MustCompile(base64URLRegexString)
+	base64RawURLRegex          = regexp.MustCompile(base64RawURLRegexString)
 	iSBN10Regex                = regexp.MustCompile(iSBN10RegexString)
 	iSBN13Regex                = regexp.MustCompile(iSBN13RegexString)
 	uUID3Regex                 = regexp.MustCompile(uUID3RegexString)
@@ -118,8 +122,6 @@ var (
 	btcUpperAddressRegexBech32 = regexp.MustCompile(btcAddressUpperRegexStringBech32)
 	btcLowerAddressRegexBech32 = regexp.MustCompile(btcAddressLowerRegexStringBech32)
 	ethAddressRegex            = regexp.MustCompile(ethAddressRegexString)
-	ethAddressRegexUpper       = regexp.MustCompile(ethAddressUpperRegexString)
-	ethAddressRegexLower       = regexp.MustCompile(ethAddressLowerRegexString)
 	uRLEncodedRegex            = regexp.MustCompile(uRLEncodedRegexString)
 	hTMLEncodedRegex           = regexp.MustCompile(hTMLEncodedRegexString)
 	hTMLRegex                  = regexp.MustCompile(hTMLRegexString)
@@ -128,4 +130,6 @@ var (
 	bicRegex                   = regexp.MustCompile(bicRegexString)
 	semverRegex                = regexp.MustCompile(semverRegexString)
 	dnsRegexRFC1035Label       = regexp.MustCompile(dnsRegexStringRFC1035Label)
+	mongodbRegex               = regexp.MustCompile(mongodbRegexString)
+	cronRegex                  = regexp.MustCompile(cronRegexString)
 )
diff --git a/translations/en/en.go b/translations/en/en.go
index ee05f91..0668bb2 100644
--- a/translations/en/en.go
+++ b/translations/en/en.go
@@ -1266,6 +1266,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
 			translation: "{0} must contain a valid MAC address",
 			override:    false,
 		},
+		{
+			tag:         "fqdn",
+			translation: "{0} must be a valid FQDN",
+			override:    false,
+		},
 		{
 			tag:         "unique",
 			translation: "{0} must contain unique values",
@@ -1276,6 +1281,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
 			translation: "{0} must be a valid color",
 			override:    false,
 		},
+		{
+			tag:         "cron",
+			translation: "{0} must be a valid cron expression",
+			override:    false,
+		},
 		{
 			tag:         "oneof",
 			translation: "{0} must be one of [{1}]",
diff --git a/translations/es/es.go b/translations/es/es.go
index 2635408..73b06cf 100644
--- a/translations/es/es.go
+++ b/translations/es/es.go
@@ -29,6 +29,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
 			translation: "{0} es un campo requerido",
 			override:    false,
 		},
+		{
+			tag:         "required_if",
+			translation: "{0} es un campo requerido",
+			override:    false,
+		},
 		{
 			tag: "len",
 			customRegisFunc: func(ut ut.Translator) (err error) {
diff --git a/translations/it/it.go b/translations/it/it.go
index 0b46fc4..8f999a4 100644
--- a/translations/it/it.go
+++ b/translations/it/it.go
@@ -124,7 +124,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
 		{
 			tag: "min",
 			customRegisFunc: func(ut ut.Translator) (err error) {
-        
+
 				if err = ut.Add("min-string", "{0} deve essere lungo almeno {1}", false); err != nil {
 					return
 				}
@@ -432,7 +432,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
 		{
 			tag: "lte",
 			customRegisFunc: func(ut ut.Translator) (err error) {
-        
+
 				if err = ut.Add("lte-string", "{0} deve essere lungo al massimo {1}", false); err != nil {
 					return
 				}
@@ -1132,6 +1132,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
 			translation: "{0} deve essere un colore valido",
 			override:    false,
 		},
+		{
+			tag:         "cron",
+			translation: "{0} deve essere una stringa cron valida",
+			override:    false,
+		},
 		{
 			tag:             "oneof",
 			translation:     "{0} deve essere uno di [{1}]",
diff --git a/validator.go b/validator.go
index 80da095..6f6d53a 100644
--- a/validator.go
+++ b/validator.go
@@ -452,7 +452,6 @@ OUTER:
 			v.ct = ct
 
 			if !ct.fn(ctx, v) {
-
 				v.str1 = string(append(ns, cf.altName...))
 
 				if v.v.hasTagNameFunc {
diff --git a/validator_instance.go b/validator_instance.go
index 9493da4..51ae1aa 100644
--- a/validator_instance.go
+++ b/validator_instance.go
@@ -190,14 +190,14 @@ func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]int
 //
 // eg. to use the names which have been specified for JSON representations of structs, rather than normal Go field names:
 //
-//    validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
-//        name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
-//        // skip if tag key says it should be ignored
-//        if name == "-" {
-//            return ""
-//        }
-//        return name
-//    })
+//	validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
+//	    name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
+//	    // skip if tag key says it should be ignored
+//	    if name == "-" {
+//	        return ""
+//	    }
+//	    return name
+//	})
 func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) {
 	v.tagNameFunc = fn
 	v.hasTagNameFunc = true
@@ -613,7 +613,7 @@ func (v *Validate) Var(field interface{}, tag string) error {
 }
 
 // VarCtx validates a single variable using tag style validation and allows passing of contextual
-// validation validation information via context.Context.
+// validation information via context.Context.
 // eg.
 // var i int
 // validate.Var(i, "gt=1,lt=10")
@@ -632,6 +632,7 @@ func (v *Validate) VarCtx(ctx context.Context, field interface{}, tag string) (e
 	}
 
 	ctag := v.fetchCacheTag(tag)
+
 	val := reflect.ValueOf(field)
 	vd := v.pool.Get().(*validate)
 	vd.top = val


Test patch:
diff --git a/translations/en/en_test.go b/translations/en/en_test.go
index 9cb6deb..69f4ee0 100644
--- a/translations/en/en_test.go
+++ b/translations/en/en_test.go
@@ -130,6 +130,7 @@ func TestTranslations(t *testing.T) {
 		IPAddrv6          string            `validate:"ip6_addr"`
 		UinxAddr          string            `validate:"unix_addr"` // can't fail from within Go's net package currently, but maybe in the future
 		MAC               string            `validate:"mac"`
+		FQDN              string            `validate:"fqdn"`
 		IsColor           string            `validate:"iscolor"`
 		StrPtrMinLen      *string           `validate:"min=10"`
 		StrPtrMaxLen      *string           `validate:"max=1"`
@@ -226,6 +227,10 @@ func TestTranslations(t *testing.T) {
 			ns:       "Test.MAC",
 			expected: "MAC must contain a valid MAC address",
 		},
+		{
+			ns:       "Test.FQDN",
+			expected: "FQDN must be a valid FQDN",
+		},
 		{
 			ns:       "Test.IPAddr",
 			expected: "IPAddr must be a resolvable IP address",
diff --git a/translations/es/es_test.go b/translations/es/es_test.go
index 814f2ab..c2c4c81 100644
--- a/translations/es/es_test.go
+++ b/translations/es/es_test.go
@@ -35,6 +35,7 @@ func TestTranslations(t *testing.T) {
 		RequiredString    string            `validate:"required"`
 		RequiredNumber    int               `validate:"required"`
 		RequiredMultiple  []string          `validate:"required"`
+		RequiredIf        string            `validate:"required_if=Inner.RequiredIf abcd"`
 		LenString         string            `validate:"len=1"`
 		LenNumber         float64           `validate:"len=1113.00"`
 		LenMultiple       []string          `validate:"len=7"`
diff --git a/validator_test.go b/validator_test.go
index 7e314d6..a4beb9f 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -8,6 +8,7 @@ import (
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
+	"os"
 	"path/filepath"
 	"reflect"
 	"strings"
@@ -2038,10 +2039,10 @@ func TestCrossNamespaceFieldValidation(t *testing.T) {
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, _, _, ok = v.getStructFieldOKInternal(val, "Inner.CrazyNonExistantField")
+	_, _, _, ok = v.getStructFieldOKInternal(val, "Inner.CrazyNonExistantField")
 	Equal(t, ok, false)
 
-	current, _, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[101]")
+	_, _, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[101]")
 	Equal(t, ok, false)
 
 	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.Map[key3]")
@@ -3819,12 +3820,14 @@ func TestDataURIValidation(t *testing.T) {
 		{"data:image/png;base64,TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=", true},
 		{"data:text/plain;base64,Vml2YW11cyBmZXJtZW50dW0gc2VtcGVyIHBvcnRhLg==", true},
 		{"image/gif;base64,U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==", false},
-		{"data:image/gif;base64,MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNw" +
-			"UAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0Ye" +
-			"rhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619" +
-			"FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5Tkukhx" +
-			"QmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZ" +
-			"Fwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZ" + "HQIDAQAB", true},
+		{
+			"data:image/gif;base64,MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNw" +
+				"UAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0Ye" +
+				"rhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619" +
+				"FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5Tkukhx" +
+				"QmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZ" +
+				"Fwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZ" + "HQIDAQAB", true,
+		},
 		{"data:image/png;base64,12345", false},
 		{"", false},
 		{"data:text,:;base85,U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==", false},
@@ -5207,6 +5210,24 @@ func TestIsNeValidation(t *testing.T) {
 	Equal(t, errs, nil)
 }
 
+func TestIsNeIgnoreCaseValidation(t *testing.T) {
+	var errs error
+	validate := New()
+	s := "abcd"
+	now := time.Now()
+
+	errs = validate.Var(s, "ne_ignore_case=efgh")
+	Equal(t, errs, nil)
+
+	errs = validate.Var(s, "ne_ignore_case=AbCd")
+	NotEqual(t, errs, nil)
+	AssertError(t, errs, "", "", "", "", "ne_ignore_case")
+
+	PanicMatches(
+		t, func() { _ = validate.Var(now, "ne_ignore_case=abcd") }, "Bad field type time.Time",
+	)
+}
+
 func TestIsEqFieldValidation(t *testing.T) {
 	var errs error
 	validate := New()
@@ -5484,6 +5505,23 @@ func TestIsEqValidation(t *testing.T) {
 	Equal(t, errs, nil)
 }
 
+func TestIsEqIgnoreCaseValidation(t *testing.T) {
+	var errs error
+	validate := New()
+	s := "abcd"
+	now := time.Now()
+
+	errs = validate.Var(s, "eq_ignore_case=abcd")
+	Equal(t, errs, nil)
+
+	errs = validate.Var(s, "eq_ignore_case=AbCd")
+	Equal(t, errs, nil)
+
+	PanicMatches(
+		t, func() { _ = validate.Var(now, "eq_ignore_case=abcd") }, "Bad field type time.Time",
+	)
+}
+
 func TestOneOfValidation(t *testing.T) {
 	validate := New()
 
@@ -5617,6 +5655,53 @@ func TestBase64URLValidation(t *testing.T) {
 	}
 }
 
+func TestBase64RawURLValidation(t *testing.T) {
+	validate := New()
+
+	testCases := []struct {
+		decoded, encoded string
+		success          bool
+	}{
+		// empty string, although a valid base64 string, should fail
+		{"", "", false},
+		// invalid length
+		{"", "a", false},
+		// base64 with padding should fail
+		{"f", "Zg==", false},
+		{"fo", "Zm8=", false},
+		// base64 without padding
+		{"foo", "Zm9v", true},
+		{"hello", "aGVsbG8", true},
+		{"", "aGVsb", false},
+		// // base64 URL safe encoding with invalid, special characters '+' and '/'
+		{"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+", false},
+		{"\x14\xfb\x9c\x03\xf9\x73", "FPucA/lz", false},
+		// // base64 URL safe encoding with valid, special characters '-' and '_'
+		{"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l-", true},
+		{"\x14\xfb\x9c\x03\xf9\x73", "FPucA_lz", true},
+		// non base64 characters
+		{"", "@mc=", false},
+		{"", "Zm 9", false},
+	}
+	for _, tc := range testCases {
+		err := validate.Var(tc.encoded, "base64rawurl")
+		if tc.success {
+			Equal(t, err, nil)
+			// make sure encoded value is decoded back to the expected value
+			d, innerErr := base64.RawURLEncoding.DecodeString(tc.encoded)
+			Equal(t, innerErr, nil)
+			Equal(t, tc.decoded, string(d))
+		} else {
+			NotEqual(t, err, nil)
+			if len(tc.encoded) > 0 {
+				// make sure that indeed the encoded value was faulty
+				_, err := base64.RawURLEncoding.DecodeString(tc.encoded)
+				NotEqual(t, err, nil)
+			}
+		}
+	}
+}
+
 func TestFileValidation(t *testing.T) {
 	validate := New()
 
@@ -5650,6 +5735,39 @@ func TestFileValidation(t *testing.T) {
 	}, "Bad field type int")
 }
 
+func TestFilePathValidation(t *testing.T) {
+	validate := New()
+
+	tests := []struct {
+		title    string
+		param    string
+		expected bool
+	}{
+		{"empty filepath", "", false},
+		{"valid filepath", filepath.Join("testdata", "a.go"), true},
+		{"invalid filepath", filepath.Join("testdata", "no\000.go"), false},
+		{"directory, not a filepath", "testdata" + string(os.PathSeparator), false},
+	}
+
+	for _, test := range tests {
+		errs := validate.Var(test.param, "filepath")
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Test: '%s' failed Error: %s", test.title, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Test: '%s' failed Error: %s", test.title, errs)
+			}
+		}
+	}
+
+	PanicMatches(t, func() {
+		_ = validate.Var(6, "filepath")
+	}, "Bad field type int")
+}
+
 func TestEthereumAddressValidation(t *testing.T) {
 	validate := New()
 
@@ -5672,7 +5790,7 @@ func TestEthereumAddressValidation(t *testing.T) {
 		{"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true},
 		{"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true},
 		{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true},
-		{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDB", false}, // Invalid checksum.
+		{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDB", true}, // Invalid checksum, but valid address.
 
 		// Other.
 		{"", false},
@@ -5703,6 +5821,56 @@ func TestEthereumAddressValidation(t *testing.T) {
 	}
 }
 
+func TestEthereumAddressChecksumValidation(t *testing.T) {
+	validate := New()
+
+	tests := []struct {
+		param    string
+		expected bool
+	}{
+		// All caps.
+		{"0x52908400098527886E0F7030069857D2E4169EE7", true},
+		{"0x8617E340B3D01FA5F11F306F4090FD50E238070D", true},
+
+		// All lower.
+		{"0x27b1fdb04752bbc536007a920d24acb045561c26", true},
+		{"0x123f681646d4a755815f9cb19e1acc8565a0c2ac", false},
+
+		// Mixed case: runs checksum validation.
+		{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true},
+		{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDB", false}, // Invalid checksum.
+		{"0x000000000000000000000000000000000000dead", false}, // Invalid checksum.
+		{"0x000000000000000000000000000000000000dEaD", true},  // Valid checksum.
+
+		// Other.
+		{"", false},
+		{"D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", false},    // Missing "0x" prefix.
+		{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDbc", false}, // More than 40 hex digits.
+		{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aD", false},   // Less than 40 hex digits.
+		{"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDw", false},  // Invalid hex digit "w".
+	}
+
+	for i, test := range tests {
+
+		errs := validate.Var(test.param, "eth_addr_checksum")
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Index: %d eth_addr_checksum failed Error: %s", i, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Index: %d eth_addr_checksum failed Error: %s", i, errs)
+			} else {
+				val := getError(errs, "", "")
+				if val.Tag() != "eth_addr_checksum" {
+					t.Fatalf("Index: %d Latitude failed Error: %s", i, errs)
+				}
+			}
+		}
+	}
+}
+
 func TestBitcoinAddressValidation(t *testing.T) {
 	validate := New()
 
@@ -7793,6 +7961,77 @@ func TestUrl(t *testing.T) {
 	PanicMatches(t, func() { _ = validate.Var(i, "url") }, "Bad field type int")
 }
 
+func TestHttpUrl(t *testing.T) {
+	tests := []struct {
+		param    string
+		expected bool
+	}{
+		{"http://foo.bar#com", true},
+		{"http://foobar.com", true},
+		{"HTTP://foobar.com", true},
+		{"https://foobar.com", true},
+		{"foobar.com", false},
+		{"http://foobar.coffee/", true},
+		{"http://foobar.中文网/", true},
+		{"http://foobar.org/", true},
+		{"http://foobar.org:8080/", true},
+		{"ftp://foobar.ru/", false},
+		{"file:///etc/passwd", false},
+		{"file://C:/windows/win.ini", false},
+		{"http://user:pass@www.foobar.com/", true},
+		{"http://127.0.0.1/", true},
+		{"http://duckduckgo.com/?q=%2F", true},
+		{"http://localhost:3000/", true},
+		{"http://foobar.com/?foo=bar#baz=qux", true},
+		{"http://foobar.com?foo=bar", true},
+		{"http://www.xn--froschgrn-x9a.net/", true},
+		{"", false},
+		{"a://b", false},
+		{"xyz://foobar.com", false},
+		{"invalid.", false},
+		{".com", false},
+		{"rtmp://foobar.com", false},
+		{"http://www.foo_bar.com/", true},
+		{"http://localhost:3000/", true},
+		{"http://foobar.com/#baz", true},
+		{"http://foobar.com#baz=qux", true},
+		{"http://foobar.com/t$-_.+!*\\'(),", true},
+		{"http://www.foobar.com/~foobar", true},
+		{"http://www.-foobar.com/", true},
+		{"http://www.foo---bar.com/", true},
+		{"mailto:someone@example.com", false},
+		{"irc://irc.server.org/channel", false},
+		{"irc://#channel@network", false},
+		{"/abs/test/dir", false},
+		{"./rel/test/dir", false},
+	}
+
+	validate := New()
+
+	for i, test := range tests {
+
+		errs := validate.Var(test.param, "http_url")
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Index: %d HTTP URL failed Error: %s", i, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Index: %d HTTP URL failed Error: %s", i, errs)
+			} else {
+				val := getError(errs, "", "")
+				if val.Tag() != "http_url" {
+					t.Fatalf("Index: %d HTTP URL failed Error: %s", i, errs)
+				}
+			}
+		}
+	}
+
+	i := 1
+	PanicMatches(t, func() { _ = validate.Var(i, "http_url") }, "Bad field type int")
+}
+
 func TestUri(t *testing.T) {
 	tests := []struct {
 		param    string
@@ -9805,6 +10044,12 @@ func TestUniqueValidation(t *testing.T) {
 		{map[string]string{"one": "a", "two": "a"}, false},
 		{map[string]interface{}{"one": "a", "two": "a"}, false},
 		{map[string]interface{}{"one": "a", "two": 1, "three": "b", "four": 1}, false},
+		{map[string]*string{"one": stringPtr("a"), "two": stringPtr("a")}, false},
+		{map[string]*string{"one": stringPtr("a"), "two": stringPtr("b")}, true},
+		{map[string]*int{"one": intPtr(1), "two": intPtr(1)}, false},
+		{map[string]*int{"one": intPtr(1), "two": intPtr(2)}, true},
+		{map[string]*float64{"one": float64Ptr(1.1), "two": float64Ptr(1.1)}, false},
+		{map[string]*float64{"one": float64Ptr(1.1), "two": float64Ptr(1.2)}, true},
 	}
 
 	validate := New()
@@ -9829,6 +10074,41 @@ func TestUniqueValidation(t *testing.T) {
 		}
 	}
 	PanicMatches(t, func() { _ = validate.Var(1.0, "unique") }, "Bad field type float64")
+
+	t.Run("struct", func(t *testing.T) {
+		tests := []struct {
+			param    interface{}
+			expected bool
+		}{
+			{struct {
+				A string `validate:"unique=B"`
+				B string
+			}{A: "abc", B: "bcd"}, true},
+			{struct {
+				A string `validate:"unique=B"`
+				B string
+			}{A: "abc", B: "abc"}, false},
+		}
+		validate := New()
+
+		for i, test := range tests {
+			errs := validate.Struct(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, "A", "A")
+					if val.Tag() != "unique" {
+						t.Fatalf("Index: %d unique failed Error: %v", i, errs)
+					}
+				}
+			}
+		}
+	})
 }
 
 func TestUniqueValidationStructSlice(t *testing.T) {
@@ -9881,6 +10161,7 @@ func TestUniqueValidationStructPtrSlice(t *testing.T) {
 	}{
 		{A: stringPtr("one"), B: stringPtr("two")},
 		{A: stringPtr("one"), B: stringPtr("three")},
+		{},
 	}
 
 	tests := []struct {
@@ -10324,6 +10605,40 @@ func TestDirValidation(t *testing.T) {
 	}, "Bad field type int")
 }
 
+func TestDirPathValidation(t *testing.T) {
+	validate := New()
+
+	tests := []struct {
+		title    string
+		param    string
+		expected bool
+	}{
+		{"empty dirpath", "", false},
+		{"valid dirpath - exists", "testdata", true},
+		{"valid dirpath - explicit", "testdatanoexist" + string(os.PathSeparator), true},
+		{"invalid dirpath", "testdata\000" + string(os.PathSeparator), false},
+		{"file, not a dirpath", filepath.Join("testdata", "a.go"), false},
+	}
+
+	for _, test := range tests {
+		errs := validate.Var(test.param, "dirpath")
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Test: '%s' failed Error: %s", test.title, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Test: '%s' failed Error: %s", test.title, errs)
+			}
+		}
+	}
+
+	PanicMatches(t, func() {
+		_ = validate.Var(6, "filepath")
+	}, "Bad field type int")
+}
+
 func TestStartsWithValidation(t *testing.T) {
 	tests := []struct {
 		Value       string `validate:"startswith=(/^ヮ^)/*:・ﾟ✧"`
@@ -11174,7 +11489,7 @@ func TestExcludedUnless(t *testing.T) {
 		FieldE  string `validate:"omitempty" json:"field_e"`
 		FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"`
 	}{
-		FieldE:  "notest",
+		FieldE:  "test",
 		FieldER: "filled",
 	}
 	errs := validate.Struct(test)
@@ -11184,7 +11499,7 @@ func TestExcludedUnless(t *testing.T) {
 		FieldE  string `validate:"omitempty" json:"field_e"`
 		FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"`
 	}{
-		FieldE:  "test",
+		FieldE:  "notest",
 		FieldER: "filled",
 	}
 	errs = validate.Struct(test2)
@@ -11193,7 +11508,26 @@ func TestExcludedUnless(t *testing.T) {
 	Equal(t, len(ve), 1)
 	AssertError(t, errs, "FieldER", "FieldER", "FieldER", "FieldER", "excluded_unless")
 
-	shouldError := "test"
+	// test5 and test6: excluded_unless has no effect if FieldER is left blank
+	test5 := struct {
+		FieldE  string `validate:"omitempty" json:"field_e"`
+		FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"`
+	}{
+		FieldE: "test",
+	}
+	errs = validate.Struct(test5)
+	Equal(t, errs, nil)
+
+	test6 := struct {
+		FieldE  string `validate:"omitempty" json:"field_e"`
+		FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"`
+	}{
+		FieldE: "notest",
+	}
+	errs = validate.Struct(test6)
+	Equal(t, errs, nil)
+
+	shouldError := "notest"
 	test3 := struct {
 		Inner  *Inner
 		Field1 string `validate:"excluded_unless=Inner.Field test" json:"field_1"`
@@ -11207,7 +11541,7 @@ func TestExcludedUnless(t *testing.T) {
 	Equal(t, len(ve), 1)
 	AssertError(t, errs, "Field1", "Field1", "Field1", "Field1", "excluded_unless")
 
-	shouldPass := "shouldPass"
+	shouldPass := "test"
 	test4 := struct {
 		Inner  *Inner
 		FieldE string `validate:"omitempty" json:"field_e"`
@@ -11219,6 +11553,26 @@ func TestExcludedUnless(t *testing.T) {
 	errs = validate.Struct(test4)
 	Equal(t, errs, nil)
 
+	// test7 and test8: excluded_unless has no effect if FieldER is left blank
+	test7 := struct {
+		Inner   *Inner
+		FieldE  string `validate:"omitempty" json:"field_e"`
+		FieldER string `validate:"excluded_unless=Inner.Field test" json:"field_er"`
+	}{
+		FieldE: "test",
+	}
+	errs = validate.Struct(test7)
+	Equal(t, errs, nil)
+
+	test8 := struct {
+		FieldE  string `validate:"omitempty" json:"field_e"`
+		FieldER string `validate:"excluded_unless=Inner.Field test" json:"field_er"`
+	}{
+		FieldE: "test",
+	}
+	errs = validate.Struct(test8)
+	Equal(t, errs, nil)
+
 	// Checks number of params in struct tag is correct
 	defer func() {
 		if r := recover(); r == nil {
@@ -12077,10 +12431,12 @@ func TestPostCodeByIso3166Alpha2(t *testing.T) {
 			{"00803", true},
 			{"1234567", false},
 		},
-		"LC": { // not support regexp for post code
+		"LC": {
+			// not support regexp for post code
 			{"123456", false},
 		},
-		"XX": { // not support country
+		"XX": {
+			// not support country
 			{"123456", false},
 		},
 	}
@@ -12262,6 +12618,42 @@ func TestValidate_ValidateMapCtx(t *testing.T) {
 	}
 }
 
+func TestMongoDBObjectIDFormatValidation(t *testing.T) {
+	tests := []struct {
+		value    string `validate:"mongodb"`
+		tag      string
+		expected bool
+	}{
+		{"507f191e810c19729de860ea", "mongodb", true},
+		{"507f191e810c19729de860eG", "mongodb", false},
+		{"M07f191e810c19729de860eG", "mongodb", false},
+		{"07f191e810c19729de860ea", "mongodb", false},
+		{"507f191e810c19729de860e", "mongodb", false},
+		{"507f191e810c19729de860ea4", "mongodb", false},
+	}
+
+	validate := New()
+
+	for i, test := range tests {
+		errs := validate.Var(test.value, test.tag)
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Index: %d mongodb failed Error: %s", i, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Index: %d mongodb failed Error: %s", i, errs)
+			} else {
+				val := getError(errs, "", "")
+				if val.Tag() != "mongodb" {
+					t.Fatalf("Index: %d mongodb failed Error: %s", i, errs)
+				}
+			}
+		}
+	}
+}
+
 func TestCreditCardFormatValidation(t *testing.T) {
 	tests := []struct {
 		value    string `validate:"credit_card"`
@@ -12323,3 +12715,37 @@ func TestMultiOrOperatorGroup(t *testing.T) {
 		}
 	}
 }
+
+func TestCronExpressionValidation(t *testing.T) {
+	tests := []struct {
+		value    string `validate:"cron"`
+		tag      string
+		expected bool
+	}{
+		{"0 0 12 * * ?", "cron", true},
+		{"0 15 10 ? * *", "cron", true},
+		{"0 15 10 * * ?", "cron", true},
+		{"0 15 10 * * ? 2005", "cron", true},
+		{"0 15 10 ? * 6L", "cron", true},
+		{"0 15 10 ? * 6L 2002-2005", "cron", true},
+		{"*/20 * * * *", "cron", true},
+		{"0 15 10 ? * MON-FRI", "cron", true},
+		{"0 15 10 ? * 6#3", "cron", true},
+		{"wrong", "cron", false},
+	}
+
+	validate := New()
+
+	for i, test := range tests {
+		errs := validate.Var(test.value, test.tag)
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf(`Index: %d cron "%s" failed Error: %s`, i, test.value, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf(`Index: %d cron "%s" should have errs`, i, test.value)
+			}
+		}
+	}
+}