Repository: go-playground/validator
Pull request #1150 (2023-08-22 04:06:00)

Struct experiment
## PR
This PR does the following:
- Reverts #1122 
- Re-implements struct level validations in a different way which also support `or`s etc.. which previous implementation did not.
- Adds special case to ignore `required` validation on non-pointer structs to preserve pre-struct level tag validation support.
  - Added new `WithRequiredStructEnabled` option to opt-in to this new behaviour, that will become the default in the next major version.

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

coverage: 73.83% (-0.04%) from 73.872% when pulling **17130c67bcb4709bb425e34daf8417d6fe57da65 on struct-experiment** into **3094d596c834c13d04bf7523a35a06aaa9965928 on master**.

MysteriousPotato: Would you consider adding an opt-in option to enable the `required` tag for structs?

This would prevent users that are already using the `required` tag on non-pointer structs (such as myself) from having to refactor their codebase as well as allowing users to use it without having to wait for the next major version.

Something like:
```go
type Validate struct {
	//...
	requiredStructEnabled bool // new field
}

type ValidateOpt func(*Validate)

// Option to enable required tag on nested structs
func EnableRequiredStruct(v *Validate) {
	v.requiredStructEnabled = true
}

func New(opts ...ValidateOpt) *Validate {
	//...
	v := &Validate{
		//...
	}

	for _, opt := range opts {
		opt(v)
	}
	//...
}

func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) {
	//...
	if ct != nil && ct.tag == requiredTag && !v.v.requiredStructEnabled {
		ct = ct.next
	}
	//...
}
```
deankarn: Yep @MysteriousPotato totally an option :)

was meaning to ask what u think of this alt approach that will support or tag etc on structs.

will be a few days before I can finish adding the tests and docs back and then add that option.
MysteriousPotato: @deankarn I think this is clearly an improvement over my implementation.

Besides adding `or` support (which I totally overlooked) it also, among other things, makes the flow of `traverseField` easier to follow IMO.

One thing though. Looking a bit more into it, I think this may have changed the behavior of `structonly` for non-struct types where `validateStruct` would get called.

Not that it's a problem in and of itself since it doesn't make any sense to do that but considering the recent events, maybe adding some kind of check before calling `validateStruct` or returning an insightful error would be a good idea just in case?

Unrelated to this MR:
While perusing the code, I noticed the `structonly` tag fails for nil struct pointers. Is this behavior intended?
deankarn: @MysteriousPotato 
> One thing though. Looking a bit more into it, I think this may have changed the behavior of structonly for non-struct types where validateStruct would get called.

Nice catch agreed, handled in this commit to preserve old behaviour.
https://github.com/go-playground/validator/pull/1150/commits/771b7bfcb74074976a24b0f834062b14a8220207

