package validator import ( "bytes" "context" "crypto/sha256" "fmt" "net" "net/url" "os" "reflect" "regexp" "strconv" "strings" "sync" "time" "unicode/utf8" urn "github.com/leodido/go-urn" ) // Func accepts a FieldLevel interface for all validation needs. The return // value should be true when validation succeeds. type Func func(fl FieldLevel) bool // FuncCtx accepts a context.Context and FieldLevel interface for all // validation needs. The return value should be true when validation succeeds. type FuncCtx func(ctx context.Context, fl FieldLevel) bool // wrapFunc wraps noramal Func makes it compatible with FuncCtx func wrapFunc(fn Func) FuncCtx { if fn == nil { return nil // be sure not to wrap a bad function. } return func(ctx context.Context, fl FieldLevel) bool { return fn(fl) } } var ( restrictedTags = map[string]struct{}{ diveTag: {}, keysTag: {}, endKeysTag: {}, structOnlyTag: {}, omitempty: {}, skipValidationTag: {}, utf8HexComma: {}, utf8Pipe: {}, noStructLevelTag: {}, requiredTag: {}, isdefault: {}, } // BakedInAliasValidators is a default mapping of a single validation tag that // defines a common or complex set of validation(s) to simplify // adding validation to structs. bakedInAliases = map[string]string{ "iscolor": "hexcolor|rgb|rgba|hsl|hsla", } // BakedInValidators is the default map of ValidationFunc // you can add, remove or even replace items to suite your needs, // or even disregard and use your own map if so desired. bakedInValidators = map[string]Func{ "required": hasValue, "required_with": requiredWith, "required_with_all": requiredWithAll, "required_without": requiredWithout, "required_without_all": requiredWithoutAll, "isdefault": isDefault, "len": hasLengthOf, "min": hasMinOf, "max": hasMaxOf, "eq": isEq, "ne": isNe, "lt": isLt, "lte": isLte, "gt": isGt, "gte": isGte, "eqfield": isEqField, "eqcsfield": isEqCrossStructField, "necsfield": isNeCrossStructField, "gtcsfield": isGtCrossStructField, "gtecsfield": isGteCrossStructField, "ltcsfield": isLtCrossStructField, "ltecsfield": isLteCrossStructField, "nefield": isNeField, "gtefield": isGteField, "gtfield": isGtField, "ltefield": isLteField, "ltfield": isLtField, "fieldcontains": fieldContains, "fieldexcludes": fieldExcludes, "alpha": isAlpha, "alphanum": isAlphanum, "alphaunicode": isAlphaUnicode, "alphanumunicode": isAlphanumUnicode, "numeric": isNumeric, "number": isNumber, "hexadecimal": isHexadecimal, "hexcolor": isHEXColor, "rgb": isRGB, "rgba": isRGBA, "hsl": isHSL, "hsla": isHSLA, "email": isEmail, "url": isURL, "uri": isURI, "urn_rfc2141": isUrnRFC2141, // RFC 2141 "file": isFile, "base64": isBase64, "base64url": isBase64URL, "contains": contains, "containsany": containsAny, "containsrune": containsRune, "excludes": excludes, "excludesall": excludesAll, "excludesrune": excludesRune, "startswith": startsWith, "endswith": endsWith, "isbn": isISBN, "isbn10": isISBN10, "isbn13": isISBN13, "eth_addr": isEthereumAddress, "btc_addr": isBitcoinAddress, "btc_addr_bech32": isBitcoinBech32Address, "uuid": isUUID, "uuid3": isUUID3, "uuid4": isUUID4, "uuid5": isUUID5, "uuid_rfc4122": isUUIDRFC4122, "uuid3_rfc4122": isUUID3RFC4122, "uuid4_rfc4122": isUUID4RFC4122, "uuid5_rfc4122": isUUID5RFC4122, "ascii": isASCII, "printascii": isPrintableASCII, "multibyte": hasMultiByteCharacter, "datauri": isDataURI, "latitude": isLatitude, "longitude": isLongitude, "ssn": isSSN, "ipv4": isIPv4, "ipv6": isIPv6, "ip": isIP, "cidrv4": isCIDRv4, "cidrv6": isCIDRv6, "cidr": isCIDR, "tcp4_addr": isTCP4AddrResolvable, "tcp6_addr": isTCP6AddrResolvable, "tcp_addr": isTCPAddrResolvable, "udp4_addr": isUDP4AddrResolvable, "udp6_addr": isUDP6AddrResolvable, "udp_addr": isUDPAddrResolvable, "ip4_addr": isIP4AddrResolvable, "ip6_addr": isIP6AddrResolvable, "ip_addr": isIPAddrResolvable, "unix_addr": isUnixAddrResolvable, "mac": isMAC, "hostname": isHostnameRFC952, // RFC 952 "hostname_rfc1123": isHostnameRFC1123, // RFC 1123 "fqdn": isFQDN, "unique": isUnique, "oneof": isOneOf, "html": isHTML, "html_encoded": isHTMLEncoded, "url_encoded": isURLEncoded, "dir": isDir, } ) var oneofValsCache = map[string][]string{} var oneofValsCacheRWLock = sync.RWMutex{} func parseOneOfParam2(s string) []string { oneofValsCacheRWLock.RLock() vals, ok := oneofValsCache[s] oneofValsCacheRWLock.RUnlock() if !ok { oneofValsCacheRWLock.Lock() re := regexp.MustCompile(`'[^']*'|\S+`) vals = re.FindAllString(s, -1) oneofValsCache[s] = vals oneofValsCacheRWLock.Unlock() } return vals } func isURLEncoded(fl FieldLevel) bool { return uRLEncodedRegex.MatchString(fl.Field().String()) } func isHTMLEncoded(fl FieldLevel) bool { return hTMLEncodedRegex.MatchString(fl.Field().String()) } func isHTML(fl FieldLevel) bool { return hTMLRegex.MatchString(fl.Field().String()) } func isOneOf(fl FieldLevel) bool { vals := parseOneOfParam2(fl.Param()) field := fl.Field() var v string switch field.Kind() { case reflect.String: v = field.String() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: v = strconv.FormatInt(field.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: v = strconv.FormatUint(field.Uint(), 10) default: panic(fmt.Sprintf("Bad field type %T", field.Interface())) } for i := 0; i < len(vals); i++ { val := strings.Replace(vals[i], "'", "", -1) if val == v { return true } } return false } // isUnique is the validation function for validating if each array|slice|map value is unique func isUnique(fl FieldLevel) bool { field := fl.Field() v := reflect.ValueOf(struct{}{}) switch field.Kind() { case reflect.Slice, reflect.Array: m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type())) for i := 0; i < field.Len(); i++ { m.SetMapIndex(field.Index(i), v) } 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: panic(fmt.Sprintf("Bad field type %T", field.Interface())) } } // IsMAC is the validation function for validating if the field's value is a valid MAC address. func isMAC(fl FieldLevel) bool { _, err := net.ParseMAC(fl.Field().String()) return err == nil } // IsCIDRv4 is the validation function for validating if the field's value is a valid v4 CIDR address. func isCIDRv4(fl FieldLevel) bool { ip, _, err := net.ParseCIDR(fl.Field().String()) return err == nil && ip.To4() != nil } // IsCIDRv6 is the validation function for validating if the field's value is a valid v6 CIDR address. func isCIDRv6(fl FieldLevel) bool { ip, _, err := net.ParseCIDR(fl.Field().String()) return err == nil && ip.To4() == nil } // IsCIDR is the validation function for validating if the field's value is a valid v4 or v6 CIDR address. func isCIDR(fl FieldLevel) bool { _, _, err := net.ParseCIDR(fl.Field().String()) return err == nil } // IsIPv4 is the validation function for validating if a value is a valid v4 IP address. func isIPv4(fl FieldLevel) bool { ip := net.ParseIP(fl.Field().String()) return ip != nil && ip.To4() != nil } // IsIPv6 is the validation function for validating if the field's value is a valid v6 IP address. func isIPv6(fl FieldLevel) bool { ip := net.ParseIP(fl.Field().String()) return ip != nil && ip.To4() == nil } // IsIP is the validation function for validating if the field's value is a valid v4 or v6 IP address. func isIP(fl FieldLevel) bool { ip := net.ParseIP(fl.Field().String()) return ip != nil } // IsSSN is the validation function for validating if the field's value is a valid SSN. func isSSN(fl FieldLevel) bool { field := fl.Field() if field.Len() != 11 { return false } return sSNRegex.MatchString(field.String()) } // IsLongitude is the validation function for validating if the field's value is a valid longitude coordinate. func isLongitude(fl FieldLevel) bool { field := fl.Field() var v string switch field.Kind() { case reflect.String: v = field.String() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: v = strconv.FormatInt(field.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: v = strconv.FormatUint(field.Uint(), 10) case reflect.Float32: v = strconv.FormatFloat(field.Float(), 'f', -1, 32) case reflect.Float64: v = strconv.FormatFloat(field.Float(), 'f', -1, 64) default: panic(fmt.Sprintf("Bad field type %T", field.Interface())) } return longitudeRegex.MatchString(v) } // IsLatitude is the validation function for validating if the field's value is a valid latitude coordinate. func isLatitude(fl FieldLevel) bool { field := fl.Field() var v string switch field.Kind() { case reflect.String: v = field.String() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: v = strconv.FormatInt(field.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: v = strconv.FormatUint(field.Uint(), 10) case reflect.Float32: v = strconv.FormatFloat(field.Float(), 'f', -1, 32) case reflect.Float64: v = strconv.FormatFloat(field.Float(), 'f', -1, 64) default: panic(fmt.Sprintf("Bad field type %T", field.Interface())) } return latitudeRegex.MatchString(v) } // IsDataURI is the validation function for validating if the field's value is a valid data URI. func isDataURI(fl FieldLevel) bool { uri := strings.SplitN(fl.Field().String(), ",", 2) if len(uri) != 2 { return false } if !dataURIRegex.MatchString(uri[0]) { return false } return base64Regex.MatchString(uri[1]) } // HasMultiByteCharacter is the validation function for validating if the field's value has a multi byte character. func hasMultiByteCharacter(fl FieldLevel) bool { field := fl.Field() if field.Len() == 0 { return true } return multibyteRegex.MatchString(field.String()) } // IsPrintableASCII is the validation function for validating if the field's value is a valid printable ASCII character. func isPrintableASCII(fl FieldLevel) bool { return printableASCIIRegex.MatchString(fl.Field().String()) } // IsASCII is the validation function for validating if the field's value is a valid ASCII character. func isASCII(fl FieldLevel) bool { return aSCIIRegex.MatchString(fl.Field().String()) } // IsUUID5 is the validation function for validating if the field's value is a valid v5 UUID. func isUUID5(fl FieldLevel) bool { return uUID5Regex.MatchString(fl.Field().String()) } // IsUUID4 is the validation function for validating if the field's value is a valid v4 UUID. func isUUID4(fl FieldLevel) bool { return uUID4Regex.MatchString(fl.Field().String()) } // IsUUID3 is the validation function for validating if the field's value is a valid v3 UUID. func isUUID3(fl FieldLevel) bool { return uUID3Regex.MatchString(fl.Field().String()) } // IsUUID is the validation function for validating if the field's value is a valid UUID of any version. func isUUID(fl FieldLevel) bool { return uUIDRegex.MatchString(fl.Field().String()) } // IsUUID5RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v5 UUID. func isUUID5RFC4122(fl FieldLevel) bool { return uUID5RFC4122Regex.MatchString(fl.Field().String()) } // IsUUID4RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v4 UUID. func isUUID4RFC4122(fl FieldLevel) bool { return uUID4RFC4122Regex.MatchString(fl.Field().String()) } // IsUUID3RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v3 UUID. func isUUID3RFC4122(fl FieldLevel) bool { return uUID3RFC4122Regex.MatchString(fl.Field().String()) } // IsUUIDRFC4122 is the validation function for validating if the field's value is a valid RFC4122 UUID of any version. func isUUIDRFC4122(fl FieldLevel) bool { return uUIDRFC4122Regex.MatchString(fl.Field().String()) } // IsISBN is the validation function for validating if the field's value is a valid v10 or v13 ISBN. func isISBN(fl FieldLevel) bool { return isISBN10(fl) || isISBN13(fl) } // IsISBN13 is the validation function for validating if the field's value is a valid v13 ISBN. func isISBN13(fl FieldLevel) bool { s := strings.Replace(strings.Replace(fl.Field().String(), "-", "", 4), " ", "", 4) if !iSBN13Regex.MatchString(s) { return false } var checksum int32 var i int32 factor := []int32{1, 3} for i = 0; i < 12; i++ { checksum += factor[i%2] * int32(s[i]-'0') } return (int32(s[12]-'0'))-((10-(checksum%10))%10) == 0 } // IsISBN10 is the validation function for validating if the field's value is a valid v10 ISBN. func isISBN10(fl FieldLevel) bool { s := strings.Replace(strings.Replace(fl.Field().String(), "-", "", 3), " ", "", 3) if !iSBN10Regex.MatchString(s) { return false } var checksum int32 var i int32 for i = 0; i < 9; i++ { checksum += (i + 1) * int32(s[i]-'0') } if s[9] == 'X' { checksum += 10 * 10 } else { checksum += 10 * int32(s[9]-'0') } return checksum%11 == 0 } // IsEthereumAddress is the validation function for validating if the field's value is a valid ethereum address based currently only on the format func isEthereumAddress(fl FieldLevel) bool { address := fl.Field().String() if !ethAddressRegex.MatchString(address) { return false } if ethaddressRegexUpper.MatchString(address) || ethAddressRegexLower.MatchString(address) { return true } // checksum validation is blocked by https://github.com/golang/crypto/pull/28 return true } // IsBitcoinAddress is the validation function for validating if the field's value is a valid btc address func isBitcoinAddress(fl FieldLevel) bool { address := fl.Field().String() if !btcAddressRegex.MatchString(address) { return false } alphabet := []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") decode := [25]byte{} for _, n := range []byte(address) { d := bytes.IndexByte(alphabet, n) for i := 24; i >= 0; i-- { d += 58 * int(decode[i]) decode[i] = byte(d % 256) d /= 256 } } h := sha256.New() _, _ = h.Write(decode[:21]) d := h.Sum([]byte{}) h = sha256.New() _, _ = h.Write(d) validchecksum := [4]byte{} computedchecksum := [4]byte{} copy(computedchecksum[:], h.Sum(d[:0])) copy(validchecksum[:], decode[21:]) return validchecksum == computedchecksum } // IsBitcoinBech32Address is the validation function for validating if the field's value is a valid bech32 btc address func isBitcoinBech32Address(fl FieldLevel) bool { address := fl.Field().String() if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address) { return false } am := len(address) % 8 if am == 0 || am == 3 || am == 5 { return false } address = strings.ToLower(address) alphabet := "qpzry9x8gf2tvdw0s3jn54khce6mua7l" hr := []int{3, 3, 0, 2, 3} // the human readable part will always be bc addr := address[3:] dp := make([]int, 0, len(addr)) for _, c := range addr { dp = append(dp, strings.IndexRune(alphabet, c)) } ver := dp[0] if ver < 0 || ver > 16 { return false } if ver == 0 { if len(address) != 42 && len(address) != 62 { return false } } values := append(hr, dp...) GEN := []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} p := 1 for _, v := range values { b := p >> 25 p = (p&0x1ffffff)<<5 ^ v for i := 0; i < 5; i++ { if (b>>uint(i))&1 == 1 { p ^= GEN[i] } } } if p != 1 { return false } b := uint(0) acc := 0 mv := (1 << 5) - 1 var sw []int for _, v := range dp[1 : len(dp)-6] { acc = (acc << 5) | v b += 5 for b >= 8 { b -= 8 sw = append(sw, (acc>>b)&mv) } } if len(sw) < 2 || len(sw) > 40 { return false } return true } // ExcludesRune is the validation function for validating that the field's value does not contain the rune specified within the param. func excludesRune(fl FieldLevel) bool { return !containsRune(fl) } // ExcludesAll is the validation function for validating that the field's value does not contain any of the characters specified within the param. func excludesAll(fl FieldLevel) bool { return !containsAny(fl) } // Excludes is the validation function for validating that the field's value does not contain the text specified within the param. func excludes(fl FieldLevel) bool { return !contains(fl) } // ContainsRune is the validation function for validating that the field's value contains the rune specified within the param. func containsRune(fl FieldLevel) bool { r, _ := utf8.DecodeRuneInString(fl.Param()) return strings.ContainsRune(fl.Field().String(), r) } // ContainsAny is the validation function for validating that the field's value contains any of the characters specified within the param. func containsAny(fl FieldLevel) bool { return strings.ContainsAny(fl.Field().String(), fl.Param()) } // Contains is the validation function for validating that the field's value contains the text specified within the param. func contains(fl FieldLevel) bool { return strings.Contains(fl.Field().String(), fl.Param()) } // StartsWith is the validation function for validating that the field's value starts with the text specified within the param. func startsWith(fl FieldLevel) bool { return strings.HasPrefix(fl.Field().String(), fl.Param()) } // EndsWith is the validation function for validating that the field's value ends with the text specified within the param. func endsWith(fl FieldLevel) bool { return strings.HasSuffix(fl.Field().String(), fl.Param()) } // FieldContains is the validation function for validating if the current field's value contains the field specified by the param's value. func fieldContains(fl FieldLevel) bool { field := fl.Field() currentField, _, ok := fl.GetStructFieldOK() if !ok { return false } return strings.Contains(field.String(), currentField.String()) } // FieldExcludes is the validation function for validating if the current field's value excludes the field specified by the param's value. func fieldExcludes(fl FieldLevel) bool { field := fl.Field() currentField, _, ok := fl.GetStructFieldOK() if !ok { return true } return !strings.Contains(field.String(), currentField.String()) } // IsNeField is the validation function for validating if the current field's value is not equal to the field specified by the param's value. func isNeField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() currentField, currentKind, ok := fl.GetStructFieldOK() if !ok || currentKind != kind { return true } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int() != currentField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return field.Uint() != currentField.Uint() case reflect.Float32, reflect.Float64: return field.Float() != currentField.Float() case reflect.Slice, reflect.Map, reflect.Array: return int64(field.Len()) != int64(currentField.Len()) case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != currentField.Type() { return true } if fieldType == timeType { t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) return !fieldTime.Equal(t) } } // default reflect.String: return field.String() != currentField.String() } // IsNe is the validation function for validating that the field's value does not equal the provided param value. func isNe(fl FieldLevel) bool { return !isEq(fl) } // IsLteCrossStructField is the validation function for validating if the current field's value is less than or equal to the field, within a separate struct, specified by the param's value. func isLteCrossStructField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() topField, topKind, ok := fl.GetStructFieldOK() if !ok || topKind != kind { return false } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int() <= topField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return field.Uint() <= topField.Uint() case reflect.Float32, reflect.Float64: return field.Float() <= topField.Float() case reflect.Slice, reflect.Map, reflect.Array: return int64(field.Len()) <= int64(topField.Len()) case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != topField.Type() { return false } if fieldType == timeType { fieldTime := field.Interface().(time.Time) topTime := topField.Interface().(time.Time) return fieldTime.Before(topTime) || fieldTime.Equal(topTime) } } // default reflect.String: return field.String() <= topField.String() } // IsLtCrossStructField is the validation function for validating if the current field's value is less than the field, within a separate struct, specified by the param's value. // NOTE: This is exposed for use within your own custom functions and not intended to be called directly. func isLtCrossStructField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() topField, topKind, ok := fl.GetStructFieldOK() if !ok || topKind != kind { return false } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int() < topField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return field.Uint() < topField.Uint() case reflect.Float32, reflect.Float64: return field.Float() < topField.Float() case reflect.Slice, reflect.Map, reflect.Array: return int64(field.Len()) < int64(topField.Len()) case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != topField.Type() { return false } if fieldType == timeType { fieldTime := field.Interface().(time.Time) topTime := topField.Interface().(time.Time) return fieldTime.Before(topTime) } } // default reflect.String: return field.String() < topField.String() } // IsGteCrossStructField is the validation function for validating if the current field's value is greater than or equal to the field, within a separate struct, specified by the param's value. func isGteCrossStructField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() topField, topKind, ok := fl.GetStructFieldOK() if !ok || topKind != kind { return false } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int() >= topField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return field.Uint() >= topField.Uint() case reflect.Float32, reflect.Float64: return field.Float() >= topField.Float() case reflect.Slice, reflect.Map, reflect.Array: return int64(field.Len()) >= int64(topField.Len()) case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != topField.Type() { return false } if fieldType == timeType { fieldTime := field.Interface().(time.Time) topTime := topField.Interface().(time.Time) return fieldTime.After(topTime) || fieldTime.Equal(topTime) } } // default reflect.String: return field.String() >= topField.String() } // IsGtCrossStructField is the validation function for validating if the current field's value is greater than the field, within a separate struct, specified by the param's value. func isGtCrossStructField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() topField, topKind, ok := fl.GetStructFieldOK() if !ok || topKind != kind { return false } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int() > topField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return field.Uint() > topField.Uint() case reflect.Float32, reflect.Float64: return field.Float() > topField.Float() case reflect.Slice, reflect.Map, reflect.Array: return int64(field.Len()) > int64(topField.Len()) case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != topField.Type() { return false } if fieldType == timeType { fieldTime := field.Interface().(time.Time) topTime := topField.Interface().(time.Time) return fieldTime.After(topTime) } } // default reflect.String: return field.String() > topField.String() } // IsNeCrossStructField is the validation function for validating that the current field's value is not equal to the field, within a separate struct, specified by the param's value. func isNeCrossStructField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() topField, currentKind, ok := fl.GetStructFieldOK() if !ok || currentKind != kind { return true } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return topField.Int() != field.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return topField.Uint() != field.Uint() case reflect.Float32, reflect.Float64: return topField.Float() != field.Float() case reflect.Slice, reflect.Map, reflect.Array: return int64(topField.Len()) != int64(field.Len()) case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != topField.Type() { return true } if fieldType == timeType { t := field.Interface().(time.Time) fieldTime := topField.Interface().(time.Time) return !fieldTime.Equal(t) } } // default reflect.String: return topField.String() != field.String() } // IsEqCrossStructField is the validation function for validating that the current field's value is equal to the field, within a separate struct, specified by the param's value. func isEqCrossStructField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() topField, topKind, ok := fl.GetStructFieldOK() if !ok || topKind != kind { return false } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return topField.Int() == field.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return topField.Uint() == field.Uint() case reflect.Float32, reflect.Float64: return topField.Float() == field.Float() case reflect.Slice, reflect.Map, reflect.Array: return int64(topField.Len()) == int64(field.Len()) case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != topField.Type() { return false } if fieldType == timeType { t := field.Interface().(time.Time) fieldTime := topField.Interface().(time.Time) return fieldTime.Equal(t) } } // default reflect.String: return topField.String() == field.String() } // IsEqField is the validation function for validating if the current field's value is equal to the field specified by the param's value. func isEqField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() currentField, currentKind, ok := fl.GetStructFieldOK() if !ok || currentKind != kind { return false } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int() == currentField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return field.Uint() == currentField.Uint() case reflect.Float32, reflect.Float64: return field.Float() == currentField.Float() case reflect.Slice, reflect.Map, reflect.Array: return int64(field.Len()) == int64(currentField.Len()) case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != currentField.Type() { return false } if fieldType == timeType { t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) return fieldTime.Equal(t) } } // default reflect.String: return field.String() == currentField.String() } // IsEq is the validation function for validating if the current field's value is equal to the param's value. func isEq(fl FieldLevel) bool { field := fl.Field() param := fl.Param() switch field.Kind() { case reflect.String: return field.String() == param case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) return int64(field.Len()) == p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) return field.Int() == p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) return field.Uint() == p case reflect.Float32, reflect.Float64: p := asFloat(param) return field.Float() == p } panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // IsBase64 is the validation function for validating if the current field's value is a valid base 64. func isBase64(fl FieldLevel) bool { return base64Regex.MatchString(fl.Field().String()) } // IsBase64URL is the validation function for validating if the current field's value is a valid base64 URL safe string. func isBase64URL(fl FieldLevel) bool { return base64URLRegex.MatchString(fl.Field().String()) } // IsURI is the validation function for validating if the current field's value is a valid URI. func isURI(fl FieldLevel) bool { field := fl.Field() switch field.Kind() { case reflect.String: s := field.String() // checks needed as of Go 1.6 because of change https://github.com/golang/go/commit/617c93ce740c3c3cc28cdd1a0d712be183d0b328#diff-6c2d018290e298803c0c9419d8739885L195 // emulate browser and strip the '#' suffix prior to validation. see issue-#237 if i := strings.Index(s, "#"); i > -1 { s = s[:i] } if len(s) == 0 { return false } _, err := url.ParseRequestURI(s) return err == nil } panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // IsURL is the validation function for validating if the current field's value is a valid URL. func isURL(fl FieldLevel) bool { field := fl.Field() switch field.Kind() { case reflect.String: var i int s := field.String() // checks needed as of Go 1.6 because of change https://github.com/golang/go/commit/617c93ce740c3c3cc28cdd1a0d712be183d0b328#diff-6c2d018290e298803c0c9419d8739885L195 // emulate browser and strip the '#' suffix prior to validation. see issue-#237 if i = strings.Index(s, "#"); i > -1 { s = s[:i] } if len(s) == 0 { return false } url, err := url.ParseRequestURI(s) if err != nil || url.Scheme == "" { return false } return err == nil } panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // isUrnRFC2141 is the validation function for validating if the current field's value is a valid URN as per RFC 2141. func isUrnRFC2141(fl FieldLevel) bool { field := fl.Field() switch field.Kind() { case reflect.String: str := field.String() _, match := urn.Parse([]byte(str)) return match } panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // IsFile is the validation function for validating if the current field's value is a valid file path. func isFile(fl FieldLevel) bool { field := fl.Field() switch field.Kind() { case reflect.String: fileInfo, err := os.Stat(field.String()) if err != nil { return false } return !fileInfo.IsDir() } panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // IsEmail is the validation function for validating if the current field's value is a valid email address. func isEmail(fl FieldLevel) bool { return emailRegex.MatchString(fl.Field().String()) } // IsHSLA is the validation function for validating if the current field's value is a valid HSLA color. func isHSLA(fl FieldLevel) bool { return hslaRegex.MatchString(fl.Field().String()) } // IsHSL is the validation function for validating if the current field's value is a valid HSL color. func isHSL(fl FieldLevel) bool { return hslRegex.MatchString(fl.Field().String()) } // IsRGBA is the validation function for validating if the current field's value is a valid RGBA color. func isRGBA(fl FieldLevel) bool { return rgbaRegex.MatchString(fl.Field().String()) } // IsRGB is the validation function for validating if the current field's value is a valid RGB color. func isRGB(fl FieldLevel) bool { return rgbRegex.MatchString(fl.Field().String()) } // IsHEXColor is the validation function for validating if the current field's value is a valid HEX color. func isHEXColor(fl FieldLevel) bool { return hexcolorRegex.MatchString(fl.Field().String()) } // IsHexadecimal is the validation function for validating if the current field's value is a valid hexadecimal. func isHexadecimal(fl FieldLevel) bool { return hexadecimalRegex.MatchString(fl.Field().String()) } // IsNumber is the validation function for validating if the current field's value is a valid number. func isNumber(fl FieldLevel) bool { switch fl.Field().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64: return true default: return numberRegex.MatchString(fl.Field().String()) } } // IsNumeric is the validation function for validating if the current field's value is a valid numeric value. func isNumeric(fl FieldLevel) bool { switch fl.Field().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64: return true default: return numericRegex.MatchString(fl.Field().String()) } } // IsAlphanum is the validation function for validating if the current field's value is a valid alphanumeric value. func isAlphanum(fl FieldLevel) bool { return alphaNumericRegex.MatchString(fl.Field().String()) } // IsAlpha is the validation function for validating if the current field's value is a valid alpha value. func isAlpha(fl FieldLevel) bool { return alphaRegex.MatchString(fl.Field().String()) } // IsAlphanumUnicode is the validation function for validating if the current field's value is a valid alphanumeric unicode value. func isAlphanumUnicode(fl FieldLevel) bool { return alphaUnicodeNumericRegex.MatchString(fl.Field().String()) } // IsAlphaUnicode is the validation function for validating if the current field's value is a valid alpha unicode value. func isAlphaUnicode(fl FieldLevel) bool { return alphaUnicodeRegex.MatchString(fl.Field().String()) } // isDefault is the opposite of required aka hasValue func isDefault(fl FieldLevel) bool { return !hasValue(fl) } // HasValue is the validation function for validating if the current field's value is not the default static value. func hasValue(fl FieldLevel) bool { field := fl.Field() switch field.Kind() { case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: return !field.IsNil() default: if fl.(*validate).fldIsPointer && field.Interface() != nil { return true } return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface() } } // requireCheckField is a func for check field kind func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue bool) bool { field := fl.Field() var ok bool kind := field.Kind() if len(param) > 0 { field, kind, ok = fl.GetStructFieldOKAdvanced(fl.Parent(), param) if !ok { return defaultNotFoundValue } } switch kind { case reflect.Invalid: return defaultNotFoundValue case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: return !field.IsNil() default: return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface() } } // RequiredWith is the validation function // The field under validation must be present and not empty only if any of the other specified fields are present. func requiredWith(fl FieldLevel) bool { params := parseOneOfParam2(fl.Param()) for _, param := range params { if requireCheckFieldKind(fl, param, false) { return hasValue(fl) } } return true } // RequiredWithAll is the validation function // The field under validation must be present and not empty only if all of the other specified fields are present. func requiredWithAll(fl FieldLevel) bool { params := parseOneOfParam2(fl.Param()) for _, param := range params { if !requireCheckFieldKind(fl, param, false) { return true } } return hasValue(fl) } // RequiredWithout is the validation function // The field under validation must be present and not empty only when any of the other specified fields are not present. func requiredWithout(fl FieldLevel) bool { params := parseOneOfParam2(fl.Param()) for _, param := range params { if !requireCheckFieldKind(fl, param, true) { return hasValue(fl) } } return true } // RequiredWithoutAll is the validation function // The field under validation must be present and not empty only when all of the other specified fields are not present. func requiredWithoutAll(fl FieldLevel) bool { params := parseOneOfParam2(fl.Param()) for _, param := range params { if requireCheckFieldKind(fl, param, true) { return true } } return hasValue(fl) } // IsGteField is the validation function for validating if the current field's value is greater than or equal to the field specified by the param's value. func isGteField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() currentField, currentKind, ok := fl.GetStructFieldOK() if !ok || currentKind != kind { return false } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int() >= currentField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return field.Uint() >= currentField.Uint() case reflect.Float32, reflect.Float64: return field.Float() >= currentField.Float() case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != currentField.Type() { return false } if fieldType == timeType { t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) return fieldTime.After(t) || fieldTime.Equal(t) } } // default reflect.String return len(field.String()) >= len(currentField.String()) } // IsGtField is the validation function for validating if the current field's value is greater than the field specified by the param's value. func isGtField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() currentField, currentKind, ok := fl.GetStructFieldOK() if !ok || currentKind != kind { return false } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int() > currentField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return field.Uint() > currentField.Uint() case reflect.Float32, reflect.Float64: return field.Float() > currentField.Float() case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != currentField.Type() { return false } if fieldType == timeType { t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) return fieldTime.After(t) } } // default reflect.String return len(field.String()) > len(currentField.String()) } // IsGte is the validation function for validating if the current field's value is greater than or equal to the param's value. func isGte(fl FieldLevel) bool { field := fl.Field() param := fl.Param() switch field.Kind() { case reflect.String: p := asInt(param) return int64(utf8.RuneCountInString(field.String())) >= p case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) return int64(field.Len()) >= p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) return field.Int() >= p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) return field.Uint() >= p case reflect.Float32, reflect.Float64: p := asFloat(param) return field.Float() >= p case reflect.Struct: if field.Type() == timeType { now := time.Now().UTC() t := field.Interface().(time.Time) return t.After(now) || t.Equal(now) } } panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // IsGt is the validation function for validating if the current field's value is greater than the param's value. func isGt(fl FieldLevel) bool { field := fl.Field() param := fl.Param() switch field.Kind() { case reflect.String: p := asInt(param) return int64(utf8.RuneCountInString(field.String())) > p case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) return int64(field.Len()) > p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) return field.Int() > p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) return field.Uint() > p case reflect.Float32, reflect.Float64: p := asFloat(param) return field.Float() > p case reflect.Struct: if field.Type() == timeType { return field.Interface().(time.Time).After(time.Now().UTC()) } } panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // HasLengthOf is the validation function for validating if the current field's value is equal to the param's value. func hasLengthOf(fl FieldLevel) bool { field := fl.Field() param := fl.Param() switch field.Kind() { case reflect.String: p := asInt(param) return int64(utf8.RuneCountInString(field.String())) == p case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) return int64(field.Len()) == p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) return field.Int() == p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) return field.Uint() == p case reflect.Float32, reflect.Float64: p := asFloat(param) return field.Float() == p } panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // HasMinOf is the validation function for validating if the current field's value is greater than or equal to the param's value. func hasMinOf(fl FieldLevel) bool { return isGte(fl) } // IsLteField is the validation function for validating if the current field's value is less than or equal to the field specified by the param's value. func isLteField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() currentField, currentKind, ok := fl.GetStructFieldOK() if !ok || currentKind != kind { return false } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int() <= currentField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return field.Uint() <= currentField.Uint() case reflect.Float32, reflect.Float64: return field.Float() <= currentField.Float() case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != currentField.Type() { return false } if fieldType == timeType { t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) return fieldTime.Before(t) || fieldTime.Equal(t) } } // default reflect.String return len(field.String()) <= len(currentField.String()) } // IsLtField is the validation function for validating if the current field's value is less than the field specified by the param's value. func isLtField(fl FieldLevel) bool { field := fl.Field() kind := field.Kind() currentField, currentKind, ok := fl.GetStructFieldOK() if !ok || currentKind != kind { return false } switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int() < currentField.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return field.Uint() < currentField.Uint() case reflect.Float32, reflect.Float64: return field.Float() < currentField.Float() case reflect.Struct: fieldType := field.Type() // Not Same underlying type i.e. struct and time if fieldType != currentField.Type() { return false } if fieldType == timeType { t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) return fieldTime.Before(t) } } // default reflect.String return len(field.String()) < len(currentField.String()) } // IsLte is the validation function for validating if the current field's value is less than or equal to the param's value. func isLte(fl FieldLevel) bool { field := fl.Field() param := fl.Param() switch field.Kind() { case reflect.String: p := asInt(param) return int64(utf8.RuneCountInString(field.String())) <= p case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) return int64(field.Len()) <= p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) return field.Int() <= p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) return field.Uint() <= p case reflect.Float32, reflect.Float64: p := asFloat(param) return field.Float() <= p case reflect.Struct: if field.Type() == timeType { now := time.Now().UTC() t := field.Interface().(time.Time) return t.Before(now) || t.Equal(now) } } panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // IsLt is the validation function for validating if the current field's value is less than the param's value. func isLt(fl FieldLevel) bool { field := fl.Field() param := fl.Param() switch field.Kind() { case reflect.String: p := asInt(param) return int64(utf8.RuneCountInString(field.String())) < p case reflect.Slice, reflect.Map, reflect.Array: p := asInt(param) return int64(field.Len()) < p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: p := asInt(param) return field.Int() < p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: p := asUint(param) return field.Uint() < p case reflect.Float32, reflect.Float64: p := asFloat(param) return field.Float() < p case reflect.Struct: if field.Type() == timeType { return field.Interface().(time.Time).Before(time.Now().UTC()) } } panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // HasMaxOf is the validation function for validating if the current field's value is less than or equal to the param's value. func hasMaxOf(fl FieldLevel) bool { return isLte(fl) } // IsTCP4AddrResolvable is the validation function for validating if the field's value is a resolvable tcp4 address. func isTCP4AddrResolvable(fl FieldLevel) bool { if !isIP4Addr(fl) { return false } _, err := net.ResolveTCPAddr("tcp4", fl.Field().String()) return err == nil } // IsTCP6AddrResolvable is the validation function for validating if the field's value is a resolvable tcp6 address. func isTCP6AddrResolvable(fl FieldLevel) bool { if !isIP6Addr(fl) { return false } _, err := net.ResolveTCPAddr("tcp6", fl.Field().String()) return err == nil } // IsTCPAddrResolvable is the validation function for validating if the field's value is a resolvable tcp address. func isTCPAddrResolvable(fl FieldLevel) bool { if !isIP4Addr(fl) && !isIP6Addr(fl) { return false } _, err := net.ResolveTCPAddr("tcp", fl.Field().String()) return err == nil } // IsUDP4AddrResolvable is the validation function for validating if the field's value is a resolvable udp4 address. func isUDP4AddrResolvable(fl FieldLevel) bool { if !isIP4Addr(fl) { return false } _, err := net.ResolveUDPAddr("udp4", fl.Field().String()) return err == nil } // IsUDP6AddrResolvable is the validation function for validating if the field's value is a resolvable udp6 address. func isUDP6AddrResolvable(fl FieldLevel) bool { if !isIP6Addr(fl) { return false } _, err := net.ResolveUDPAddr("udp6", fl.Field().String()) return err == nil } // IsUDPAddrResolvable is the validation function for validating if the field's value is a resolvable udp address. func isUDPAddrResolvable(fl FieldLevel) bool { if !isIP4Addr(fl) && !isIP6Addr(fl) { return false } _, err := net.ResolveUDPAddr("udp", fl.Field().String()) return err == nil } // IsIP4AddrResolvable is the validation function for validating if the field's value is a resolvable ip4 address. func isIP4AddrResolvable(fl FieldLevel) bool { if !isIPv4(fl) { return false } _, err := net.ResolveIPAddr("ip4", fl.Field().String()) return err == nil } // IsIP6AddrResolvable is the validation function for validating if the field's value is a resolvable ip6 address. func isIP6AddrResolvable(fl FieldLevel) bool { if !isIPv6(fl) { return false } _, err := net.ResolveIPAddr("ip6", fl.Field().String()) return err == nil } // IsIPAddrResolvable is the validation function for validating if the field's value is a resolvable ip address. func isIPAddrResolvable(fl FieldLevel) bool { if !isIP(fl) { return false } _, err := net.ResolveIPAddr("ip", fl.Field().String()) return err == nil } // IsUnixAddrResolvable is the validation function for validating if the field's value is a resolvable unix address. func isUnixAddrResolvable(fl FieldLevel) bool { _, err := net.ResolveUnixAddr("unix", fl.Field().String()) return err == nil } func isIP4Addr(fl FieldLevel) bool { val := fl.Field().String() if idx := strings.LastIndex(val, ":"); idx != -1 { val = val[0:idx] } ip := net.ParseIP(val) return ip != nil && ip.To4() != nil } func isIP6Addr(fl FieldLevel) bool { val := fl.Field().String() if idx := strings.LastIndex(val, ":"); idx != -1 { if idx != 0 && val[idx-1:idx] == "]" { val = val[1 : idx-1] } } ip := net.ParseIP(val) return ip != nil && ip.To4() == nil } func isHostnameRFC952(fl FieldLevel) bool { return hostnameRegexRFC952.MatchString(fl.Field().String()) } func isHostnameRFC1123(fl FieldLevel) bool { return hostnameRegexRFC1123.MatchString(fl.Field().String()) } func isFQDN(fl FieldLevel) bool { val := fl.Field().String() if val == "" { return false } if val[len(val)-1] == '.' { val = val[0 : len(val)-1] } return strings.ContainsAny(val, ".") && hostnameRegexRFC952.MatchString(val) } // IsDir is the validation function for validating if the current field's value is a valid directory. func isDir(fl FieldLevel) bool { field := fl.Field() if field.Kind() == reflect.String { fileInfo, err := os.Stat(field.String()) if err != nil { return false } return fileInfo.IsDir() } panic(fmt.Sprintf("Bad field type %T", field.Interface())) }