Repository: go-playground/validator
Pull request #541 (2019-11-17 07:13:55)

Adds ability to validate oneof for space separated strings
Fixes Or Enhances https://github.com/go-playground/validator/issues/525.

**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.

Change Details:

* Adds the ability to match on space separated strings when using the
`oneof` validation. Space separted strings must be surrounded by single
quotes to be validated as one string. For example:

```
oneof='Awaiting Verification' 'Verified' 'Failed Verification'
```

passes validation for a field that is exactly `Failed Verification`
(though just `Failed` would...fail).

@go-playground/admins


Patch:
diff --git a/README.md b/README.md
index 250da3e..f712fff 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.0.1-green.svg)
+![Project status](https://img.shields.io/badge/version-10.0.0-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)
@@ -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 http://godoc.org/github.com/go-playground/validator/v10 for detailed usage docs.
 
 ##### Examples:
 
diff --git a/baked_in.go b/baked_in.go
index 23f76b3..ff4da23 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -177,7 +177,10 @@ func parseOneOfParam2(s string) []string {
 	oneofValsCacheRWLock.RUnlock()
 	if !ok {
 		oneofValsCacheRWLock.Lock()
-		vals = strings.Fields(s)
+		vals = splitParamsRegex.FindAllString(s, -1)
+		for i := 0; i < len(vals); i++ {
+			vals[i] = strings.Replace(vals[i], "'", "", -1)
+		}
 		oneofValsCache[s] = vals
 		oneofValsCacheRWLock.Unlock()
 	}
@@ -1316,11 +1319,11 @@ func hasValue(fl FieldLevel) bool {
 // requireCheckField is a func for check field kind
 func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue bool) bool {
 	field := fl.Field()
+	var ok bool
 	kind := field.Kind()
-	var nullable, found bool
 	if len(param) > 0 {
-		field, kind, nullable, found = fl.GetStructFieldOKAdvanced2(fl.Parent(), param)
-		if !found {
+		field, kind, ok = fl.GetStructFieldOKAdvanced(fl.Parent(), param)
+		if !ok {
 			return defaultNotFoundValue
 		}
 	}
@@ -1328,12 +1331,9 @@ func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue boo
 	case reflect.Invalid:
 		return defaultNotFoundValue
 	case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
-		return field.IsNil()
+		return !field.IsNil()
 	default:
-		if nullable && field.Interface() != nil {
-			return false
-		}
-		return field.IsValid() && field.Interface() == reflect.Zero(field.Type()).Interface()
+		return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface()
 	}
 }
 
@@ -1342,7 +1342,7 @@ func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue boo
 func requiredWith(fl FieldLevel) bool {
 	params := parseOneOfParam2(fl.Param())
 	for _, param := range params {
-		if !requireCheckFieldKind(fl, param, true) {
+		if requireCheckFieldKind(fl, param, false) {
 			return hasValue(fl)
 		}
 	}
@@ -1354,7 +1354,7 @@ func requiredWith(fl FieldLevel) bool {
 func requiredWithAll(fl FieldLevel) bool {
 	params := parseOneOfParam2(fl.Param())
 	for _, param := range params {
-		if requireCheckFieldKind(fl, param, true) {
+		if !requireCheckFieldKind(fl, param, false) {
 			return true
 		}
 	}
@@ -1364,8 +1364,11 @@ func requiredWithAll(fl FieldLevel) bool {
 // RequiredWithout is the validation function
 // The field under validation must be present and not empty only when any of the other specified fields are not present.
 func requiredWithout(fl FieldLevel) bool {
-	if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) {
-		return hasValue(fl)
+	params := parseOneOfParam2(fl.Param())
+	for _, param := range params {
+		if !requireCheckFieldKind(fl, param, true) {
+			return hasValue(fl)
+		}
 	}
 	return true
 }
@@ -1375,7 +1378,7 @@ func requiredWithout(fl FieldLevel) bool {
 func requiredWithoutAll(fl FieldLevel) bool {
 	params := parseOneOfParam2(fl.Param())
 	for _, param := range params {
-		if !requireCheckFieldKind(fl, param, true) {
+		if requireCheckFieldKind(fl, param, true) {
 			return true
 		}
 	}
diff --git a/doc.go b/doc.go
index 95029dd..cdfb759 100644
--- a/doc.go
+++ b/doc.go
@@ -361,10 +361,12 @@ 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
-a list of values separated by whitespace.  Values may be
-strings or numbers.
+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
 
 Greater Than
diff --git a/field_level.go b/field_level.go
index 7a13f33..24bc134 100644
--- a/field_level.go
+++ b/field_level.go
@@ -37,27 +37,11 @@ type FieldLevel interface {
 	//
 	// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
 	// could not be retrieved because it didn't exist.
-	//
-	// Deprecated: Use GetStructFieldOK2() instead which also return if the value is nullable.
 	GetStructFieldOK() (reflect.Value, reflect.Kind, bool)
 
 	// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
 	// the field and namespace allowing more extensibility for validators.
-	//
-	// Deprecated: Use GetStructFieldOKAdvanced2() instead which also return if the value is nullable.
 	GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool)
-
-	// traverses the parent struct to retrieve a specific field denoted by the provided namespace
-	// in the param and returns the field, field kind, if it's a nullable type and whether is was successful in retrieving
-	// the field at all.
-	//
-	// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
-	// could not be retrieved because it didn't exist.
-	GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool)
-
-	// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
-	// the field and namespace allowing more extensibility for validators.
-	GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool)
 }
 
 var _ FieldLevel = new(validate)
@@ -68,7 +52,7 @@ func (v *validate) Field() reflect.Value {
 }
 
 // FieldName returns the field's name with the tag
-// name taking precedence over the fields actual name.
+// name takeing precedence over the fields actual name.
 func (v *validate) FieldName() string {
 	return v.cf.altName
 }
@@ -84,29 +68,12 @@ func (v *validate) Param() string {
 }
 
 // GetStructFieldOK returns Param returns param for validation against current field
-//
-// Deprecated: Use GetStructFieldOK2() instead which also return if the value is nullable.
 func (v *validate) GetStructFieldOK() (reflect.Value, reflect.Kind, bool) {
-	current, kind, _, found := v.getStructFieldOKInternal(v.slflParent, v.ct.param)
-	return current, kind, found
-}
-
-// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
-// the field and namespace allowing more extensibility for validators.
-//
-// Deprecated: Use GetStructFieldOKAdvanced2() instead which also return if the value is nullable.
-func (v *validate) GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) {
-	current, kind, _, found := v.GetStructFieldOKAdvanced2(val, namespace)
-	return current, kind, found
-}
-
-// GetStructFieldOK returns Param returns param for validation against current field
-func (v *validate) GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool) {
 	return v.getStructFieldOKInternal(v.slflParent, v.ct.param)
 }
 
 // GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
 // the field and namespace allowing more extensibility for validators.
-func (v *validate) GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool) {
+func (v *validate) GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) {
 	return v.getStructFieldOKInternal(val, namespace)
 }
diff --git a/regexes.go b/regexes.go
index 0253d70..fac2545 100644
--- a/regexes.go
+++ b/regexes.go
@@ -46,6 +46,7 @@ const (
 	uRLEncodedRegexString            = `(%[A-Fa-f0-9]{2})`
 	hTMLEncodedRegexString           = `&#[x]?([0-9a-fA-F]{2})|(&gt)|(&lt)|(&quot)|(&amp)+[;]?`
 	hTMLRegexString                  = `<[/]?([a-zA-Z]+).*?>`
+	splitParamsRegexString           = `'[^']*'|\S+`
 )
 
 var (
@@ -92,4 +93,5 @@ var (
 	uRLEncodedRegex            = regexp.MustCompile(uRLEncodedRegexString)
 	hTMLEncodedRegex           = regexp.MustCompile(hTMLEncodedRegexString)
 	hTMLRegex                  = regexp.MustCompile(hTMLRegexString)
+	splitParamsRegex           = regexp.MustCompile(splitParamsRegexString)
 )
diff --git a/util.go b/util.go
index 71acbdc..16a5517 100644
--- a/util.go
+++ b/util.go
@@ -57,10 +57,11 @@ BEGIN:
 //
 // NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
 // could not be retrieved because it didn't exist.
-func (v *validate) getStructFieldOKInternal(val reflect.Value, namespace string) (current reflect.Value, kind reflect.Kind, nullable bool, found bool) {
+func (v *validate) getStructFieldOKInternal(val reflect.Value, namespace string) (current reflect.Value, kind reflect.Kind, found bool) {
 
 BEGIN:
-	current, kind, nullable = v.ExtractType(val)
+	current, kind, _ = v.ExtractType(val)
+
 	if kind == reflect.Invalid {
 		return
 	}
@@ -111,7 +112,7 @@ BEGIN:
 		arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2])
 
 		if arrIdx >= current.Len() {
-			return
+			return current, kind, false
 		}
 
 		startIdx := idx2 + 1
diff --git a/validator.go b/validator.go
index 342e72e..3abf5d3 100644
--- a/validator.go
+++ b/validator.go
@@ -7,7 +7,7 @@ import (
 	"strconv"
 )
 
-// per validate construct
+// per validate contruct
 type validate struct {
 	v              *Validate
 	top            reflect.Value


Test patch:
diff --git a/validator_test.go b/validator_test.go
index 2787f9e..e64a91b 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -1640,125 +1640,125 @@ func TestCrossNamespaceFieldValidation(t *testing.T) {
 		v: vd,
 	}
 
-	current, kind, _, ok := v.getStructFieldOKInternal(val, "Inner.CreatedAt")
+	current, kind, ok := v.getStructFieldOKInternal(val, "Inner.CreatedAt")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.Struct)
 	tm, ok := current.Interface().(time.Time)
 	Equal(t, ok, true)
 	Equal(t, tm, now)
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[1]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.Slice[1]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, _, _, ok = v.getStructFieldOKInternal(val, "Inner.CrazyNonExistantField")
+	current, _, ok = v.getStructFieldOKInternal(val, "Inner.CrazyNonExistantField")
 	Equal(t, ok, false)
 
-	current, _, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[101]")
+	current, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[101]")
 	Equal(t, ok, false)
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.Map[key3]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.Map[key3]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val3")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapMap[key2][key2-1]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapMap[key2][key2-1]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapStructs[key2].Name")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapStructs[key2].Name")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "name2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapMapStruct[key3][key3-1].Name")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapMapStruct[key3][key3-1].Name")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "name3")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceSlice[2][0]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.SliceSlice[2][0]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "7")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceSliceStruct[2][1].Name")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.SliceSliceStruct[2][1].Name")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "name8")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceMap[1][key5]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.SliceMap[1][key5]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val5")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapSlice[key3][2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapSlice[key3][2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "9")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt[2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapInt[2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt8[2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapInt8[2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt16[2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapInt16[2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt32[2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapInt32[2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt64[2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapInt64[2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint[2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapUint[2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint8[2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapUint8[2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint16[2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapUint16[2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint32[2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapUint32[2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint64[2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapUint64[2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapFloat32[3.03]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapFloat32[3.03]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val3")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapFloat64[2.02]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapFloat64[2.02]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val2")
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapBool[true]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapBool[true]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.String)
 	Equal(t, current.String(), "val1")