Patch:
diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
index 435aeb7..a5346d2 100644
--- a/.github/workflows/workflow.yml
+++ b/.github/workflows/workflow.yml
@@ -8,7 +8,7 @@ jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.20.x]
+        go-version: [1.17.x,1.18.x,1.21.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.20.x'
+        if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.21.x'
         uses: shogo82148/actions-goveralls@v1
         with:
           path-to-profile: profile.cov
@@ -43,7 +43,7 @@ jobs:
     steps:
       - uses: actions/setup-go@v3
         with:
-          go-version: 1.19.x
+          go-version: 1.21.x
       - uses: actions/checkout@v3
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v3
diff --git a/Makefile b/Makefile
index ec3455b..09f171b 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,6 @@ test:
 	$(GOCMD) test -cover -race ./...
 
 bench:
-	$(GOCMD) test -bench=. -benchmem ./...
+	$(GOCMD) test -run=NONE -bench=. -benchmem ./...
 
 .PHONY: test lint linters-install
\ No newline at end of file
diff --git a/README.md b/README.md
index b2e0e2d..19f9723 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 Package validator
 =================
 <img align="right" src="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.15.0-green.svg)
+![Project status](https://img.shields.io/badge/version-10.15.1-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)
@@ -67,6 +67,12 @@ Please see https://pkg.go.dev/github.com/go-playground/validator/v10 for detaile
 Baked-in Validations
 ------
 
+### Special Notes:
+- If new to using validator it is highly recommended to initialize it using the `WithRequiredStructEnabled` option which is opt-in to new behaviour that will become the default behaviour in v11+. See documentation for more details.
+```go
+validate := validator.New(validator.WithRequiredStructEnabled())
+```
+
 ### Fields:
 
 | Tag | Description |
@@ -260,71 +266,72 @@ Benchmarks
 ------
 ###### Run on MacBook Pro (15-inch, 2017) go version go1.10.2 darwin/amd64
 ```go
+go version go1.21.0 darwin/arm64
 goos: darwin
-goarch: amd64
-pkg: github.com/go-playground/validator
-BenchmarkFieldSuccess-8                                         20000000                83.6 ns/op             0 B/op          0 allocs/op
-BenchmarkFieldSuccessParallel-8                                 50000000                26.8 ns/op             0 B/op          0 allocs/op
-BenchmarkFieldFailure-8                                          5000000               291 ns/op             208 B/op          4 allocs/op
-BenchmarkFieldFailureParallel-8                                 20000000               107 ns/op             208 B/op          4 allocs/op
-BenchmarkFieldArrayDiveSuccess-8                                 2000000               623 ns/op             201 B/op         11 allocs/op
-BenchmarkFieldArrayDiveSuccessParallel-8                        10000000               237 ns/op             201 B/op         11 allocs/op
-BenchmarkFieldArrayDiveFailure-8                                 2000000               859 ns/op             412 B/op         16 allocs/op
-BenchmarkFieldArrayDiveFailureParallel-8                         5000000               335 ns/op             413 B/op         16 allocs/op
-BenchmarkFieldMapDiveSuccess-8                                   1000000              1292 ns/op             432 B/op         18 allocs/op
-BenchmarkFieldMapDiveSuccessParallel-8                           3000000               467 ns/op             432 B/op         18 allocs/op
-BenchmarkFieldMapDiveFailure-8                                   1000000              1082 ns/op             512 B/op         16 allocs/op
-BenchmarkFieldMapDiveFailureParallel-8                           5000000               425 ns/op             512 B/op         16 allocs/op
-BenchmarkFieldMapDiveWithKeysSuccess-8                           1000000              1539 ns/op             480 B/op         21 allocs/op
-BenchmarkFieldMapDiveWithKeysSuccessParallel-8                   3000000               613 ns/op             480 B/op         21 allocs/op
-BenchmarkFieldMapDiveWithKeysFailure-8                           1000000              1413 ns/op             721 B/op         21 allocs/op
-BenchmarkFieldMapDiveWithKeysFailureParallel-8                   3000000               575 ns/op             721 B/op         21 allocs/op
-BenchmarkFieldCustomTypeSuccess-8                               10000000               216 ns/op              32 B/op          2 allocs/op
-BenchmarkFieldCustomTypeSuccessParallel-8                       20000000                82.2 ns/op            32 B/op          2 allocs/op
-BenchmarkFieldCustomTypeFailure-8                                5000000               274 ns/op             208 B/op          4 allocs/op
-BenchmarkFieldCustomTypeFailureParallel-8                       20000000               116 ns/op             208 B/op          4 allocs/op
-BenchmarkFieldOrTagSuccess-8                                     2000000               740 ns/op              16 B/op          1 allocs/op
-BenchmarkFieldOrTagSuccessParallel-8                             3000000               474 ns/op              16 B/op          1 allocs/op
-BenchmarkFieldOrTagFailure-8                                     3000000               471 ns/op             224 B/op          5 allocs/op
-BenchmarkFieldOrTagFailureParallel-8                             3000000               414 ns/op             224 B/op          5 allocs/op
-BenchmarkStructLevelValidationSuccess-8                         10000000               213 ns/op              32 B/op          2 allocs/op
-BenchmarkStructLevelValidationSuccessParallel-8                 20000000                91.8 ns/op            32 B/op          2 allocs/op
-BenchmarkStructLevelValidationFailure-8                          3000000               473 ns/op             304 B/op          8 allocs/op
-BenchmarkStructLevelValidationFailureParallel-8                 10000000               234 ns/op             304 B/op          8 allocs/op
-BenchmarkStructSimpleCustomTypeSuccess-8                         5000000               385 ns/op              32 B/op          2 allocs/op
-BenchmarkStructSimpleCustomTypeSuccessParallel-8                10000000               161 ns/op              32 B/op          2 allocs/op
-BenchmarkStructSimpleCustomTypeFailure-8                         2000000               640 ns/op             424 B/op          9 allocs/op
-BenchmarkStructSimpleCustomTypeFailureParallel-8                 5000000               318 ns/op             440 B/op         10 allocs/op
-BenchmarkStructFilteredSuccess-8                                 2000000               597 ns/op             288 B/op          9 allocs/op
-BenchmarkStructFilteredSuccessParallel-8                        10000000               266 ns/op             288 B/op          9 allocs/op
-BenchmarkStructFilteredFailure-8                                 3000000               454 ns/op             256 B/op          7 allocs/op
-BenchmarkStructFilteredFailureParallel-8                        10000000               214 ns/op             256 B/op          7 allocs/op
-BenchmarkStructPartialSuccess-8                                  3000000               502 ns/op             256 B/op          6 allocs/op
-BenchmarkStructPartialSuccessParallel-8                         10000000               225 ns/op             256 B/op          6 allocs/op
-BenchmarkStructPartialFailure-8                                  2000000               702 ns/op             480 B/op         11 allocs/op
-BenchmarkStructPartialFailureParallel-8                          5000000               329 ns/op             480 B/op         11 allocs/op
-BenchmarkStructExceptSuccess-8                                   2000000               793 ns/op             496 B/op         12 allocs/op
-BenchmarkStructExceptSuccessParallel-8                          10000000               193 ns/op             240 B/op          5 allocs/op
-BenchmarkStructExceptFailure-8                                   2000000               639 ns/op             464 B/op         10 allocs/op
-BenchmarkStructExceptFailureParallel-8                           5000000               300 ns/op             464 B/op         10 allocs/op
-BenchmarkStructSimpleCrossFieldSuccess-8                         3000000               417 ns/op              72 B/op          3 allocs/op
-BenchmarkStructSimpleCrossFieldSuccessParallel-8                10000000               163 ns/op              72 B/op          3 allocs/op
-BenchmarkStructSimpleCrossFieldFailure-8                         2000000               645 ns/op             304 B/op          8 allocs/op
-BenchmarkStructSimpleCrossFieldFailureParallel-8                 5000000               285 ns/op             304 B/op          8 allocs/op
-BenchmarkStructSimpleCrossStructCrossFieldSuccess-8              3000000               588 ns/op              80 B/op          4 allocs/op
-BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8     10000000               221 ns/op              80 B/op          4 allocs/op
-BenchmarkStructSimpleCrossStructCrossFieldFailure-8              2000000               868 ns/op             320 B/op          9 allocs/op
-BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8      5000000               337 ns/op             320 B/op          9 allocs/op
-BenchmarkStructSimpleSuccess-8                                   5000000               260 ns/op               0 B/op          0 allocs/op
-BenchmarkStructSimpleSuccessParallel-8                          20000000                90.6 ns/op             0 B/op          0 allocs/op
-BenchmarkStructSimpleFailure-8                                   2000000               619 ns/op             424 B/op          9 allocs/op
-BenchmarkStructSimpleFailureParallel-8                           5000000               296 ns/op             424 B/op          9 allocs/op
-BenchmarkStructComplexSuccess-8                                  1000000              1454 ns/op             128 B/op          8 allocs/op
-BenchmarkStructComplexSuccessParallel-8                          3000000               579 ns/op             128 B/op          8 allocs/op
-BenchmarkStructComplexFailure-8                                   300000              4140 ns/op            3041 B/op         53 allocs/op
-BenchmarkStructComplexFailureParallel-8                          1000000              2127 ns/op            3041 B/op         53 allocs/op
-BenchmarkOneof-8                                                10000000               140 ns/op               0 B/op          0 allocs/op
-BenchmarkOneofParallel-8                                        20000000                70.1 ns/op             0 B/op          0 allocs/op
+goarch: arm64
+pkg: github.com/go-playground/validator/v10
+BenchmarkFieldSuccess-8                                         33142266                35.94 ns/op            0 B/op          0 allocs/op
+BenchmarkFieldSuccessParallel-8                                 200816191                6.568 ns/op           0 B/op          0 allocs/op
+BenchmarkFieldFailure-8                                          6779707               175.1 ns/op           200 B/op          4 allocs/op
+BenchmarkFieldFailureParallel-8                                 11044147               108.4 ns/op           200 B/op          4 allocs/op
+BenchmarkFieldArrayDiveSuccess-8                                 6054232               194.4 ns/op            97 B/op          5 allocs/op
+BenchmarkFieldArrayDiveSuccessParallel-8                        12523388                94.07 ns/op           97 B/op          5 allocs/op
+BenchmarkFieldArrayDiveFailure-8                                 3587043               334.3 ns/op           300 B/op         10 allocs/op
+BenchmarkFieldArrayDiveFailureParallel-8                         5816665               200.8 ns/op           300 B/op         10 allocs/op
+BenchmarkFieldMapDiveSuccess-8                                   2217910               540.1 ns/op           288 B/op         14 allocs/op
+BenchmarkFieldMapDiveSuccessParallel-8                           4446698               258.7 ns/op           288 B/op         14 allocs/op
+BenchmarkFieldMapDiveFailure-8                                   2392759               504.6 ns/op           376 B/op         13 allocs/op
+BenchmarkFieldMapDiveFailureParallel-8                           4244199               286.9 ns/op           376 B/op         13 allocs/op
+BenchmarkFieldMapDiveWithKeysSuccess-8                           2005857               592.1 ns/op           288 B/op         14 allocs/op
+BenchmarkFieldMapDiveWithKeysSuccessParallel-8                   4400850               296.9 ns/op           288 B/op         14 allocs/op
+BenchmarkFieldMapDiveWithKeysFailure-8                           1850227               643.8 ns/op           553 B/op         16 allocs/op
+BenchmarkFieldMapDiveWithKeysFailureParallel-8                   3293233               375.1 ns/op           553 B/op         16 allocs/op
+BenchmarkFieldCustomTypeSuccess-8                               12174412                98.25 ns/op           32 B/op          2 allocs/op
+BenchmarkFieldCustomTypeSuccessParallel-8                       34389907                35.49 ns/op           32 B/op          2 allocs/op
+BenchmarkFieldCustomTypeFailure-8                                7582524               156.6 ns/op           184 B/op          3 allocs/op
+BenchmarkFieldCustomTypeFailureParallel-8                       13019902                92.79 ns/op          184 B/op          3 allocs/op
+BenchmarkFieldOrTagSuccess-8                                     3427260               349.4 ns/op            16 B/op          1 allocs/op
+BenchmarkFieldOrTagSuccessParallel-8                            15144128                81.25 ns/op           16 B/op          1 allocs/op
+BenchmarkFieldOrTagFailure-8                                     5913546               201.9 ns/op           216 B/op          5 allocs/op
+BenchmarkFieldOrTagFailureParallel-8                             9810212               113.7 ns/op           216 B/op          5 allocs/op
+BenchmarkStructLevelValidationSuccess-8                         13456327                87.66 ns/op           16 B/op          1 allocs/op
+BenchmarkStructLevelValidationSuccessParallel-8                 41818888                27.77 ns/op           16 B/op          1 allocs/op
+BenchmarkStructLevelValidationFailure-8                          4166284               272.6 ns/op           264 B/op          7 allocs/op
+BenchmarkStructLevelValidationFailureParallel-8                  7594581               152.1 ns/op           264 B/op          7 allocs/op
+BenchmarkStructSimpleCustomTypeSuccess-8                         6508082               182.6 ns/op            32 B/op          2 allocs/op
+BenchmarkStructSimpleCustomTypeSuccessParallel-8                23078605                54.78 ns/op           32 B/op          2 allocs/op
+BenchmarkStructSimpleCustomTypeFailure-8                         3118352               381.0 ns/op           416 B/op          9 allocs/op
+BenchmarkStructSimpleCustomTypeFailureParallel-8                 5300738               224.1 ns/op           432 B/op         10 allocs/op
+BenchmarkStructFilteredSuccess-8                                 4761807               251.1 ns/op           216 B/op          5 allocs/op
+BenchmarkStructFilteredSuccessParallel-8                         8792598               128.6 ns/op           216 B/op          5 allocs/op
+BenchmarkStructFilteredFailure-8                                 5202573               232.1 ns/op           216 B/op          5 allocs/op
+BenchmarkStructFilteredFailureParallel-8                         9591267               121.4 ns/op           216 B/op          5 allocs/op
+BenchmarkStructPartialSuccess-8                                  5188512               231.6 ns/op           224 B/op          4 allocs/op
+BenchmarkStructPartialSuccessParallel-8                          9179776               123.1 ns/op           224 B/op          4 allocs/op
+BenchmarkStructPartialFailure-8                                  3071212               392.5 ns/op           440 B/op          9 allocs/op
+BenchmarkStructPartialFailureParallel-8                          5344261               223.7 ns/op           440 B/op          9 allocs/op
+BenchmarkStructExceptSuccess-8                                   3184230               375.0 ns/op           424 B/op          8 allocs/op
+BenchmarkStructExceptSuccessParallel-8                          10090130               108.9 ns/op           208 B/op          3 allocs/op
+BenchmarkStructExceptFailure-8                                   3347226               357.7 ns/op           424 B/op          8 allocs/op
+BenchmarkStructExceptFailureParallel-8                           5654923               209.5 ns/op           424 B/op          8 allocs/op
+BenchmarkStructSimpleCrossFieldSuccess-8                         5232265               229.1 ns/op            56 B/op          3 allocs/op
+BenchmarkStructSimpleCrossFieldSuccessParallel-8                17436674                64.75 ns/op           56 B/op          3 allocs/op
+BenchmarkStructSimpleCrossFieldFailure-8                         3128613               383.6 ns/op           272 B/op          8 allocs/op
+BenchmarkStructSimpleCrossFieldFailureParallel-8                 6994113               168.8 ns/op           272 B/op          8 allocs/op
+BenchmarkStructSimpleCrossStructCrossFieldSuccess-8              3506487               340.9 ns/op            64 B/op          4 allocs/op
+BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8     13431300                91.77 ns/op           64 B/op          4 allocs/op
+BenchmarkStructSimpleCrossStructCrossFieldFailure-8              2410566               500.9 ns/op           288 B/op          9 allocs/op
+BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8      6344510               188.2 ns/op           288 B/op          9 allocs/op
+BenchmarkStructSimpleSuccess-8                                   8922726               133.8 ns/op             0 B/op          0 allocs/op
+BenchmarkStructSimpleSuccessParallel-8                          55291153                23.63 ns/op            0 B/op          0 allocs/op
+BenchmarkStructSimpleFailure-8                                   3171553               378.4 ns/op           416 B/op          9 allocs/op
+BenchmarkStructSimpleFailureParallel-8                           5571692               212.0 ns/op           416 B/op          9 allocs/op
+BenchmarkStructComplexSuccess-8                                  1683750               714.5 ns/op           224 B/op          5 allocs/op
+BenchmarkStructComplexSuccessParallel-8                          4578046               257.0 ns/op           224 B/op          5 allocs/op
+BenchmarkStructComplexFailure-8                                   481585              2547 ns/op            3041 B/op         48 allocs/op
+BenchmarkStructComplexFailureParallel-8                           965764              1577 ns/op            3040 B/op         48 allocs/op
+BenchmarkOneof-8                                                17380881                68.50 ns/op            0 B/op          0 allocs/op
+BenchmarkOneofParallel-8                                         8084733               153.5 ns/op             0 B/op          0 allocs/op
 ```
 
 Complementary Software
diff --git a/_examples/simple/main.go b/_examples/simple/main.go
index 503669e..35a56c1 100644
--- a/_examples/simple/main.go
+++ b/_examples/simple/main.go
@@ -30,7 +30,7 @@ var validate *validator.Validate
 
 func main() {
 
-	validate = validator.New()
+	validate = validator.New(validator.WithRequiredStructEnabled())
 
 	validateStruct()
 	validateVariable()
diff --git a/baked_in.go b/baked_in.go
index ca9eeb1..cc92b78 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -23,7 +23,7 @@ import (
 	"golang.org/x/text/language"
 
 	"github.com/gabriel-vasile/mimetype"
-	"github.com/leodido/go-urn"
+	urn "github.com/leodido/go-urn"
 )
 
 // Func accepts a FieldLevel interface for all validation needs. The return
diff --git a/cache.go b/cache.go
index ddd37b8..bbfd2a4 100644
--- a/cache.go
+++ b/cache.go
@@ -20,7 +20,6 @@ const (
 	typeOr
 	typeKeys
 	typeEndKeys
-	typeNestedStructLevel
 )
 
 const (
@@ -153,7 +152,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
 		// and so only struct level caching can be used instead of combined with Field tag caching
 
 		if len(tag) > 0 {
-			ctag, _ = v.parseFieldTagsRecursive(tag, fld, "", false)
+			ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, "", false)
 		} else {
 			// even if field doesn't have validations need cTag for traversing to potential inner/nested
 			// elements of the field.
@@ -172,7 +171,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
 	return cs
 }
 
-func (v *Validate) parseFieldTagsRecursive(tag string, field reflect.StructField, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
+func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
 	var t string
 	noAlias := len(alias) == 0
 	tags := strings.Split(tag, tagSeparator)
@@ -186,9 +185,9 @@ func (v *Validate) parseFieldTagsRecursive(tag string, field reflect.StructField
 		// check map for alias and process new tags, otherwise process as usual
 		if tagsVal, found := v.aliases[t]; found {
 			if i == 0 {
-				firstCtag, current = v.parseFieldTagsRecursive(tagsVal, field, t, true)
+				firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
 			} else {
-				next, curr := v.parseFieldTagsRecursive(tagsVal, field, t, true)
+				next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
 				current.next, current = next, curr
 
 			}
@@ -236,7 +235,7 @@ func (v *Validate) parseFieldTagsRecursive(tag string, field reflect.StructField
 				}
 			}
 
-			current.keys, _ = v.parseFieldTagsRecursive(string(b[:len(b)-1]), field, "", false)
+			current.keys, _ = v.parseFieldTagsRecursive(string(b[:len(b)-1]), fieldName, "", false)
 			continue
 
 		case endKeysTag:
@@ -285,18 +284,14 @@ func (v *Validate) parseFieldTagsRecursive(tag string, field reflect.StructField
 
 				current.tag = vals[0]
 				if len(current.tag) == 0 {
-					panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, field.Name)))
+					panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
 				}
 
 				if wrapper, ok := v.validations[current.tag]; ok {
 					current.fn = wrapper.fn
 					current.runValidationWhenNil = wrapper.runValidatinOnNil
 				} else {
-					panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, field.Name)))
-				}
-
-				if current.typeof == typeDefault && isNestedStructOrStructPtr(field) {
-					current.typeof = typeNestedStructLevel
+					panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, fieldName)))
 				}
 
 				if len(orVals) > 1 {
@@ -324,7 +319,7 @@ func (v *Validate) fetchCacheTag(tag string) *cTag {
 		// isn't parsed again.
 		ctag, found = v.tagCache.Get(tag)
 		if !found {
-			ctag, _ = v.parseFieldTagsRecursive(tag, reflect.StructField{}, "", false)
+			ctag, _ = v.parseFieldTagsRecursive(tag, "", "", false)
 			v.tagCache.Set(tag, ctag)
 		}
 	}
diff --git a/doc.go b/doc.go
index d1eff50..c4dbb59 100644
--- a/doc.go
+++ b/doc.go
@@ -247,7 +247,7 @@ Example #2
 This validates that the value is not the data types default zero value.
 For numbers ensures value is not zero. For strings ensures value is
 not "". For slices, maps, pointers, interfaces, channels and functions
-ensures the value is not nil. For structs ensures value is not the zero value.
+ensures the value is not nil. For structs ensures value is not the zero value when using WithRequiredStructEnabled.
 
 	Usage: required
 
diff --git a/options.go b/options.go
new file mode 100644
index 0000000..1dea56f
--- /dev/null
+++ b/options.go
@@ -0,0 +1,16 @@
+package validator
+
+// Option represents a configurations option to be applied to validator during initialization.
+type Option func(*Validate)
+
+// WithRequiredStructEnabled enables required tag on non-pointer structs to be applied instead of ignored.
+//
+// This was made opt-in behaviour in order to maintain backward compatibility with the behaviour previous
+// to being able to apply struct level validations on struct fields directly.
+//
+// It is recommended you enabled this as it will be the default behaviour in v11+
+func WithRequiredStructEnabled() Option {
+	return func(v *Validate) {
+		v.requiredStructEnabled = true
+	}
+}
diff --git a/util.go b/util.go
index 084d461..4bd947b 100644
--- a/util.go
+++ b/util.go
@@ -292,11 +292,3 @@ func panicIf(err error) {
 		panic(err.Error())
 	}
 }
