Repository: go-playground/validator
Pull request #644 (2020-07-29 16:05:36)

Make unique tag work with pointer fields.
Fixes Or Enhances #627 .

**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:

- Compares the underlying values of the pointers for uniqueness when using the unique tag with a pointer field.

@go-playground/admins


Patch:
diff --git a/baked_in.go b/baked_in.go
index 36e8057..902eb4e 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -241,23 +241,33 @@ func isUnique(fl FieldLevel) bool {
 
 	switch field.Kind() {
 	case reflect.Slice, reflect.Array:
+		elem := field.Type().Elem()
+		if elem.Kind() == reflect.Ptr {
+			elem = elem.Elem()
+		}
+
 		if param == "" {
-			m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
+			m := reflect.MakeMap(reflect.MapOf(elem, v.Type()))
 
 			for i := 0; i < field.Len(); i++ {
-				m.SetMapIndex(field.Index(i), v)
+				m.SetMapIndex(reflect.Indirect(field.Index(i)), v)
 			}
 			return field.Len() == m.Len()
 		}
 
-		sf, ok := field.Type().Elem().FieldByName(param)
+		sf, ok := elem.FieldByName(param)
 		if !ok {
 			panic(fmt.Sprintf("Bad field name %s", param))
 		}
 
-		m := reflect.MakeMap(reflect.MapOf(sf.Type, v.Type()))
+		sfTyp := sf.Type
+		if sfTyp.Kind() == reflect.Ptr {
+			sfTyp = sfTyp.Elem()
+		}
+
+		m := reflect.MakeMap(reflect.MapOf(sfTyp, v.Type()))
 		for i := 0; i < field.Len(); i++ {
-			m.SetMapIndex(field.Index(i).FieldByName(param), v)
+			m.SetMapIndex(reflect.Indirect(reflect.Indirect(field.Index(i)).FieldByName(param)), v)
 		}
 		return field.Len() == m.Len()
 	case reflect.Map:


Test patch:
diff --git a/validator_test.go b/validator_test.go
index e76a3cd..00bd11a 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -346,6 +346,18 @@ func StructLevelInvalidError(sl StructLevel) {
 	}
 }
 
+func stringPtr(v string) *string {
+	return &v
+}
+
+func intPtr(v int) *int {
+	return &v
+}
+
+func float64Ptr(v float64) *float64 {
+	return &v
+}
+
 func TestStructLevelInvalidError(t *testing.T) {
 
 	validate := New()
@@ -8144,6 +8156,12 @@ func TestUniqueValidation(t *testing.T) {
 		{[2]string{"a", "a"}, false},
 		{[2]interface{}{"a", "a"}, false},
 		{[4]interface{}{"a", 1, "b", 1}, false},
+		{[2]*string{stringPtr("a"), stringPtr("b")}, true},
+		{[2]*int{intPtr(1), intPtr(2)}, true},
+		{[2]*float64{float64Ptr(1), float64Ptr(2)}, true},
+		{[2]*string{stringPtr("a"), stringPtr("a")}, false},
+		{[2]*float64{float64Ptr(1), float64Ptr(1)}, false},
+		{[2]*int{intPtr(1), intPtr(1)}, false},
 		// Slices
 		{[]string{"a", "b"}, true},
 		{[]int{1, 2}, true},
@@ -8155,6 +8173,12 @@ func TestUniqueValidation(t *testing.T) {
 		{[]string{"a", "a"}, false},
 		{[]interface{}{"a", "a"}, false},
 		{[]interface{}{"a", 1, "b", 1}, false},
+		{[]*string{stringPtr("a"), stringPtr("b")}, true},
+		{[]*int{intPtr(1), intPtr(2)}, true},
+		{[]*float64{float64Ptr(1), float64Ptr(2)}, true},
+		{[]*string{stringPtr("a"), stringPtr("a")}, false},
+		{[]*float64{float64Ptr(1), float64Ptr(1)}, false},
+		{[]*int{intPtr(1), intPtr(1)}, false},
 		// Maps
 		{map[string]string{"one": "a", "two": "b"}, true},
 		{map[string]int{"one": 1, "two": 2}, true},
@@ -8235,6 +8259,49 @@ func TestUniqueValidationStructSlice(t *testing.T) {
 	PanicMatches(t, func() { validate.Var(testStructs, "unique=C") }, "Bad field name C")
 }
 
+func TestUniqueValidationStructPtrSlice(t *testing.T) {
+	testStructs := []*struct {
+		A *string
+		B *string
+	}{
+		{A: stringPtr("one"), B: stringPtr("two")},
+		{A: stringPtr("one"), B: stringPtr("three")},
+	}
+
+	tests := []struct {
+		target   interface{}
+		param    string
+		expected bool
+	}{
+		{testStructs, "unique", true},
+		{testStructs, "unique=A", false},
+		{testStructs, "unique=B", true},
+	}
+
+	validate := New()
+
+	for i, test := range tests {
+
+		errs := validate.Var(test.target, test.param)
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Index: %d unique failed Error: %v", i, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Index: %d unique failed Error: %v", i, errs)
+			} else {
+				val := getError(errs, "", "")
+				if val.Tag() != "unique" {
+					t.Fatalf("Index: %d unique failed Error: %v", i, errs)
+				}
+			}
+		}
+	}
+	PanicMatches(t, func() { validate.Var(testStructs, "unique=C") }, "Bad field name C")
+}
+
 func TestHTMLValidation(t *testing.T) {
 	tests := []struct {
 		param    string