Repository: go-playground/validator
Pull request #706 (2020-12-19 16:57:48)

New official Documentation URL
## Fixes Or Enhances


**Make sure that you've checked the boxes below before you submit PR:**
- [ ] Tests exist or have been written that cover this particular change.

@go-playground/admins

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

Coverage remained the same at 76.07% when pulling **d050c1a438d8af202e2e419e9bd244976df330f8 on frederikhors:patch-1** into **f6584a41c8acc5dfc0b62f7962811f5231c11530 on go-playground:master**.


Patch:
diff --git a/README.md b/README.md
index 04fbb3c..cdbc79b 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ validationErrors := err.(validator.ValidationErrors)
 Usage and documentation
 ------
 
-Please see https://godoc.org/github.com/go-playground/validator for detailed usage docs.
+Please see https://pkg.go.dev/github.com/go-playground/validator/v10 for detailed usage docs.
 
 ##### Examples:
 
diff --git a/_examples/struct-level/main.go b/_examples/struct-level/main.go
index 3231a81..6002a3b 100644
--- a/_examples/struct-level/main.go
+++ b/_examples/struct-level/main.go
@@ -44,7 +44,7 @@ func main() {
 
 	// register validation for 'User'
 	// NOTE: only have to register a non-pointer type for 'User', validator
-	// interanlly dereferences during it's type checks.
+	// internally dereferences during it's type checks.
 	validate.RegisterStructValidation(UserStructLevelValidation, User{})
 
 	// build 'User' info, normally posted data etc...
diff --git a/baked_in.go b/baked_in.go
index 6ce762d..ce05ac8 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -791,6 +791,9 @@ func isNeField(fl FieldLevel) bool {
 	case reflect.Slice, reflect.Map, reflect.Array:
 		return int64(field.Len()) != int64(currentField.Len())
 
+	case reflect.Bool:
+		return field.Bool() != currentField.Bool()
+
 	case reflect.Struct:
 
 		fieldType := field.Type()
@@ -1544,7 +1547,7 @@ func requiredWithout(fl FieldLevel) bool {
 	return true
 }
 
-// RequiredWithoutAll is the validation function
+// ExcludedWithoutAll is the validation function
 // The field under validation must not be present or is empty when all of the other specified fields are not present.
 func excludedWithoutAll(fl FieldLevel) bool {
 	params := parseOneOfParam2(fl.Param())
diff --git a/validator.go b/validator.go
index f097f39..9569c0d 100644
--- a/validator.go
+++ b/validator.go
@@ -74,7 +74,7 @@ func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, cur
 				}
 			}
 
-			v.traverseField(ctx, parent, current.Field(f.idx), ns, structNs, f, f.cTags)
+			v.traverseField(ctx, current, current.Field(f.idx), ns, structNs, f, f.cTags)
 		}
 	}
 
@@ -222,7 +222,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
 				structNs = append(append(structNs, cf.name...), '.')
 			}
 
-			v.validateStruct(ctx, current, current, typ, ns, structNs, ct)
+			v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
 			return
 		}
 	}