-
-func isNestedStructOrStructPtr(v reflect.StructField) bool {
-	if v.Type == nil {
-		return false
-	}
-	kind := v.Type.Kind()
-	return kind == reflect.Struct || kind == reflect.Ptr && v.Type.Elem().Kind() == reflect.Struct
-}
diff --git a/validator.go b/validator.go
index a6fa1f5..d3ff51d 100644
--- a/validator.go
+++ b/validator.go
@@ -99,6 +99,8 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
 
 	current, kind, v.fldIsPointer = v.extractTypeInternal(current, false)
 
+	var isNestedStruct bool
+
 	switch kind {
 	case reflect.Ptr, reflect.Interface, reflect.Invalid:
 
@@ -161,85 +163,56 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
 		}
 
 	case reflect.Struct:
-
-		typ = current.Type()
-
-		if !typ.ConvertibleTo(timeType) {
-
-			if ct != nil {
-
-				if ct.typeof == typeStructOnly {
-					goto CONTINUE
-				} else if ct.typeof == typeIsDefault || ct.typeof == typeNestedStructLevel {
-					// set Field Level fields
-					v.slflParent = parent
-					v.flField = current
-					v.cf = cf
-					v.ct = ct
-
-					if !ct.fn(ctx, v) {
-						v.str1 = string(append(ns, cf.altName...))
-
-						if v.v.hasTagNameFunc {
-							v.str2 = string(append(structNs, cf.name...))
-						} else {
-							v.str2 = v.str1
-						}
-
-						v.errs = append(v.errs,
-							&fieldError{
-								v:              v.v,
-								tag:            ct.aliasTag,
-								actualTag:      ct.tag,
-								ns:             v.str1,
-								structNs:       v.str2,
-								fieldLen:       uint8(len(cf.altName)),
-								structfieldLen: uint8(len(cf.name)),
-								value:          current.Interface(),
-								param:          ct.param,
-								kind:           kind,
-								typ:            typ,
-							},
-						)
-						return
-					}
-				}
-
-				ct = ct.next
-			}
-
-			if ct != nil && ct.typeof == typeNoStructLevel {
-				return
-			}
-
-		CONTINUE:
-			// if len == 0 then validating using 'Var' or 'VarWithValue'
-			// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
-			// VarWithField - this allows for validating against each field within the struct against a specific value
-			//                pretty handy in certain situations
-			if len(cf.name) > 0 {
-				ns = append(append(ns, cf.altName...), '.')
-				structNs = append(append(structNs, cf.name...), '.')
-			}
-
-			v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
-			return
+		isNestedStruct = !current.Type().ConvertibleTo(timeType)
+		// For backward compatibility before struct level validation tags were supported
+		// as there were a number of projects relying on `required` not failing on non-pointer
+		// structs. Since it's basically nonsensical to use `required` with a non-pointer struct
+		// are explicitly skipping the required validation for it. This WILL be removed in the
+		// next major version.
+		if !v.v.requiredStructEnabled && ct != nil && ct.tag == requiredTag {
+			ct = ct.next
 		}
 	}
 
