Skip to content

Commit 6720e73

Browse files
author
Dean Karn
authored
Merge pull request #496 from shihanng/unique_struct_field
Extending unique to ensure uniqueness of a specific field in a slices of struct
2 parents 5bbca66 + 8efe088 commit 6720e73

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

baked_in.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,28 @@ func isOneOf(fl FieldLevel) bool {
225225
func isUnique(fl FieldLevel) bool {
226226

227227
field := fl.Field()
228+
param := fl.Param()
228229
v := reflect.ValueOf(struct{}{})
229230

230231
switch field.Kind() {
231232
case reflect.Slice, reflect.Array:
232-
m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
233+
if param == "" {
234+
m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
235+
236+
for i := 0; i < field.Len(); i++ {
237+
m.SetMapIndex(field.Index(i), v)
238+
}
239+
return field.Len() == m.Len()
240+
}
241+
242+
sf, ok := field.Type().Elem().FieldByName(param)
243+
if !ok {
244+
panic(fmt.Sprintf("Bad field name %s", param))
245+
}
233246

247+
m := reflect.MakeMap(reflect.MapOf(sf.Type, v.Type()))
234248
for i := 0; i < field.Len(); i++ {
235-
m.SetMapIndex(field.Index(i), v)
249+
m.SetMapIndex(field.Index(i).FieldByName(param), v)
236250
}
237251
return field.Len() == m.Len()
238252
case reflect.Map:

doc.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,9 +585,15 @@ Unique
585585
586586
For arrays & slices, unique will ensure that there are no duplicates.
587587
For maps, unique will ensure that there are no duplicate values.
588+
For slices of struct, unique will ensure that there are no duplicate values
589+
in a field of the struct specified via a parameter.
588590
591+
// For arrays, slices, and maps:
589592
Usage: unique
590593
594+
// For slices of struct:
595+
Usage: unique=field
596+
591597
Alpha Only
592598
593599
This validates that a string value contains ASCII alpha characters only

validator_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8160,6 +8160,49 @@ func TestUniqueValidation(t *testing.T) {
81608160
PanicMatches(t, func() { _ = validate.Var(1.0, "unique") }, "Bad field type float64")
81618161
}
81628162

8163+
func TestUniqueValidationStructSlice(t *testing.T) {
8164+
testStructs := []struct {
8165+
A string
8166+
B string
8167+
}{
8168+
{A: "one", B: "two"},
8169+
{A: "one", B: "three"},
8170+
}
8171+
8172+
tests := []struct {
8173+
target interface{}
8174+
param string
8175+
expected bool
8176+
}{
8177+
{testStructs, "unique", true},
8178+
{testStructs, "unique=A", false},
8179+
{testStructs, "unique=B", true},
8180+
}
8181+
8182+
validate := New()
8183+
8184+
for i, test := range tests {
8185+
8186+
errs := validate.Var(test.target, test.param)
8187+
8188+
if test.expected {
8189+
if !IsEqual(errs, nil) {
8190+
t.Fatalf("Index: %d unique failed Error: %v", i, errs)
8191+
}
8192+
} else {
8193+
if IsEqual(errs, nil) {
8194+
t.Fatalf("Index: %d unique failed Error: %v", i, errs)
8195+
} else {
8196+
val := getError(errs, "", "")
8197+
if val.Tag() != "unique" {
8198+
t.Fatalf("Index: %d unique failed Error: %v", i, errs)
8199+
}
8200+
}
8201+
}
8202+
}
8203+
PanicMatches(t, func() { validate.Var(testStructs, "unique=C") }, "Bad field name C")
8204+
}
8205+
81638206
func TestHTMLValidation(t *testing.T) {
81648207
tests := []struct {
81658208
param string

0 commit comments

Comments
 (0)