diff --git a/validator_instance.go b/validator_instance.go
index fe6a487..d230a32 100644
--- a/validator_instance.go
+++ b/validator_instance.go
@@ -29,6 +29,10 @@ const (
 	requiredWithAllTag    = "required_with_all"
 	requiredIfTag         = "required_if"
 	requiredUnlessTag     = "required_unless"
+	excludedWithoutAllTag = "excluded_without_all"
+	excludedWithoutTag    = "excluded_without"
+	excludedWithTag       = "excluded_with"
+	excludedWithAllTag    = "excluded_with_all"
 	skipValidationTag     = "-"
 	diveTag               = "dive"
 	keysTag               = "keys"
@@ -111,7 +115,8 @@ func New() *Validate {
 
 		switch k {
 		// these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour
-		case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag:
+		case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag,
+			excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag:
 			_ = v.registerValidation(k, wrapFunc(val), true, true)
 		default:
 			// no need to error check here, baked in will always be valid
@@ -409,7 +414,10 @@ func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields .
 		if len(flds) > 0 {
 
 			vd.misc = append(vd.misc[0:0], name...)
-			vd.misc = append(vd.misc, '.')
+			// Don't append empty name for unnamed structs
+			if len(vd.misc) != 0 {
+				vd.misc = append(vd.misc, '.')
+			}
 
 			for _, s := range flds {
 


Test patch:
diff --git a/validator_test.go b/validator_test.go
index 683a2dd..7d517ec 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -949,6 +949,38 @@ func TestStructPartial(t *testing.T) {
 	NotEqual(t, errs, nil)
 	AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "Test", "required")
 
+	// Test for unnamed struct
+	testStruct := &TestStruct{
+		String: "test",
+	}
+	unnamedStruct := struct {
+		String string `validate:"required" json:"StringVal"`
+	}{String: "test"}
+	composedUnnamedStruct := struct{ *TestStruct }{&TestStruct{String: "test"}}
+
+	errs = validate.StructPartial(testStruct, "String")
+	Equal(t, errs, nil)
+
+	errs = validate.StructPartial(unnamedStruct, "String")
+	Equal(t, errs, nil)
+
+	errs = validate.StructPartial(composedUnnamedStruct, "TestStruct.String")
+	Equal(t, errs, nil)
+
+	testStruct.String = ""
+	errs = validate.StructPartial(testStruct, "String")
+	NotEqual(t, errs, nil)
+	AssertError(t, errs, "TestStruct.String", "TestStruct.String", "String", "String", "required")
+
+	unnamedStruct.String = ""
+	errs = validate.StructPartial(unnamedStruct, "String")
+	NotEqual(t, errs, nil)
+	AssertError(t, errs, "String", "String", "String", "String", "required")
+
+	composedUnnamedStruct.String = ""
+	errs = validate.StructPartial(composedUnnamedStruct, "TestStruct.String")
+	NotEqual(t, errs, nil)
+	AssertError(t, errs, "TestStruct.String", "TestStruct.String", "String", "String", "required")
 }
 
 func TestCrossStructLteFieldValidation(t *testing.T) {
@@ -4557,6 +4589,7 @@ func TestIsNeFieldValidation(t *testing.T) {
 	i := 1
 	j = 1
 	k = 1.543
+	b := true
 	arr := []string{"test"}
 	now := time.Now().UTC()
 
@@ -4566,6 +4599,7 @@ func TestIsNeFieldValidation(t *testing.T) {
 	i2 := 3
 	j2 = 2
 	k2 = 1.5434456
+	b2 := false
 	arr2 := []string{"test", "test2"}
 	arr3 := []string{"test"}
 	now2 := now
@@ -4582,6 +4616,9 @@ func TestIsNeFieldValidation(t *testing.T) {
 	errs = validate.VarWithValue(k2, k, "nefield")
 	Equal(t, errs, nil)
 
+	errs = validate.VarWithValue(b2, b, "nefield")
+	Equal(t, errs, nil)
+
 	errs = validate.VarWithValue(arr2, arr, "nefield")
 	Equal(t, errs, nil)
 
@@ -10202,6 +10239,28 @@ func TestExcludedWith(t *testing.T) {
 		name := fmt.Sprintf("Field%d", i)
 		AssertError(t, errs, name, name, name, name, "excluded_with")
 	}
+
+	test3 := struct {
+		Inner  *Inner
+		Inner2 *Inner
+		Field  string            `validate:"omitempty" json:"field"`
+		FieldE string            `validate:"omitempty" json:"field_e"`
+		Field1 string            `validate:"excluded_with=FieldE" json:"field_1"`
+		Field2 *string           `validate:"excluded_with=FieldE" json:"field_2"`
+		Field3 map[string]string `validate:"excluded_with=FieldE" json:"field_3"`
+		Field4 interface{}       `validate:"excluded_with=FieldE" json:"field_4"`
+		Field5 string            `validate:"excluded_with=Inner.FieldE" json:"field_5"`
+		Field6 string            `validate:"excluded_with=Inner2.FieldE" json:"field_6"`
+	}{
+		Inner:  &Inner{FieldE: "populated"},
+		Inner2: &Inner{FieldE: "populated"},
+		FieldE: "populated",
+	}
+
+	validate = New()
+
+	errs = validate.Struct(test3)
+	Equal(t, errs, nil)
 }
 
 func TestExcludedWithout(t *testing.T) {
@@ -10266,6 +10325,26 @@ func TestExcludedWithout(t *testing.T) {
 		name := fmt.Sprintf("Field%d", i)
 		AssertError(t, errs, name, name, name, name, "excluded_without")
 	}
+
+	test3 := struct {
+		Inner  *Inner
+		Inner2 *Inner
+		Field  string            `validate:"omitempty" json:"field"`
+		FieldE string            `validate:"omitempty" json:"field_e"`
+		Field1 string            `validate:"excluded_without=Field" json:"field_1"`
+		Field2 *string           `validate:"excluded_without=Field" json:"field_2"`
+		Field3 map[string]string `validate:"excluded_without=Field" json:"field_3"`
+		Field4 interface{}       `validate:"excluded_without=Field" json:"field_4"`
+		Field5 string            `validate:"excluded_without=Inner.Field" json:"field_5"`
+	}{
+		Inner: &Inner{Field: &fieldVal},
+		Field: "populated",
+	}
+
+	validate = New()
+
+	errs = validate.Struct(test3)
+	Equal(t, errs, nil)
 }
 
 func TestExcludedWithAll(t *testing.T) {
@@ -10334,6 +10413,29 @@ func TestExcludedWithAll(t *testing.T) {
 		name := fmt.Sprintf("Field%d", i)
 		AssertError(t, errs, name, name, name, name, "excluded_with_all")
 	}
+
+	test3 := struct {
+		Inner  *Inner
+		Inner2 *Inner
+		Field  string            `validate:"omitempty" json:"field"`
+		FieldE string            `validate:"omitempty" json:"field_e"`
+		Field1 string            `validate:"excluded_with_all=FieldE Field" json:"field_1"`
+		Field2 *string           `validate:"excluded_with_all=FieldE Field" json:"field_2"`
+		Field3 map[string]string `validate:"excluded_with_all=FieldE Field" json:"field_3"`
+		Field4 interface{}       `validate:"excluded_with_all=FieldE Field" json:"field_4"`
+		Field5 string            `validate:"excluded_with_all=Inner.FieldE" json:"field_5"`
+		Field6 string            `validate:"excluded_with_all=Inner2.FieldE" json:"field_6"`
+	}{
+		Inner:  &Inner{FieldE: "populated"},
+		Inner2: &Inner{FieldE: "populated"},
+		Field:  "populated",
+		FieldE: "populated",
+	}
+
+	validate = New()
+
+	errs = validate.Struct(test3)
+	Equal(t, errs, nil)
 }
 
 func TestExcludedWithoutAll(t *testing.T) {
@@ -10352,9 +10454,10 @@ func TestExcludedWithoutAll(t *testing.T) {
 		Field2 *string           `validate:"excluded_without_all=Field FieldE" json:"field_2"`
 		Field3 map[string]string `validate:"excluded_without_all=Field FieldE" json:"field_3"`
 		Field4 interface{}       `validate:"excluded_without_all=Field FieldE" json:"field_4"`
-		Field5 string            `validate:"excluded_without_all=Inner.Field Inner.Field2" json:"field_5"`
+		Field5 string            `validate:"excluded_without_all=Inner.Field Inner2.Field" json:"field_5"`
 	}{
 		Inner:  &Inner{Field: &fieldVal},
+		Inner2: &Inner{Field: &fieldVal},
 		Field:  "populated",
 		Field1: fieldVal,
 		Field2: &fieldVal,
@@ -10398,6 +10501,28 @@ func TestExcludedWithoutAll(t *testing.T) {
 		name := fmt.Sprintf("Field%d", i)
 		AssertError(t, errs, name, name, name, name, "excluded_without_all")
 	}
+
+	test3 := struct {
+		Inner  *Inner
+		Inner2 *Inner
+		Field  string            `validate:"omitempty" json:"field"`
+		FieldE string            `validate:"omitempty" json:"field_e"`
+		Field1 string            `validate:"excluded_without_all=Field FieldE" json:"field_1"`
+		Field2 *string           `validate:"excluded_without_all=Field FieldE" json:"field_2"`
+		Field3 map[string]string `validate:"excluded_without_all=Field FieldE" json:"field_3"`
+		Field4 interface{}       `validate:"excluded_without_all=Field FieldE" json:"field_4"`
+		Field5 string            `validate:"excluded_without_all=Inner.Field Inner2.Field" json:"field_5"`
+	}{
+		Inner:  &Inner{Field: &fieldVal},
+		Inner2: &Inner{Field: &fieldVal},
+		Field:  "populated",
+		FieldE: "populated",
+	}
+
+	validate = New()
+
+	errs = validate.Struct(test3)
+	Equal(t, errs, nil)
 }
 
 func TestRequiredWithAll(t *testing.T) {