-	if ct == nil || !ct.hasTag {
-		return
-	}
-
 	typ = current.Type()
 
 OUTER:
 	for {
-		if ct == nil {
+		if ct == nil || !ct.hasTag || (isNestedStruct && len(cf.name) == 0) {
+			// isNestedStruct check here
+			if isNestedStruct {
+				// if len == 0 then validating using 'Var' or 'VarWithValue'
+				// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
+				// VarWithField - this allows for validating against each field within the struct against a specific value
+				//                pretty handy in certain situations
+				if len(cf.name) > 0 {
+					ns = append(append(ns, cf.altName...), '.')
+					structNs = append(append(structNs, cf.name...), '.')
+				}
+
+				v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
+			}
 			return
 		}
 
 		switch ct.typeof {
+		case typeNoStructLevel:
+			return
+
+		case typeStructOnly:
+			if isNestedStruct {
+				// if len == 0 then validating using 'Var' or 'VarWithValue'
+				// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
+				// VarWithField - this allows for validating against each field within the struct against a specific value
+				//                pretty handy in certain situations
+				if len(cf.name) > 0 {
+					ns = append(append(ns, cf.altName...), '.')
+					structNs = append(append(structNs, cf.name...), '.')
+				}
+
+				v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
+			}
+			return
 
 		case typeOmitEmpty:
 
@@ -366,7 +339,7 @@ OUTER:
 						ct = ct.next
 
 						if ct == nil {
-							return
+							continue OUTER
 						}
 
 						if ct.typeof != typeOr {
diff --git a/validator_instance.go b/validator_instance.go
index d9dbf0c..a4dbdd0 100644
--- a/validator_instance.go
+++ b/validator_instance.go
@@ -79,19 +79,20 @@ type internalValidationFuncWrapper struct {
 
 // Validate contains the validator settings and cache
 type Validate struct {
-	tagName          string
-	pool             *sync.Pool
-	hasCustomFuncs   bool
-	hasTagNameFunc   bool
-	tagNameFunc      TagNameFunc
-	structLevelFuncs map[reflect.Type]StructLevelFuncCtx
-	customFuncs      map[reflect.Type]CustomTypeFunc
-	aliases          map[string]string
-	validations      map[string]internalValidationFuncWrapper
-	transTagFunc     map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
-	rules            map[reflect.Type]map[string]string
-	tagCache         *tagCache
-	structCache      *structCache
+	tagName               string
+	pool                  *sync.Pool
+	tagNameFunc           TagNameFunc
+	structLevelFuncs      map[reflect.Type]StructLevelFuncCtx
+	customFuncs           map[reflect.Type]CustomTypeFunc
+	aliases               map[string]string
+	validations           map[string]internalValidationFuncWrapper
+	transTagFunc          map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
+	rules                 map[reflect.Type]map[string]string
+	tagCache              *tagCache
+	structCache           *structCache
+	hasCustomFuncs        bool
+	hasTagNameFunc        bool
+	requiredStructEnabled bool
 }
 
 // New returns a new instance of 'validate' with sane defaults.
@@ -99,7 +100,7 @@ type Validate struct {
 // It caches information about your struct and validations,
 // in essence only parsing your validation tags once per struct type.
 // Using multiple instances neglects the benefit of caching.
-func New() *Validate {
+func New(options ...Option) *Validate {
 
 	tc := new(tagCache)
 	tc.m.Store(make(map[string]*cTag))
@@ -146,6 +147,9 @@ func New() *Validate {
 		},
 	}
 
+	for _, o := range options {
+		o(v)
+	}
 	return v
 }
 


Test patch:
diff --git a/validator_test.go b/validator_test.go
index 0887827..d9b715c 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -3150,7 +3150,7 @@ func TestInterfaceErrValidation(t *testing.T) {
 	AssertError(t, errs, "ExternalCMD.Data.Name", "ExternalCMD.Data.Name", "Name", "Name", "required")
 
 	type TestMapStructPtr struct {
-		Errs map[int]interface{} `validate:"gt=0,dive,len=2"`
+		Errs map[int]interface{} `validate:"gt=0,dive,required"`
 	}
 
 	mip := map[int]interface{}{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}}
@@ -3162,7 +3162,7 @@ func TestInterfaceErrValidation(t *testing.T) {
 	errs = validate.Struct(msp)
 	NotEqual(t, errs, nil)
 	Equal(t, len(errs.(ValidationErrors)), 1)
-	AssertError(t, errs, "TestMapStructPtr.Errs[3]", "TestMapStructPtr.Errs[3]", "Errs[3]", "Errs[3]", "len")
+	AssertError(t, errs, "TestMapStructPtr.Errs[3]", "TestMapStructPtr.Errs[3]", "Errs[3]", "Errs[3]", "required")
 
 	type TestMultiDimensionalStructs struct {
 		Errs [][]interface{} `validate:"gt=0,dive,dive"`
@@ -6152,7 +6152,8 @@ func TestNoStructLevelValidation(t *testing.T) {
 		InnerStruct:    Inner{},
 	}
 
-	validate := New()
+	// test with struct required failing on
+	validate := New(WithRequiredStructEnabled())
 
 	errs := validate.Struct(outer)
 	NotEqual(t, errs, nil)
@@ -6170,6 +6171,30 @@ func TestNoStructLevelValidation(t *testing.T) {
 
 	errs = validate.Struct(outer)
 	Equal(t, errs, nil)
+
+	// test with struct required failing off
+
+	outer = &Outer{
+		InnerStructPtr: nil,
+		InnerStruct:    Inner{},
+	}
+	validate = New()
+
+	errs = validate.Struct(outer)
+	NotEqual(t, errs, nil)
+	AssertError(t, errs, "Outer.InnerStructPtr", "Outer.InnerStructPtr", "InnerStructPtr", "InnerStructPtr", "required")
+
+	inner = Inner{
+		Test: "1234",
+	}
+
+	outer = &Outer{
+		InnerStruct:    inner,
+		InnerStructPtr: &inner,
+	}
+
+	errs = validate.Struct(outer)
+	Equal(t, errs, nil)
 }
 
 func TestStructOnlyValidation(t *testing.T) {
@@ -6187,10 +6212,18 @@ func TestStructOnlyValidation(t *testing.T) {
 		InnerStructPtr: nil,
 	}
 
+	// without required struct on
 	validate := New()
 
 	errs := validate.Struct(outer)
 	NotEqual(t, errs, nil)
+	AssertError(t, errs, "Outer.InnerStructPtr", "Outer.InnerStructPtr", "InnerStructPtr", "InnerStructPtr", "required")
+
+	// with required struct on
+	validate.requiredStructEnabled = true
+
+	errs = validate.Struct(outer)
+	NotEqual(t, errs, nil)
 	AssertError(t, errs, "Outer.InnerStruct", "Outer.InnerStruct", "InnerStruct", "InnerStruct", "required")
 	AssertError(t, errs, "Outer.InnerStructPtr", "Outer.InnerStructPtr", "InnerStructPtr", "InnerStructPtr", "required")
 
@@ -11197,7 +11230,11 @@ func TestExcludedWith(t *testing.T) {
 
 	ve := errs.(ValidationErrors)
 	Equal(t, len(ve), 7)
-	for i := 1; i <= 5; i++ {
+	for i := 1; i <= 7; i++ {
+		// accounting for field 7 & 8 failures, 6 skipped because no failure
+		if i > 5 {
+			i++
+		}
 		name := fmt.Sprintf("Field%d", i)
 		AssertError(t, errs, name, name, name, name, "excluded_with")
 	}
@@ -11293,7 +11330,7 @@ func TestExcludedWithout(t *testing.T) {
 
 	ve := errs.(ValidationErrors)
 	Equal(t, len(ve), 8)
-	for i := 1; i <= 6; i++ {
+	for i := 1; i <= 8; i++ {
 		name := fmt.Sprintf("Field%d", i)
 		AssertError(t, errs, name, name, name, name, "excluded_without")
 	}
@@ -11391,7 +11428,11 @@ func TestExcludedWithAll(t *testing.T) {
 
 	ve := errs.(ValidationErrors)
 	Equal(t, len(ve), 7)
-	for i := 1; i <= 5; i++ {
+	for i := 1; i <= 7; i++ {
+		// accounting for no err for field 6
+		if i > 5 {
+			i++
+		}
 		name := fmt.Sprintf("Field%d", i)
 		AssertError(t, errs, name, name, name, name, "excluded_with_all")
 	}
@@ -11489,7 +11530,7 @@ func TestExcludedWithoutAll(t *testing.T) {
 
 	ve := errs.(ValidationErrors)
 	Equal(t, len(ve), 8)
-	for i := 1; i <= 6; i++ {
+	for i := 1; i <= 8; i++ {
 		name := fmt.Sprintf("Field%d", i)
 		AssertError(t, errs, name, name, name, name, "excluded_without_all")
 	}
@@ -11658,7 +11699,6 @@ func TestRequiredWithoutAll(t *testing.T) {
 	type nested struct {
 		value string
 	}
-
 	fieldVal := "test"
 	test := struct {
 		Field1 string            `validate:"omitempty" json:"field_1"`
@@ -13312,7 +13352,7 @@ func TestCronExpressionValidation(t *testing.T) {
 }
 
 func TestNestedStructValidation(t *testing.T) {
-	validator := New()
+	validator := New(WithRequiredStructEnabled())
 
 	t.Run("required", func(t *testing.T) {
 		type (