From d7abf3244e3d4f860c36def311aad53f8e85a45a Mon Sep 17 00:00:00 2001 From: Connor Carnes Date: Sat, 16 Nov 2024 10:44:02 -0600 Subject: [PATCH] Add oneofci validator (oneof case insensitive) (#1321) ## Fixes Or Enhances - Adds oneofci, a case insensitive version of oneof - resolves https://github.com/go-playground/validator/issues/840 **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 --- baked_in.go | 18 +++++++++++ doc.go | 7 +++++ validator_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) diff --git a/baked_in.go b/baked_in.go index d1a3656ac..a3a9bf6f8 100644 --- a/baked_in.go +++ b/baked_in.go @@ -205,6 +205,7 @@ var ( "fqdn": isFQDN, "unique": isUnique, "oneof": isOneOf, + "oneofci": isOneOfCI, "html": isHTML, "html_encoded": isHTMLEncoded, "url_encoded": isURLEncoded, @@ -299,6 +300,23 @@ func isOneOf(fl FieldLevel) bool { return false } +// isOneOfCI is the validation function for validating if the current field's value is one of the provided string values (case insensitive). +func isOneOfCI(fl FieldLevel) bool { + vals := parseOneOfParam2(fl.Param()) + field := fl.Field() + + if field.Kind() != reflect.String { + panic(fmt.Sprintf("Bad field type %T", field.Interface())) + } + v := field.String() + for _, val := range vals { + if strings.EqualFold(val, v) { + return true + } + } + return false +} + // isUnique is the validation function for validating if each array|slice|map value is unique func isUnique(fl FieldLevel) bool { field := fl.Field() diff --git a/doc.go b/doc.go index 83d33824e..c9b1616ee 100644 --- a/doc.go +++ b/doc.go @@ -495,6 +495,13 @@ the target string between single quotes. Kind of like an 'enum'. oneof='red green' 'blue yellow' oneof=5 7 9 +# One Of Case Insensitive + +Works the same as oneof but is case insensitive and therefore only accepts strings. + + Usage: oneofci=red green + oneofci='red green' 'blue yellow' + # Greater Than For numbers, this will ensure that the value is greater than the diff --git a/validator_test.go b/validator_test.go index 6bb5b2988..863da2ec3 100644 --- a/validator_test.go +++ b/validator_test.go @@ -5680,6 +5680,82 @@ func TestOneOfValidation(t *testing.T) { }, "Bad field type float64") } +func TestOneOfCIValidation(t *testing.T) { + validate := New() + + passSpecs := []struct { + f interface{} + t string + }{ + {f: "red", t: "oneofci=RED GREEN"}, + {f: "RED", t: "oneofci=red green"}, + {f: "red", t: "oneofci=red green"}, + {f: "RED", t: "oneofci=RED GREEN"}, + {f: "green", t: "oneofci=red green"}, + {f: "red green", t: "oneofci='red green' blue"}, + {f: "blue", t: "oneofci='red green' blue"}, + {f: "GREEN", t: "oneofci=Red Green"}, + {f: "ReD", t: "oneofci=RED GREEN"}, + {f: "gReEn", t: "oneofci=rEd GrEeN"}, + {f: "RED GREEN", t: "oneofci='red green' blue"}, + {f: "red Green", t: "oneofci='RED GREEN' Blue"}, + {f: "Red green", t: "oneofci='Red Green' BLUE"}, + {f: "rEd GrEeN", t: "oneofci='ReD gReEn' BlUe"}, + {f: "BLUE", t: "oneofci='Red Green' BLUE"}, + {f: "BlUe", t: "oneofci='RED GREEN' Blue"}, + {f: "bLuE", t: "oneofci='red green' BLUE"}, + } + + for _, spec := range passSpecs { + t.Logf("%#v", spec) + errs := validate.Var(spec.f, spec.t) + Equal(t, errs, nil) + } + + failSpecs := []struct { + f interface{} + t string + }{ + {f: "", t: "oneofci=red green"}, + {f: "yellow", t: "oneofci=red green"}, + {f: "green", t: "oneofci='red green' blue"}, + } + + for _, spec := range failSpecs { + t.Logf("%#v", spec) + errs := validate.Var(spec.f, spec.t) + AssertError(t, errs, "", "", "", "", "oneofci") + } + + panicSpecs := []struct { + f interface{} + t string + }{ + {f: 3.14, t: "oneofci=red green"}, + {f: 5, t: "oneofci=red green"}, + {f: uint(6), t: "oneofci=7"}, + {f: int8(5), t: "oneofci=red green"}, + {f: int16(5), t: "oneofci=red green"}, + {f: int32(5), t: "oneofci=red green"}, + {f: int64(5), t: "oneofci=red green"}, + {f: uint(5), t: "oneofci=red green"}, + {f: uint8(5), t: "oneofci=red green"}, + {f: uint16(5), t: "oneofci=red green"}, + {f: uint32(5), t: "oneofci=red green"}, + {f: uint64(5), t: "oneofci=red green"}, + } + + panicCount := 0 + for _, spec := range panicSpecs { + t.Logf("%#v", spec) + PanicMatches(t, func() { + _ = validate.Var(spec.f, spec.t) + }, fmt.Sprintf("Bad field type %T", spec.f)) + panicCount++ + } + Equal(t, panicCount, len(panicSpecs)) +} + func TestBase32Validation(t *testing.T) { validate := New()