@@ -1784,13 +1784,13 @@ func TestCrossNamespaceFieldValidation(t *testing.T) {
 
 	val = reflect.ValueOf(test)
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceStructs[2]")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.SliceStructs[2]")
 	Equal(t, ok, true)
 	Equal(t, kind, reflect.Ptr)
 	Equal(t, current.String(), "<*validator.SliceStruct Value>")
 	Equal(t, current.IsNil(), true)
 
-	current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceStructs[2].Name")
+	current, kind, ok = v.getStructFieldOKInternal(val, "Inner.SliceStructs[2].Name")
 	Equal(t, ok, false)
 	Equal(t, kind, reflect.Ptr)
 	Equal(t, current.String(), "<*validator.SliceStruct Value>")
@@ -4484,6 +4484,8 @@ func TestOneOfValidation(t *testing.T) {
 	}{
 		{f: "red", t: "oneof=red green"},
 		{f: "green", t: "oneof=red green"},
+		{f: "red green", t: "oneof='red green' blue"},
+		{f: "blue", t: "oneof='red green' blue"},
 		{f: 5, t: "oneof=5 6"},
 		{f: 6, t: "oneof=5 6"},
 		{f: int8(6), t: "oneof=5 6"},
@@ -4509,6 +4511,7 @@ func TestOneOfValidation(t *testing.T) {
 	}{
 		{f: "", t: "oneof=red green"},
 		{f: "yellow", t: "oneof=red green"},
+		{f: "green", t: "oneof='red green' blue"},
 		{f: 5, t: "oneof=red green"},
 		{f: 6, t: "oneof=red green"},
 		{f: 6, t: "oneof=7"},
@@ -8864,53 +8867,3 @@ func TestAbilityToValidateNils(t *testing.T) {
 	errs = val.Struct(ts)
 	NotEqual(t, errs, nil)
 }
-
-func TestRequiredWithoutPointers(t *testing.T) {
-	type Lookup struct {
-		FieldA *bool `json:"fieldA,omitempty" validate:"required_without=FieldB"`
-		FieldB *bool `json:"fieldB,omitempty" validate:"required_without=FieldA"`
-	}
-
-	b := true
-	lookup := Lookup{
-		FieldA: &b,
-		FieldB: nil,
-	}
-
-	val := New()
-	errs := val.Struct(lookup)
-	Equal(t, errs, nil)
-
-	b = false
-	lookup = Lookup{
-		FieldA: &b,
-		FieldB: nil,
-	}
-	errs = val.Struct(lookup)
-	Equal(t, errs, nil)
-}
-
-func TestRequiredWithoutAllPointers(t *testing.T) {
-	type Lookup struct {
-		FieldA *bool `json:"fieldA,omitempty" validate:"required_without_all=FieldB"`
-		FieldB *bool `json:"fieldB,omitempty" validate:"required_without_all=FieldA"`
-	}
-
-	b := true
-	lookup := Lookup{
-		FieldA: &b,
-		FieldB: nil,
-	}
-
-	val := New()
-	errs := val.Struct(lookup)
-	Equal(t, errs, nil)
-
-	b = false
-	lookup = Lookup{
-		FieldA: &b,
-		FieldB: nil,
-	}
-	errs = val.Struct(lookup)
-	Equal(t, errs, nil)
-}