Skip to content

Commit a4eee8d

Browse files
committed
pr feedback: unique_slice_with_nil_and_zero_value_struct
1 parent 77361f2 commit a4eee8d

File tree

2 files changed

+84
-53
lines changed

2 files changed

+84
-53
lines changed

baked_in.go

Lines changed: 76 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -336,87 +336,110 @@ func isOneOfCI(fl FieldLevel) bool {
336336
func isUnique(fl FieldLevel) bool {
337337
field := fl.Field()
338338
param := fl.Param()
339-
v := reflect.ValueOf(struct{}{})
339+
340+
// sentinel used as map key for nil values
341+
var nilKey = struct{}{}
340342

341343
switch field.Kind() {
342344
case reflect.Slice, reflect.Array:
343-
elem := field.Type().Elem()
344-
if elem.Kind() == reflect.Ptr {
345-
elem = elem.Elem()
346-
}
345+
seen := make(map[interface{}]struct{})
347346

348-
if param == "" {
349-
m := reflect.MakeMap(reflect.MapOf(elem, v.Type()))
350-
zero := reflect.Zero(elem)
347+
for i := 0; i < field.Len(); i++ {
348+
elem := field.Index(i)
349+
350+
// -------- unique (no param) --------
351+
if param == "" {
352+
var key interface{}
353+
354+
if elem.Kind() == reflect.Ptr {
355+
if elem.IsNil() {
356+
key = nilKey
357+
} else {
358+
key = elem.Elem().Interface() // <-- compare underlying value
359+
}
360+
} else {
361+
key = elem.Interface()
362+
}
351363

352-
for i := 0; i < field.Len(); i++ {
353-
e := reflect.Indirect(field.Index(i))
354-
if !e.IsValid() {
355-
m.SetMapIndex(zero, v)
356-
continue
364+
if _, ok := seen[key]; ok {
365+
return false
357366
}
358-
m.SetMapIndex(e, v)
367+
seen[key] = struct{}{}
368+
continue
359369
}
360-
return field.Len() == m.Len()
361-
}
362370

363-
sf, ok := elem.FieldByName(param)
364-
if !ok {
365-
panic(fmt.Sprintf("Bad field name %s", param))
366-
}
371+
// -------- unique=Field --------
367372

368-
sfTyp := sf.Type
369-
if sfTyp.Kind() == reflect.Ptr {
370-
sfTyp = sfTyp.Elem()
371-
}
373+
if elem.Kind() == reflect.Ptr {
374+
if elem.IsNil() {
375+
if _, ok := seen[nilKey]; ok {
376+
return false
377+
}
378+
seen[nilKey] = struct{}{}
379+
continue
380+
}
381+
elem = elem.Elem()
382+
}
372383

373-
m := reflect.MakeMap(reflect.MapOf(sfTyp, v.Type()))
374-
zero := reflect.Zero(sfTyp)
384+
if elem.Kind() != reflect.Struct {
385+
panic(fmt.Sprintf("Bad field type %s", elem.Type()))
386+
}
375387

376-
for i := 0; i < field.Len(); i++ {
377-
parent := reflect.Indirect(field.Index(i))
378-
if !parent.IsValid() {
379-
m.SetMapIndex(zero, v)
380-
continue
388+
sf := elem.FieldByName(param)
389+
if !sf.IsValid() {
390+
panic(fmt.Sprintf("Bad field name %s", param))
381391
}
382392

383-
key := reflect.Indirect(parent.FieldByName(param))
384-
if !key.IsValid() {
385-
m.SetMapIndex(zero, v)
386-
continue
393+
var key interface{}
394+
395+
if sf.Kind() == reflect.Ptr {
396+
if sf.IsNil() {
397+
key = nilKey
398+
} else {
399+
key = sf.Elem().Interface()
400+
}
401+
} else {
402+
key = sf.Interface()
387403
}
388404

389-
m.SetMapIndex(key, v)
405+
if _, ok := seen[key]; ok {
406+
return false
407+
}
408+
seen[key] = struct{}{}
390409
}
391410

392-
return field.Len() == m.Len()
411+
return true
393412

394413
case reflect.Map:
395-
var keyType reflect.Type
396-
if field.Type().Elem().Kind() == reflect.Ptr {
397-
keyType = field.Type().Elem().Elem()
398-
} else {
399-
keyType = field.Type().Elem()
400-
}
401-
402-
m := reflect.MakeMap(reflect.MapOf(keyType, v.Type()))
403-
zero := reflect.Zero(keyType)
414+
seen := make(map[interface{}]struct{})
404415

405416
for _, k := range field.MapKeys() {
406-
val := reflect.Indirect(field.MapIndex(k))
407-
if !val.IsValid() {
408-
m.SetMapIndex(zero, v)
409-
continue
417+
val := field.MapIndex(k)
418+
419+
var key interface{}
420+
421+
if val.Kind() == reflect.Ptr {
422+
if val.IsNil() {
423+
key = nilKey
424+
} else {
425+
key = val.Elem().Interface() // <-- compare underlying value
426+
}
427+
} else {
428+
key = val.Interface()
429+
}
430+
431+
if _, ok := seen[key]; ok {
432+
return false
410433
}
411-
m.SetMapIndex(val, v)
434+
seen[key] = struct{}{}
412435
}
413436

414-
return field.Len() == m.Len()
437+
return true
415438

416439
default:
417440
if parent := fl.Parent(); parent.Kind() == reflect.Struct {
418441
uniqueField := parent.FieldByName(param)
419-
if uniqueField == reflect.ValueOf(nil) {
442+
if !uniqueField.IsValid() {
420443
panic(fmt.Sprintf("Bad field name provided %s", param))
421444
}
422445

validator_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11029,6 +11029,14 @@ func TestUniqueValidationNilPtrSlice(t *testing.T) {
1102911029
t.Fatalf("nil and non-nil map values should pass unique validation, got: %v", errs)
1103011030
}
1103111031
})
11032+
11033+
t.Run("unique_slice_with_nil_and_zero_value_struct", func(t *testing.T) {
11034+
s := []*Inner{nil, {Name: ""}}
11035+
errs := validate.Var(s, "unique")
11036+
if errs != nil {
11037+
t.Fatalf("nil and zero value struct should pass unique validation, got: %v", errs)
11038+
}
11039+
})
1103211040
}
1103311041

1103411042
func TestHTMLValidation(t *testing.T) {

0 commit comments

Comments
 (0)