Extend the unique tag to also cover map values.

pull/361/head
James Service 7 years ago
parent 14984d9132
commit 8ae3903dd3
  1. 33
      baked_in.go
  2. 23
      validator_test.go

@ -1,7 +1,9 @@
package validator package validator
import ( import (
"bytes"
"context" "context"
"crypto/sha256"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
@ -11,8 +13,6 @@ import (
"sync" "sync"
"time" "time"
"unicode/utf8" "unicode/utf8"
"crypto/sha256"
"bytes"
) )
// Func accepts a FieldLevel interface for all validation needs. The return // Func accepts a FieldLevel interface for all validation needs. The return
@ -187,7 +187,7 @@ func isOneOf(fl FieldLevel) bool {
return false return false
} }
// isUnique is the validation function for validating if each array|slice element is unique // isUnique is the validation function for validating if each array|slice|map value is unique
func isUnique(fl FieldLevel) bool { func isUnique(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
@ -195,12 +195,19 @@ func isUnique(fl FieldLevel) bool {
switch field.Kind() { switch field.Kind() {
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
m := reflect.MakeMap(reflect.MapOf(fl.Field().Type().Elem(), v.Type())) m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
for i := 0; i < field.Len(); i++ { for i := 0; i < field.Len(); i++ {
m.SetMapIndex(field.Index(i), v) m.SetMapIndex(field.Index(i), v)
} }
return field.Len() == m.Len() return field.Len() == m.Len()
case reflect.Map:
m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
for _, k := range field.MapKeys() {
m.SetMapIndex(field.MapIndex(k), v)
}
return field.Len() == m.Len()
default: default:
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %T", field.Interface()))
} }
@ -451,13 +458,13 @@ func isBitcoinAddress(fl FieldLevel) bool {
func isBitcoinBech32Address(fl FieldLevel) bool { func isBitcoinBech32Address(fl FieldLevel) bool {
address := fl.Field().String() address := fl.Field().String()
if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address){ if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address) {
return false return false
} }
am := len(address) % 8 am := len(address) % 8
if am == 0 || am == 3 || am == 5{ if am == 0 || am == 3 || am == 5 {
return false return false
} }
@ -474,7 +481,7 @@ func isBitcoinBech32Address(fl FieldLevel) bool {
ver := dp[0] ver := dp[0]
if ver < 0 || ver > 16{ if ver < 0 || ver > 16 {
return false return false
} }
@ -486,16 +493,16 @@ func isBitcoinBech32Address(fl FieldLevel) bool {
values := append(hr, dp...) values := append(hr, dp...)
GEN := []int{ 0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3 } GEN := []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
p := 1 p := 1
for _, v := range values { for _, v := range values {
b := p >> 25 b := p >> 25
p = (p & 0x1ffffff) << 5 ^ v p = (p&0x1ffffff)<<5 ^ v
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
if (b >> uint(i)) & 1 == 1 { if (b>>uint(i))&1 == 1 {
p ^= GEN[i] p ^= GEN[i]
} }
} }
@ -510,16 +517,16 @@ func isBitcoinBech32Address(fl FieldLevel) bool {
mv := (1 << 5) - 1 mv := (1 << 5) - 1
sw := []int{} sw := []int{}
for _, v := range dp[1:len(dp) - 6]{ for _, v := range dp[1 : len(dp)-6] {
acc = (acc << 5) | v acc = (acc << 5) | v
b += 5 b += 5
for b >= 8{ for b >= 8 {
b -= 8 b -= 8
sw = append(sw, (acc>>b)&mv) sw = append(sw, (acc>>b)&mv)
} }
} }
if len(sw) < 2 || len(sw) > 40{ if len(sw) < 2 || len(sw) > 40 {
return false return false
} }

@ -7696,6 +7696,18 @@ func TestUniqueValidation(t *testing.T) {
param interface{} param interface{}
expected bool expected bool
}{ }{
// Arrays
{[2]string{"a", "b"}, true},
{[2]int{1, 2}, true},
{[2]float64{1, 2}, true},
{[2]interface{}{"a", "b"}, true},
{[2]interface{}{"a", 1}, true},
{[2]float64{1, 1}, false},
{[2]int{1, 1}, false},
{[2]string{"a", "a"}, false},
{[2]interface{}{"a", "a"}, false},
{[4]interface{}{"a", 1, "b", 1}, false},
// Slices
{[]string{"a", "b"}, true}, {[]string{"a", "b"}, true},
{[]int{1, 2}, true}, {[]int{1, 2}, true},
{[]float64{1, 2}, true}, {[]float64{1, 2}, true},
@ -7706,6 +7718,17 @@ func TestUniqueValidation(t *testing.T) {
{[]string{"a", "a"}, false}, {[]string{"a", "a"}, false},
{[]interface{}{"a", "a"}, false}, {[]interface{}{"a", "a"}, false},
{[]interface{}{"a", 1, "b", 1}, false}, {[]interface{}{"a", 1, "b", 1}, false},
// Maps
{map[string]string{"one": "a", "two": "b"}, true},
{map[string]int{"one": 1, "two": 2}, true},
{map[string]float64{"one": 1, "two": 2}, true},
{map[string]interface{}{"one": "a", "two": "b"}, true},
{map[string]interface{}{"one": "a", "two": 1}, true},
{map[string]float64{"one": 1, "two": 1}, false},
{map[string]int{"one": 1, "two": 1}, false},
{map[string]string{"one": "a", "two": "a"}, false},
{map[string]interface{}{"one": "a", "two": "a"}, false},
{map[string]interface{}{"one": "a", "two": 1, "three": "b", "four": 1}, false},
} }
validate := New() validate := New()

Loading…
Cancel
Save