Skip to content

Commit 1521730

Browse files
committed
Add oneof validation
Validates that a string or number value equals one of a set of allowed values. Examples: validator.Var(“red”, “oneof=red green”) validator.Var(6, “oneof=6 7 8”)
1 parent 535f852 commit 1521730

File tree

5 files changed

+130
-0
lines changed

5 files changed

+130
-0
lines changed

baked_in.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"net"
77
"net/url"
88
"reflect"
9+
"strconv"
910
"strings"
11+
"sync"
1012
"time"
1113
"unicode/utf8"
1214
)
@@ -135,9 +137,44 @@ var (
135137
"hostname_rfc1123": isHostnameRFC1123, // RFC 1123
136138
"fqdn": isFQDN,
137139
"unique": isUnique,
140+
"oneof": isOneOf,
138141
}
139142
)
140143

144+
var oneofValCache = map[string][]string{}
145+
var oneofValCacheLock = sync.Mutex{}
146+
147+
func isOneOf(fl FieldLevel) bool {
148+
param := fl.Param()
149+
oneofValCacheLock.Lock()
150+
vals, ok := oneofValCache[param]
151+
if !ok {
152+
vals = strings.Fields(param)
153+
oneofValCache[param] = vals
154+
}
155+
oneofValCacheLock.Unlock()
156+
157+
field := fl.Field()
158+
159+
var v string
160+
switch field.Kind() {
161+
case reflect.String:
162+
v = field.String()
163+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
164+
v = strconv.FormatInt(field.Int(), 10)
165+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
166+
v = strconv.FormatUint(field.Uint(), 10)
167+
default:
168+
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
169+
}
170+
for i := 0; i < len(vals); i++ {
171+
if vals[i] == v {
172+
return true
173+
}
174+
}
175+
return false
176+
}
177+
141178
// isUnique is the validation function for validating if each array|slice element is unique
142179
func isUnique(fl FieldLevel) bool {
143180

doc.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,16 @@ validates the number of items.
295295
296296
Usage: ne=10
297297
298+
One Of
299+
300+
For strings, ints, and uints, oneof will ensure that the value
301+
is one of the values in the parameter. The parameter should be
302+
a list of values separated by whitespace. Values may be
303+
strings or numbers.
304+
305+
Usage: oneof=red green
306+
oneof=5 7 9
307+
298308
Greater Than
299309
300310
For numbers, this will ensure that the value is greater than the

translations/en/en.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,19 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
12941294
translation: "{0} must be a valid color",
12951295
override: false,
12961296
},
1297+
{
1298+
tag: "oneof",
1299+
translation: "{0} must be one of [{1}]",
1300+
override: false,
1301+
customTransFunc: func(ut ut.Translator, fe validator.FieldError) string {
1302+
s, err := ut.T(fe.Tag(), fe.Field(), fe.Param())
1303+
if err != nil {
1304+
log.Printf("warning: error translating FieldError: %#v", fe)
1305+
return fe.(error).Error()
1306+
}
1307+
return s
1308+
},
1309+
},
12971310
}
12981311

12991312
for _, t := range translations {

translations/en/en_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ func TestTranslations(t *testing.T) {
136136
StrPtrLte *string `validate:"lte=1"`
137137
StrPtrGt *string `validate:"gt=10"`
138138
StrPtrGte *string `validate:"gte=10"`
139+
OneOfString string `validate:"oneof=red green"`
140+
OneOfInt int `validate:"oneof=5 63"`
139141
}
140142

141143
var test Test
@@ -604,6 +606,14 @@ func TestTranslations(t *testing.T) {
604606
ns: "Test.StrPtrGte",
605607
expected: "StrPtrGte must be at least 10 characters in length",
606608
},
609+
{
610+
ns: "Test.OneOfString",
611+
expected: "OneOfString must be one of [red green]",
612+
},
613+
{
614+
ns: "Test.OneOfInt",
615+
expected: "OneOfInt must be one of [5 63]",
616+
},
607617
}
608618

609619
for _, tt := range tests {

validator_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4314,6 +4314,66 @@ func TestIsEqValidation(t *testing.T) {
43144314
PanicMatches(t, func() { validate.Var(now, "eq=now") }, "Bad field type time.Time")
43154315
}
43164316

4317+
func TestOneOfValidation(t *testing.T) {
4318+
validate := New()
4319+
4320+
passSpecs := []struct {
4321+
f interface{}
4322+
t string
4323+
}{
4324+
{f: "red", t: "oneof=red green"},
4325+
{f: "green", t: "oneof=red green"},
4326+
{f: 5, t: "oneof=5 6"},
4327+
{f: 6, t: "oneof=5 6"},
4328+
{f: int8(6), t: "oneof=5 6"},
4329+
{f: int16(6), t: "oneof=5 6"},
4330+
{f: int32(6), t: "oneof=5 6"},
4331+
{f: int64(6), t: "oneof=5 6"},
4332+
{f: uint(6), t: "oneof=5 6"},
4333+
{f: uint8(6), t: "oneof=5 6"},
4334+
{f: uint16(6), t: "oneof=5 6"},
4335+
{f: uint32(6), t: "oneof=5 6"},
4336+
{f: uint64(6), t: "oneof=5 6"},
4337+
}
4338+
4339+
for _, spec := range passSpecs {
4340+
t.Logf("%#v", spec)
4341+
errs := validate.Var(spec.f, spec.t)
4342+
Equal(t, errs, nil)
4343+
}
4344+
4345+
failSpecs := []struct {
4346+
f interface{}
4347+
t string
4348+
}{
4349+
{f: "", t: "oneof=red green"},
4350+
{f: "yellow", t: "oneof=red green"},
4351+
{f: 5, t: "oneof=red green"},
4352+
{f: 6, t: "oneof=red green"},
4353+
{f: 6, t: "oneof=7"},
4354+
{f: uint(6), t: "oneof=7"},
4355+
{f: int8(5), t: "oneof=red green"},
4356+
{f: int16(5), t: "oneof=red green"},
4357+
{f: int32(5), t: "oneof=red green"},
4358+
{f: int64(5), t: "oneof=red green"},
4359+
{f: uint(5), t: "oneof=red green"},
4360+
{f: uint8(5), t: "oneof=red green"},
4361+
{f: uint16(5), t: "oneof=red green"},
4362+
{f: uint32(5), t: "oneof=red green"},
4363+
{f: uint64(5), t: "oneof=red green"},
4364+
}
4365+
4366+
for _, spec := range failSpecs {
4367+
t.Logf("%#v", spec)
4368+
errs := validate.Var(spec.f, spec.t)
4369+
AssertError(t, errs, "", "", "", "", "oneof")
4370+
}
4371+
4372+
PanicMatches(t, func() {
4373+
validate.Var(3.14, "oneof=red green")
4374+
}, "Bad field type float64")
4375+
}
4376+
43174377
func TestBase64Validation(t *testing.T) {
43184378

43194379
validate := New()

0 commit comments

Comments
 (0)