mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-23 10:42:12 +00:00
2956 lines
85 KiB
Go
2956 lines
85 KiB
Go
package validator
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"golang.org/x/crypto/sha3"
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/gabriel-vasile/mimetype"
|
|
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 normal 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: {},
|
|
omitnil: {},
|
|
skipValidationTag: {},
|
|
utf8HexComma: {},
|
|
utf8Pipe: {},
|
|
noStructLevelTag: {},
|
|
requiredTag: {},
|
|
isdefault: {},
|
|
}
|
|
|
|
// bakedInAliases 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",
|
|
"country_code": "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric",
|
|
}
|
|
|
|
// 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_if": requiredIf,
|
|
"required_unless": requiredUnless,
|
|
"skip_unless": skipUnless,
|
|
"required_with": requiredWith,
|
|
"required_with_all": requiredWithAll,
|
|
"required_without": requiredWithout,
|
|
"required_without_all": requiredWithoutAll,
|
|
"excluded_if": excludedIf,
|
|
"excluded_unless": excludedUnless,
|
|
"excluded_with": excludedWith,
|
|
"excluded_with_all": excludedWithAll,
|
|
"excluded_without": excludedWithout,
|
|
"excluded_without_all": excludedWithoutAll,
|
|
"isdefault": isDefault,
|
|
"len": hasLengthOf,
|
|
"min": hasMinOf,
|
|
"max": hasMaxOf,
|
|
"eq": isEq,
|
|
"eq_ignore_case": isEqIgnoreCase,
|
|
"ne": isNe,
|
|
"ne_ignore_case": isNeIgnoreCase,
|
|
"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,
|
|
"boolean": isBoolean,
|
|
"numeric": isNumeric,
|
|
"number": isNumber,
|
|
"hexadecimal": isHexadecimal,
|
|
"hexcolor": isHEXColor,
|
|
"rgb": isRGB,
|
|
"rgba": isRGBA,
|
|
"hsl": isHSL,
|
|
"hsla": isHSLA,
|
|
"e164": isE164,
|
|
"email": isEmail,
|
|
"url": isURL,
|
|
"http_url": isHttpURL,
|
|
"uri": isURI,
|
|
"urn_rfc2141": isUrnRFC2141, // RFC 2141
|
|
"file": isFile,
|
|
"filepath": isFilePath,
|
|
"base64": isBase64,
|
|
"base64url": isBase64URL,
|
|
"base64rawurl": isBase64RawURL,
|
|
"contains": contains,
|
|
"containsany": containsAny,
|
|
"containsrune": containsRune,
|
|
"excludes": excludes,
|
|
"excludesall": excludesAll,
|
|
"excludesrune": excludesRune,
|
|
"startswith": startsWith,
|
|
"endswith": endsWith,
|
|
"startsnotwith": startsNotWith,
|
|
"endsnotwith": endsNotWith,
|
|
"image": isImage,
|
|
"isbn": isISBN,
|
|
"isbn10": isISBN10,
|
|
"isbn13": isISBN13,
|
|
"issn": isISSN,
|
|
"eth_addr": isEthereumAddress,
|
|
"eth_addr_checksum": isEthereumAddressChecksum,
|
|
"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,
|
|
"ulid": isULID,
|
|
"md4": isMD4,
|
|
"md5": isMD5,
|
|
"sha256": isSHA256,
|
|
"sha384": isSHA384,
|
|
"sha512": isSHA512,
|
|
"ripemd128": isRIPEMD128,
|
|
"ripemd160": isRIPEMD160,
|
|
"tiger128": isTIGER128,
|
|
"tiger160": isTIGER160,
|
|
"tiger192": isTIGER192,
|
|
"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,
|
|
"dirpath": isDirPath,
|
|
"json": isJSON,
|
|
"jwt": isJWT,
|
|
"hostname_port": isHostnamePort,
|
|
"lowercase": isLowercase,
|
|
"uppercase": isUppercase,
|
|
"datetime": isDatetime,
|
|
"timezone": isTimeZone,
|
|
"iso3166_1_alpha2": isIso3166Alpha2,
|
|
"iso3166_1_alpha3": isIso3166Alpha3,
|
|
"iso3166_1_alpha_numeric": isIso3166AlphaNumeric,
|
|
"iso3166_2": isIso31662,
|
|
"iso4217": isIso4217,
|
|
"iso4217_numeric": isIso4217Numeric,
|
|
"bcp47_language_tag": isBCP47LanguageTag,
|
|
"postcode_iso3166_alpha2": isPostcodeByIso3166Alpha2,
|
|
"postcode_iso3166_alpha2_field": isPostcodeByIso3166Alpha2Field,
|
|
"bic": isIsoBicFormat,
|
|
"semver": isSemverFormat,
|
|
"dns_rfc1035_label": isDnsRFC1035LabelFormat,
|
|
"credit_card": isCreditCard,
|
|
"cve": isCveFormat,
|
|
"luhn_checksum": hasLuhnChecksum,
|
|
"mongodb": isMongoDB,
|
|
"cron": isCron,
|
|
"spicedb": isSpiceDB,
|
|
}
|
|
)
|
|
|
|
var (
|
|
oneofValsCache = map[string][]string{}
|
|
oneofValsCacheRWLock = sync.RWMutex{}
|
|
)
|
|
|
|
func parseOneOfParam2(s string) []string {
|
|
oneofValsCacheRWLock.RLock()
|
|
vals, ok := oneofValsCache[s]
|
|
oneofValsCacheRWLock.RUnlock()
|
|
if !ok {
|
|
oneofValsCacheRWLock.Lock()
|
|
vals = splitParamsRegex.FindAllString(s, -1)
|
|
for i := 0; i < len(vals); i++ {
|
|
vals[i] = strings.Replace(vals[i], "'", "", -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++ {
|
|
if vals[i] == 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()
|
|
param := fl.Param()
|
|
v := reflect.ValueOf(struct{}{})
|
|
|
|
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(elem, v.Type()))
|
|
|
|
for i := 0; i < field.Len(); i++ {
|
|
m.SetMapIndex(reflect.Indirect(field.Index(i)), v)
|
|
}
|
|
return field.Len() == m.Len()
|
|
}
|
|
|
|
sf, ok := elem.FieldByName(param)
|
|
if !ok {
|
|
panic(fmt.Sprintf("Bad field name %s", param))
|
|
}
|
|
|
|
sfTyp := sf.Type
|
|
if sfTyp.Kind() == reflect.Ptr {
|
|
sfTyp = sfTyp.Elem()
|
|
}
|
|
|
|
m := reflect.MakeMap(reflect.MapOf(sfTyp, v.Type()))
|
|
var fieldlen int
|
|
for i := 0; i < field.Len(); i++ {
|
|
key := reflect.Indirect(reflect.Indirect(field.Index(i)).FieldByName(param))
|
|
if key.IsValid() {
|
|
fieldlen++
|
|
m.SetMapIndex(key, v)
|
|
}
|
|
}
|
|
return fieldlen == m.Len()
|
|
case reflect.Map:
|
|
var m reflect.Value
|
|
if field.Type().Elem().Kind() == reflect.Ptr {
|
|
m = reflect.MakeMap(reflect.MapOf(field.Type().Elem().Elem(), v.Type()))
|
|
} else {
|
|
m = reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
|
|
}
|
|
|
|
for _, k := range field.MapKeys() {
|
|
m.SetMapIndex(reflect.Indirect(field.MapIndex(k)), v)
|
|
}
|
|
|
|
return field.Len() == m.Len()
|
|
default:
|
|
if parent := fl.Parent(); parent.Kind() == reflect.Struct {
|
|
uniqueField := parent.FieldByName(param)
|
|
if uniqueField == reflect.ValueOf(nil) {
|
|
panic(fmt.Sprintf("Bad field name provided %s", param))
|
|
}
|
|
|
|
if uniqueField.Kind() != field.Kind() {
|
|
panic(fmt.Sprintf("Bad field type %T:%T", field.Interface(), uniqueField.Interface()))
|
|
}
|
|
|
|
return field.Interface() != uniqueField.Interface()
|
|
}
|
|
|
|
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, net, err := net.ParseCIDR(fl.Field().String())
|
|
|
|
return err == nil && ip.To4() != nil && net.IP.Equal(ip)
|
|
}
|
|
|
|
// 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 fieldMatchesRegexByStringerValOrString(uUID5Regex, fl)
|
|
}
|
|
|
|
// isUUID4 is the validation function for validating if the field's value is a valid v4 UUID.
|
|
func isUUID4(fl FieldLevel) bool {
|
|
return fieldMatchesRegexByStringerValOrString(uUID4Regex, fl)
|
|
}
|
|
|
|
// isUUID3 is the validation function for validating if the field's value is a valid v3 UUID.
|
|
func isUUID3(fl FieldLevel) bool {
|
|
return fieldMatchesRegexByStringerValOrString(uUID3Regex, fl)
|
|
}
|
|
|
|
// 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 fieldMatchesRegexByStringerValOrString(uUIDRegex, fl)
|
|
}
|
|
|
|
// isUUID5RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v5 UUID.
|
|
func isUUID5RFC4122(fl FieldLevel) bool {
|
|
return fieldMatchesRegexByStringerValOrString(uUID5RFC4122Regex, fl)
|
|
}
|
|
|
|
// isUUID4RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v4 UUID.
|
|
func isUUID4RFC4122(fl FieldLevel) bool {
|
|
return fieldMatchesRegexByStringerValOrString(uUID4RFC4122Regex, fl)
|
|
}
|
|
|
|
// isUUID3RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v3 UUID.
|
|
func isUUID3RFC4122(fl FieldLevel) bool {
|
|
return fieldMatchesRegexByStringerValOrString(uUID3RFC4122Regex, fl)
|
|
}
|
|
|
|
// 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 fieldMatchesRegexByStringerValOrString(uUIDRFC4122Regex, fl)
|
|
}
|
|
|
|
// isULID is the validation function for validating if the field's value is a valid ULID.
|
|
func isULID(fl FieldLevel) bool {
|
|
return fieldMatchesRegexByStringerValOrString(uLIDRegex, fl)
|
|
}
|
|
|
|
// isMD4 is the validation function for validating if the field's value is a valid MD4.
|
|
func isMD4(fl FieldLevel) bool {
|
|
return md4Regex.MatchString(fl.Field().String())
|
|
}
|
|
|
|
// isMD5 is the validation function for validating if the field's value is a valid MD5.
|
|
func isMD5(fl FieldLevel) bool {
|
|
return md5Regex.MatchString(fl.Field().String())
|
|
}
|
|
|
|
// isSHA256 is the validation function for validating if the field's value is a valid SHA256.
|
|
func isSHA256(fl FieldLevel) bool {
|
|
return sha256Regex.MatchString(fl.Field().String())
|
|
}
|
|
|
|
// isSHA384 is the validation function for validating if the field's value is a valid SHA384.
|
|
func isSHA384(fl FieldLevel) bool {
|
|
return sha384Regex.MatchString(fl.Field().String())
|
|
}
|
|
|
|
// isSHA512 is the validation function for validating if the field's value is a valid SHA512.
|
|
func isSHA512(fl FieldLevel) bool {
|
|
return sha512Regex.MatchString(fl.Field().String())
|
|
}
|
|
|
|
// isRIPEMD128 is the validation function for validating if the field's value is a valid PIPEMD128.
|
|
func isRIPEMD128(fl FieldLevel) bool {
|
|
return ripemd128Regex.MatchString(fl.Field().String())
|
|
}
|
|
|
|
// isRIPEMD160 is the validation function for validating if the field's value is a valid PIPEMD160.
|
|
func isRIPEMD160(fl FieldLevel) bool {
|
|
return ripemd160Regex.MatchString(fl.Field().String())
|
|
}
|
|
|
|
// isTIGER128 is the validation function for validating if the field's value is a valid TIGER128.
|
|
func isTIGER128(fl FieldLevel) bool {
|
|
return tiger128Regex.MatchString(fl.Field().String())
|
|
}
|
|
|
|
// isTIGER160 is the validation function for validating if the field's value is a valid TIGER160.
|
|
func isTIGER160(fl FieldLevel) bool {
|
|
return tiger160Regex.MatchString(fl.Field().String())
|
|
}
|
|
|
|
// isTIGER192 is the validation function for validating if the field's value is a valid isTIGER192.
|
|
func isTIGER192(fl FieldLevel) bool {
|
|
return tiger192Regex.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
|
|
}
|
|
|
|
// isISSN is the validation function for validating if the field's value is a valid ISSN.
|
|
func isISSN(fl FieldLevel) bool {
|
|
s := fl.Field().String()
|
|
|
|
if !iSSNRegex.MatchString(s) {
|
|
return false
|
|
}
|
|
s = strings.ReplaceAll(s, "-", "")
|
|
|
|
pos := 8
|
|
checksum := 0
|
|
|
|
for i := 0; i < 7; i++ {
|
|
checksum += pos * int(s[i]-'0')
|
|
pos--
|
|
}
|
|
|
|
if s[7] == 'X' {
|
|
checksum += 10
|
|
} else {
|
|
checksum += int(s[7] - '0')
|
|
}
|
|
|
|
return checksum%11 == 0
|
|
}
|
|
|
|
// isEthereumAddress is the validation function for validating if the field's value is a valid Ethereum address.
|
|
func isEthereumAddress(fl FieldLevel) bool {
|
|
address := fl.Field().String()
|
|
|
|
return ethAddressRegex.MatchString(address)
|
|
}
|
|
|
|
// isEthereumAddressChecksum is the validation function for validating if the field's value is a valid checksumed Ethereum address.
|
|
func isEthereumAddressChecksum(fl FieldLevel) bool {
|
|
address := fl.Field().String()
|
|
|
|
if !ethAddressRegex.MatchString(address) {
|
|
return false
|
|
}
|
|
// Checksum validation. Reference: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
|
|
address = address[2:] // Skip "0x" prefix.
|
|
h := sha3.NewLegacyKeccak256()
|
|
// hash.Hash's io.Writer implementation says it never returns an error. https://golang.org/pkg/hash/#Hash
|
|
_, _ = h.Write([]byte(strings.ToLower(address)))
|
|
hash := hex.EncodeToString(h.Sum(nil))
|
|
|
|
for i := 0; i < len(address); i++ {
|
|
if address[i] <= '9' { // Skip 0-9 digits: they don't have upper/lower-case.
|
|
continue
|
|
}
|
|
if hash[i] > '7' && address[i] >= 'a' || hash[i] <= '7' && address[i] <= 'F' {
|
|
return false
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|
|
|
|
// startsNotWith is the validation function for validating that the field's value does not start with the text specified within the param.
|
|
func startsNotWith(fl FieldLevel) bool {
|
|
return !startsWith(fl)
|
|
}
|
|
|
|
// endsNotWith is the validation function for validating that the field's value does not end with the text specified within the param.
|
|
func endsNotWith(fl FieldLevel) bool {
|
|
return !endsWith(fl)
|
|
}
|
|
|
|
// 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.Bool:
|
|
return field.Bool() != currentField.Bool()
|
|
|
|
case reflect.Struct:
|
|
|
|
fieldType := field.Type()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
|
|
|
|
t := currentField.Interface().(time.Time)
|
|
fieldTime := field.Interface().(time.Time)
|
|
|
|
return !fieldTime.Equal(t)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != currentField.Type() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// isNeIgnoreCase is the validation function for validating that the field's string value does not equal the
|
|
// provided param value. The comparison is case-insensitive
|
|
func isNeIgnoreCase(fl FieldLevel) bool {
|
|
return !isEqIgnoreCase(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()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
|
|
|
|
fieldTime := field.Convert(timeType).Interface().(time.Time)
|
|
topTime := topField.Convert(timeType).Interface().(time.Time)
|
|
|
|
return fieldTime.Before(topTime) || fieldTime.Equal(topTime)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != topField.Type() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
|
|
|
|
fieldTime := field.Convert(timeType).Interface().(time.Time)
|
|
topTime := topField.Convert(timeType).Interface().(time.Time)
|
|
|
|
return fieldTime.Before(topTime)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != topField.Type() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
|
|
|
|
fieldTime := field.Convert(timeType).Interface().(time.Time)
|
|
topTime := topField.Convert(timeType).Interface().(time.Time)
|
|
|
|
return fieldTime.After(topTime) || fieldTime.Equal(topTime)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != topField.Type() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
|
|
|
|
fieldTime := field.Convert(timeType).Interface().(time.Time)
|
|
topTime := topField.Convert(timeType).Interface().(time.Time)
|
|
|
|
return fieldTime.After(topTime)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != topField.Type() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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.Bool:
|
|
return topField.Bool() != field.Bool()
|
|
|
|
case reflect.Struct:
|
|
|
|
fieldType := field.Type()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
|
|
|
|
t := field.Convert(timeType).Interface().(time.Time)
|
|
fieldTime := topField.Convert(timeType).Interface().(time.Time)
|
|
|
|
return !fieldTime.Equal(t)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != topField.Type() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// 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.Bool:
|
|
return topField.Bool() == field.Bool()
|
|
|
|
case reflect.Struct:
|
|
|
|
fieldType := field.Type()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
|
|
|
|
t := field.Convert(timeType).Interface().(time.Time)
|
|
fieldTime := topField.Convert(timeType).Interface().(time.Time)
|
|
|
|
return fieldTime.Equal(t)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != topField.Type() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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.Bool:
|
|
return field.Bool() == currentField.Bool()
|
|
|
|
case reflect.Struct:
|
|
|
|
fieldType := field.Type()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
|
|
|
|
t := currentField.Convert(timeType).Interface().(time.Time)
|
|
fieldTime := field.Convert(timeType).Interface().(time.Time)
|
|
|
|
return fieldTime.Equal(t)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != currentField.Type() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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 := asIntFromType(field.Type(), 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:
|
|
p := asFloat32(param)
|
|
|
|
return field.Float() == p
|
|
|
|
case reflect.Float64:
|
|
p := asFloat64(param)
|
|
|
|
return field.Float() == p
|
|
|
|
case reflect.Bool:
|
|
p := asBool(param)
|
|
|
|
return field.Bool() == p
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
|
|
// isEqIgnoreCase is the validation function for validating if the current field's string value is
|
|
// equal to the param's value.
|
|
// The comparison is case-insensitive.
|
|
func isEqIgnoreCase(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
param := fl.Param()
|
|
|
|
switch field.Kind() {
|
|
|
|
case reflect.String:
|
|
return strings.EqualFold(field.String(), param)
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
|
|
// isPostcodeByIso3166Alpha2 validates by value which is country code in iso 3166 alpha 2
|
|
// example: `postcode_iso3166_alpha2=US`
|
|
func isPostcodeByIso3166Alpha2(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
param := fl.Param()
|
|
|
|
reg, found := postCodeRegexDict[param]
|
|
if !found {
|
|
return false
|
|
}
|
|
|
|
return reg.MatchString(field.String())
|
|
}
|
|
|
|
// isPostcodeByIso3166Alpha2Field validates by field which represents for a value of country code in iso 3166 alpha 2
|
|
// example: `postcode_iso3166_alpha2_field=CountryCode`
|
|
func isPostcodeByIso3166Alpha2Field(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
params := parseOneOfParam2(fl.Param())
|
|
|
|
if len(params) != 1 {
|
|
return false
|
|
}
|
|
|
|
currentField, kind, _, found := fl.GetStructFieldOKAdvanced2(fl.Parent(), params[0])
|
|
if !found {
|
|
return false
|
|
}
|
|
|
|
if kind != reflect.String {
|
|
panic(fmt.Sprintf("Bad field type %T", currentField.Interface()))
|
|
}
|
|
|
|
reg, found := postCodeRegexDict[currentField.String()]
|
|
if !found {
|
|
return false
|
|
}
|
|
|
|
return reg.MatchString(field.String())
|
|
}
|
|
|
|
// 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())
|
|
}
|
|
|
|
// isBase64RawURL is the validation function for validating if the current field's value is a valid base64 URL safe string without '=' padding.
|
|
func isBase64RawURL(fl FieldLevel) bool {
|
|
return base64RawURLRegex.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()))
|
|
}
|
|
|
|
// isFileURL is the helper function for validating if the `path` valid file URL as per RFC8089
|
|
func isFileURL(path string) bool {
|
|
if !strings.HasPrefix(path, "file:/") {
|
|
return false
|
|
}
|
|
_, err := url.ParseRequestURI(path)
|
|
return err == nil
|
|
}
|
|
|
|
// 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:
|
|
|
|
s := strings.ToLower(field.String())
|
|
|
|
if len(s) == 0 {
|
|
return false
|
|
}
|
|
|
|
if isFileURL(s) {
|
|
return true
|
|
}
|
|
|
|
url, err := url.Parse(s)
|
|
if err != nil || url.Scheme == "" {
|
|
return false
|
|
}
|
|
|
|
if url.Host == "" && url.Fragment == "" && url.Opaque == "" {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
|
|
// isHttpURL is the validation function for validating if the current field's value is a valid HTTP(s) URL.
|
|
func isHttpURL(fl FieldLevel) bool {
|
|
if !isURL(fl) {
|
|
return false
|
|
}
|
|
|
|
field := fl.Field()
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
|
|
s := strings.ToLower(field.String())
|
|
|
|
url, err := url.Parse(s)
|
|
if err != nil || url.Host == "" {
|
|
return false
|
|
}
|
|
|
|
return url.Scheme == "http" || url.Scheme == "https"
|
|
}
|
|
|
|
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 existing 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()))
|
|
}
|
|
|
|
// isImage is the validation function for validating if the current field's value contains the path to a valid image file
|
|
func isImage(fl FieldLevel) bool {
|
|
mimetypes := map[string]bool{
|
|
"image/bmp": true,
|
|
"image/cis-cod": true,
|
|
"image/gif": true,
|
|
"image/ief": true,
|
|
"image/jpeg": true,
|
|
"image/jp2": true,
|
|
"image/jpx": true,
|
|
"image/jpm": true,
|
|
"image/pipeg": true,
|
|
"image/png": true,
|
|
"image/svg+xml": true,
|
|
"image/tiff": true,
|
|
"image/webp": true,
|
|
"image/x-cmu-raster": true,
|
|
"image/x-cmx": true,
|
|
"image/x-icon": true,
|
|
"image/x-portable-anymap": true,
|
|
"image/x-portable-bitmap": true,
|
|
"image/x-portable-graymap": true,
|
|
"image/x-portable-pixmap": true,
|
|
"image/x-rgb": true,
|
|
"image/x-xbitmap": true,
|
|
"image/x-xpixmap": true,
|
|
"image/x-xwindowdump": true,
|
|
}
|
|
field := fl.Field()
|
|
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
filePath := field.String()
|
|
fileInfo, err := os.Stat(filePath)
|
|
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if fileInfo.IsDir() {
|
|
return false
|
|
}
|
|
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer file.Close()
|
|
|
|
mime, err := mimetype.DetectReader(file)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if _, ok := mimetypes[mime.String()]; ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isFilePath is the validation function for validating if the current field's value is a valid file path.
|
|
func isFilePath(fl FieldLevel) bool {
|
|
|
|
var exists bool
|
|
var err error
|
|
|
|
field := fl.Field()
|
|
|
|
// Not valid if it is a directory.
|
|
if isDir(fl) {
|
|
return false
|
|
}
|
|
// If it exists, it obviously is valid.
|
|
// This is done first to avoid code duplication and unnecessary additional logic.
|
|
if exists = isFile(fl); exists {
|
|
return true
|
|
}
|
|
|
|
// It does not exist but may still be a valid filepath.
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
// Every OS allows for whitespace, but none
|
|
// let you use a file with no filename (to my knowledge).
|
|
// Unless you're dealing with raw inodes, but I digress.
|
|
if strings.TrimSpace(field.String()) == "" {
|
|
return false
|
|
}
|
|
// We make sure it isn't a directory.
|
|
if strings.HasSuffix(field.String(), string(os.PathSeparator)) {
|
|
return false
|
|
}
|
|
if _, err = os.Stat(field.String()); err != nil {
|
|
switch t := err.(type) {
|
|
case *fs.PathError:
|
|
if t.Err == syscall.EINVAL {
|
|
// It's definitely an invalid character in the filepath.
|
|
return false
|
|
}
|
|
// It could be a permission error, a does-not-exist error, etc.
|
|
// Out-of-scope for this validation, though.
|
|
return true
|
|
default:
|
|
// Something went *seriously* wrong.
|
|
/*
|
|
Per https://pkg.go.dev/os#Stat:
|
|
"If there is an error, it will be of type *PathError."
|
|
*/
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
|
|
// isE164 is the validation function for validating if the current field's value is a valid e.164 formatted phone number.
|
|
func isE164(fl FieldLevel) bool {
|
|
return e164Regex.MatchString(fl.Field().String())
|
|
}
|
|
|
|
// 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())
|
|
}
|
|
|
|
// isBoolean is the validation function for validating if the current field's value is a valid boolean value or can be safely converted to a boolean value.
|
|
func isBoolean(fl FieldLevel) bool {
|
|
switch fl.Field().Kind() {
|
|
case reflect.Bool:
|
|
return true
|
|
default:
|
|
_, err := strconv.ParseBool(fl.Field().String())
|
|
return err == nil
|
|
}
|
|
}
|
|
|
|
// 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.IsZero()
|
|
}
|
|
}
|
|
|
|
// requireCheckFieldKind is a func for check field kind
|
|
func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue bool) bool {
|
|
field := fl.Field()
|
|
kind := field.Kind()
|
|
var nullable, found bool
|
|
if len(param) > 0 {
|
|
field, kind, nullable, found = fl.GetStructFieldOKAdvanced2(fl.Parent(), param)
|
|
if !found {
|
|
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:
|
|
if nullable && field.Interface() != nil {
|
|
return false
|
|
}
|
|
return field.IsValid() && field.IsZero()
|
|
}
|
|
}
|
|
|
|
// requireCheckFieldValue is a func for check field value
|
|
func requireCheckFieldValue(
|
|
fl FieldLevel, param string, value string, defaultNotFoundValue bool,
|
|
) bool {
|
|
field, kind, _, found := fl.GetStructFieldOKAdvanced2(fl.Parent(), param)
|
|
if !found {
|
|
return defaultNotFoundValue
|
|
}
|
|
|
|
switch kind {
|
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return field.Int() == asInt(value)
|
|
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
return field.Uint() == asUint(value)
|
|
|
|
case reflect.Float32:
|
|
return field.Float() == asFloat32(value)
|
|
|
|
case reflect.Float64:
|
|
return field.Float() == asFloat64(value)
|
|
|
|
case reflect.Slice, reflect.Map, reflect.Array:
|
|
return int64(field.Len()) == asInt(value)
|
|
|
|
case reflect.Bool:
|
|
return field.Bool() == asBool(value)
|
|
}
|
|
|
|
// default reflect.String:
|
|
return field.String() == value
|
|
}
|
|
|
|
// requiredIf is the validation function
|
|
// The field under validation must be present and not empty only if all the other specified fields are equal to the value following with the specified field.
|
|
func requiredIf(fl FieldLevel) bool {
|
|
params := parseOneOfParam2(fl.Param())
|
|
if len(params)%2 != 0 {
|
|
panic(fmt.Sprintf("Bad param number for required_if %s", fl.FieldName()))
|
|
}
|
|
for i := 0; i < len(params); i += 2 {
|
|
if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
|
|
return true
|
|
}
|
|
}
|
|
return hasValue(fl)
|
|
}
|
|
|
|
// excludedIf is the validation function
|
|
// The field under validation must not be present or is empty only if all the other specified fields are equal to the value following with the specified field.
|
|
func excludedIf(fl FieldLevel) bool {
|
|
params := parseOneOfParam2(fl.Param())
|
|
if len(params)%2 != 0 {
|
|
panic(fmt.Sprintf("Bad param number for excluded_if %s", fl.FieldName()))
|
|
}
|
|
|
|
for i := 0; i < len(params); i += 2 {
|
|
if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
|
|
return true
|
|
}
|
|
}
|
|
return !hasValue(fl)
|
|
}
|
|
|
|
// requiredUnless is the validation function
|
|
// The field under validation must be present and not empty only unless all the other specified fields are equal to the value following with the specified field.
|
|
func requiredUnless(fl FieldLevel) bool {
|
|
params := parseOneOfParam2(fl.Param())
|
|
if len(params)%2 != 0 {
|
|
panic(fmt.Sprintf("Bad param number for required_unless %s", fl.FieldName()))
|
|
}
|
|
|
|
for i := 0; i < len(params); i += 2 {
|
|
if requireCheckFieldValue(fl, params[i], params[i+1], false) {
|
|
return true
|
|
}
|
|
}
|
|
return hasValue(fl)
|
|
}
|
|
|
|
// skipUnless is the validation function
|
|
// The field under validation must be present and not empty only unless all the other specified fields are equal to the value following with the specified field.
|
|
func skipUnless(fl FieldLevel) bool {
|
|
params := parseOneOfParam2(fl.Param())
|
|
if len(params)%2 != 0 {
|
|
panic(fmt.Sprintf("Bad param number for skip_unless %s", fl.FieldName()))
|
|
}
|
|
for i := 0; i < len(params); i += 2 {
|
|
if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
|
|
return true
|
|
}
|
|
}
|
|
return hasValue(fl)
|
|
}
|
|
|
|
// excludedUnless is the validation function
|
|
// The field under validation must not be present or is empty unless all the other specified fields are equal to the value following with the specified field.
|
|
func excludedUnless(fl FieldLevel) bool {
|
|
params := parseOneOfParam2(fl.Param())
|
|
if len(params)%2 != 0 {
|
|
panic(fmt.Sprintf("Bad param number for excluded_unless %s", fl.FieldName()))
|
|
}
|
|
for i := 0; i < len(params); i += 2 {
|
|
if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
|
|
return !hasValue(fl)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// excludedWith is the validation function
|
|
// The field under validation must not be present or is empty if any of the other specified fields are present.
|
|
func excludedWith(fl FieldLevel) bool {
|
|
params := parseOneOfParam2(fl.Param())
|
|
for _, param := range params {
|
|
if !requireCheckFieldKind(fl, param, true) {
|
|
return !hasValue(fl)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// 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, true) {
|
|
return hasValue(fl)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// excludedWithAll is the validation function
|
|
// The field under validation must not be present or is empty if all of the other specified fields are present.
|
|
func excludedWithAll(fl FieldLevel) bool {
|
|
params := parseOneOfParam2(fl.Param())
|
|
for _, param := range params {
|
|
if requireCheckFieldKind(fl, param, true) {
|
|
return true
|
|
}
|
|
}
|
|
return !hasValue(fl)
|
|
}
|
|
|
|
// 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, true) {
|
|
return true
|
|
}
|
|
}
|
|
return hasValue(fl)
|
|
}
|
|
|
|
// excludedWithout is the validation function
|
|
// The field under validation must not be present or is empty when any of the other specified fields are not present.
|
|
func excludedWithout(fl FieldLevel) bool {
|
|
if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) {
|
|
return !hasValue(fl)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// 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 {
|
|
if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) {
|
|
return hasValue(fl)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// excludedWithoutAll is the validation function
|
|
// The field under validation must not be present or is empty when all of the other specified fields are not present.
|
|
func excludedWithoutAll(fl FieldLevel) bool {
|
|
params := parseOneOfParam2(fl.Param())
|
|
for _, param := range params {
|
|
if !requireCheckFieldKind(fl, param, true) {
|
|
return true
|
|
}
|
|
}
|
|
return !hasValue(fl)
|
|
}
|
|
|
|
// 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()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
|
|
|
|
t := currentField.Convert(timeType).Interface().(time.Time)
|
|
fieldTime := field.Convert(timeType).Interface().(time.Time)
|
|
|
|
return fieldTime.After(t) || fieldTime.Equal(t)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != currentField.Type() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
|
|
|
|
t := currentField.Convert(timeType).Interface().(time.Time)
|
|
fieldTime := field.Convert(timeType).Interface().(time.Time)
|
|
|
|
return fieldTime.After(t)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != currentField.Type() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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 := asIntFromType(field.Type(), 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:
|
|
p := asFloat32(param)
|
|
|
|
return field.Float() >= p
|
|
|
|
case reflect.Float64:
|
|
p := asFloat64(param)
|
|
|
|
return field.Float() >= p
|
|
|
|
case reflect.Struct:
|
|
|
|
if field.Type().ConvertibleTo(timeType) {
|
|
|
|
now := time.Now().UTC()
|
|
t := field.Convert(timeType).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 := asIntFromType(field.Type(), 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:
|
|
p := asFloat32(param)
|
|
|
|
return field.Float() > p
|
|
|
|
case reflect.Float64:
|
|
p := asFloat64(param)
|
|
|
|
return field.Float() > p
|
|
|
|
case reflect.Struct:
|
|
|
|
if field.Type().ConvertibleTo(timeType) {
|
|
|
|
return field.Convert(timeType).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 := asIntFromType(field.Type(), 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:
|
|
p := asFloat32(param)
|
|
|
|
return field.Float() == p
|
|
|
|
case reflect.Float64:
|
|
p := asFloat64(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()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
|
|
|
|
t := currentField.Convert(timeType).Interface().(time.Time)
|
|
fieldTime := field.Convert(timeType).Interface().(time.Time)
|
|
|
|
return fieldTime.Before(t) || fieldTime.Equal(t)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != currentField.Type() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
|
|
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
|
|
|
|
t := currentField.Convert(timeType).Interface().(time.Time)
|
|
fieldTime := field.Convert(timeType).Interface().(time.Time)
|
|
|
|
return fieldTime.Before(t)
|
|
}
|
|
|
|
// Not Same underlying type i.e. struct and time
|
|
if fieldType != currentField.Type() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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 := asIntFromType(field.Type(), 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:
|
|
p := asFloat32(param)
|
|
|
|
return field.Float() <= p
|
|
|
|
case reflect.Float64:
|
|
p := asFloat64(param)
|
|
|
|
return field.Float() <= p
|
|
|
|
case reflect.Struct:
|
|
|
|
if field.Type().ConvertibleTo(timeType) {
|
|
|
|
now := time.Now().UTC()
|
|
t := field.Convert(timeType).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 := asIntFromType(field.Type(), 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:
|
|
p := asFloat32(param)
|
|
|
|
return field.Float() < p
|
|
|
|
case reflect.Float64:
|
|
p := asFloat64(param)
|
|
|
|
return field.Float() < p
|
|
|
|
case reflect.Struct:
|
|
|
|
if field.Type().ConvertibleTo(timeType) {
|
|
|
|
return field.Convert(timeType).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
|
|
}
|
|
|
|
return fqdnRegexRFC1123.MatchString(val)
|
|
}
|
|
|
|
// isDir is the validation function for validating if the current field's value is a valid existing 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()))
|
|
}
|
|
|
|
// isDirPath is the validation function for validating if the current field's value is a valid directory.
|
|
func isDirPath(fl FieldLevel) bool {
|
|
|
|
var exists bool
|
|
var err error
|
|
|
|
field := fl.Field()
|
|
|
|
// If it exists, it obviously is valid.
|
|
// This is done first to avoid code duplication and unnecessary additional logic.
|
|
if exists = isDir(fl); exists {
|
|
return true
|
|
}
|
|
|
|
// It does not exist but may still be a valid path.
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
// Every OS allows for whitespace, but none
|
|
// let you use a dir with no name (to my knowledge).
|
|
// Unless you're dealing with raw inodes, but I digress.
|
|
if strings.TrimSpace(field.String()) == "" {
|
|
return false
|
|
}
|
|
if _, err = os.Stat(field.String()); err != nil {
|
|
switch t := err.(type) {
|
|
case *fs.PathError:
|
|
if t.Err == syscall.EINVAL {
|
|
// It's definitely an invalid character in the path.
|
|
return false
|
|
}
|
|
// It could be a permission error, a does-not-exist error, etc.
|
|
// Out-of-scope for this validation, though.
|
|
// Lastly, we make sure it is a directory.
|
|
if strings.HasSuffix(field.String(), string(os.PathSeparator)) {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
default:
|
|
// Something went *seriously* wrong.
|
|
/*
|
|
Per https://pkg.go.dev/os#Stat:
|
|
"If there is an error, it will be of type *PathError."
|
|
*/
|
|
panic(err)
|
|
}
|
|
}
|
|
// We repeat the check here to make sure it is an explicit directory in case the above os.Stat didn't trigger an error.
|
|
if strings.HasSuffix(field.String(), string(os.PathSeparator)) {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
|
|
// isJSON is the validation function for validating if the current field's value is a valid json string.
|
|
func isJSON(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
val := field.String()
|
|
return json.Valid([]byte(val))
|
|
case reflect.Slice:
|
|
fieldType := field.Type()
|
|
|
|
if fieldType.ConvertibleTo(byteSliceType) {
|
|
b := field.Convert(byteSliceType).Interface().([]byte)
|
|
return json.Valid(b)
|
|
}
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
|
|
// isJWT is the validation function for validating if the current field's value is a valid JWT string.
|
|
func isJWT(fl FieldLevel) bool {
|
|
return jWTRegex.MatchString(fl.Field().String())
|
|
}
|
|
|
|
// isHostnamePort validates a <dns>:<port> combination for fields typically used for socket address.
|
|
func isHostnamePort(fl FieldLevel) bool {
|
|
val := fl.Field().String()
|
|
host, port, err := net.SplitHostPort(val)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
// Port must be a iny <= 65535.
|
|
if portNum, err := strconv.ParseInt(
|
|
port, 10, 32,
|
|
); err != nil || portNum > 65535 || portNum < 1 {
|
|
return false
|
|
}
|
|
|
|
// If host is specified, it should match a DNS name
|
|
if host != "" {
|
|
return hostnameRegexRFC1123.MatchString(host)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// isLowercase is the validation function for validating if the current field's value is a lowercase string.
|
|
func isLowercase(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
|
|
if field.Kind() == reflect.String {
|
|
if field.String() == "" {
|
|
return false
|
|
}
|
|
return field.String() == strings.ToLower(field.String())
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
|
|
// isUppercase is the validation function for validating if the current field's value is an uppercase string.
|
|
func isUppercase(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
|
|
if field.Kind() == reflect.String {
|
|
if field.String() == "" {
|
|
return false
|
|
}
|
|
return field.String() == strings.ToUpper(field.String())
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
|
|
// isDatetime is the validation function for validating if the current field's value is a valid datetime string.
|
|
func isDatetime(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
param := fl.Param()
|
|
|
|
if field.Kind() == reflect.String {
|
|
_, err := time.Parse(param, field.String())
|
|
|
|
return err == nil
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
|
|
// isTimeZone is the validation function for validating if the current field's value is a valid time zone string.
|
|
func isTimeZone(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
|
|
if field.Kind() == reflect.String {
|
|
// empty value is converted to UTC by time.LoadLocation but disallow it as it is not a valid time zone name
|
|
if field.String() == "" {
|
|
return false
|
|
}
|
|
|
|
// Local value is converted to the current system time zone by time.LoadLocation but disallow it as it is not a valid time zone name
|
|
if strings.ToLower(field.String()) == "local" {
|
|
return false
|
|
}
|
|
|
|
_, err := time.LoadLocation(field.String())
|
|
return err == nil
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
|
|
// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-2 country code.
|
|
func isIso3166Alpha2(fl FieldLevel) bool {
|
|
val := fl.Field().String()
|
|
return iso3166_1_alpha2[val]
|
|
}
|
|
|
|
// isIso3166Alpha3 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-3 country code.
|
|
func isIso3166Alpha3(fl FieldLevel) bool {
|
|
val := fl.Field().String()
|
|
return iso3166_1_alpha3[val]
|
|
}
|
|
|
|
// isIso3166AlphaNumeric is the validation function for validating if the current field's value is a valid iso3166-1 alpha-numeric country code.
|
|
func isIso3166AlphaNumeric(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
|
|
var code int
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
i, err := strconv.Atoi(field.String())
|
|
if err != nil {
|
|
return false
|
|
}
|
|
code = i % 1000
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
code = int(field.Int() % 1000)
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
code = int(field.Uint() % 1000)
|
|
default:
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
return iso3166_1_alpha_numeric[code]
|
|
}
|
|
|
|
// isIso31662 is the validation function for validating if the current field's value is a valid iso3166-2 code.
|
|
func isIso31662(fl FieldLevel) bool {
|
|
val := fl.Field().String()
|
|
return iso3166_2[val]
|
|
}
|
|
|
|
// isIso4217 is the validation function for validating if the current field's value is a valid iso4217 currency code.
|
|
func isIso4217(fl FieldLevel) bool {
|
|
val := fl.Field().String()
|
|
return iso4217[val]
|
|
}
|
|
|
|
// isIso4217Numeric is the validation function for validating if the current field's value is a valid iso4217 numeric currency code.
|
|
func isIso4217Numeric(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
|
|
var code int
|
|
switch field.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
code = int(field.Int())
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
code = int(field.Uint())
|
|
default:
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
return iso4217_numeric[code]
|
|
}
|
|
|
|
// isBCP47LanguageTag is the validation function for validating if the current field's value is a valid BCP 47 language tag, as parsed by language.Parse
|
|
func isBCP47LanguageTag(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
|
|
if field.Kind() == reflect.String {
|
|
_, err := language.Parse(field.String())
|
|
return err == nil
|
|
}
|
|
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
|
|
// isIsoBicFormat is the validation function for validating if the current field's value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362
|
|
func isIsoBicFormat(fl FieldLevel) bool {
|
|
bicString := fl.Field().String()
|
|
|
|
return bicRegex.MatchString(bicString)
|
|
}
|
|
|
|
// isSemverFormat is the validation function for validating if the current field's value is a valid semver version, defined in Semantic Versioning 2.0.0
|
|
func isSemverFormat(fl FieldLevel) bool {
|
|
semverString := fl.Field().String()
|
|
|
|
return semverRegex.MatchString(semverString)
|
|
}
|
|
|
|
// isCveFormat is the validation function for validating if the current field's value is a valid cve id, defined in CVE mitre org
|
|
func isCveFormat(fl FieldLevel) bool {
|
|
cveString := fl.Field().String()
|
|
|
|
return cveRegex.MatchString(cveString)
|
|
}
|
|
|
|
// isDnsRFC1035LabelFormat is the validation function
|
|
// for validating if the current field's value is
|
|
// a valid dns RFC 1035 label, defined in RFC 1035.
|
|
func isDnsRFC1035LabelFormat(fl FieldLevel) bool {
|
|
val := fl.Field().String()
|
|
return dnsRegexRFC1035Label.MatchString(val)
|
|
}
|
|
|
|
// digitsHaveLuhnChecksum returns true if and only if the last element of the given digits slice is the Luhn checksum of the previous elements
|
|
func digitsHaveLuhnChecksum(digits []string) bool {
|
|
size := len(digits)
|
|
sum := 0
|
|
for i, digit := range digits {
|
|
value, err := strconv.Atoi(digit)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if size%2 == 0 && i%2 == 0 || size%2 == 1 && i%2 == 1 {
|
|
v := value * 2
|
|
if v >= 10 {
|
|
sum += 1 + (v % 10)
|
|
} else {
|
|
sum += v
|
|
}
|
|
} else {
|
|
sum += value
|
|
}
|
|
}
|
|
return (sum % 10) == 0
|
|
}
|
|
|
|
// isMongoDB is the validation function for validating if the current field's value is valid mongoDB objectID
|
|
func isMongoDB(fl FieldLevel) bool {
|
|
val := fl.Field().String()
|
|
return mongodbRegex.MatchString(val)
|
|
}
|
|
|
|
// isSpiceDB is the validation function for validating if the current field's value is valid for use with Authzed SpiceDB in the indicated way
|
|
func isSpiceDB(fl FieldLevel) bool {
|
|
val := fl.Field().String()
|
|
param := fl.Param()
|
|
|
|
switch param {
|
|
case "permission":
|
|
return spicedbPermissionRegex.MatchString(val)
|
|
case "type":
|
|
return spicedbTypeRegex.MatchString(val)
|
|
case "id", "":
|
|
return spicedbIDRegex.MatchString(val)
|
|
}
|
|
|
|
panic("Unrecognized parameter: " + param)
|
|
}
|
|
|
|
// isCreditCard is the validation function for validating if the current field's value is a valid credit card number
|
|
func isCreditCard(fl FieldLevel) bool {
|
|
val := fl.Field().String()
|
|
var creditCard bytes.Buffer
|
|
segments := strings.Split(val, " ")
|
|
for _, segment := range segments {
|
|
if len(segment) < 3 {
|
|
return false
|
|
}
|
|
creditCard.WriteString(segment)
|
|
}
|
|
|
|
ccDigits := strings.Split(creditCard.String(), "")
|
|
size := len(ccDigits)
|
|
if size < 12 || size > 19 {
|
|
return false
|
|
}
|
|
|
|
return digitsHaveLuhnChecksum(ccDigits)
|
|
}
|
|
|
|
// hasLuhnChecksum is the validation for validating if the current field's value has a valid Luhn checksum
|
|
func hasLuhnChecksum(fl FieldLevel) bool {
|
|
field := fl.Field()
|
|
var str string // convert to a string which will then be split into single digits; easier and more readable than shifting/extracting single digits from a number
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
str = field.String()
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
str = strconv.FormatInt(field.Int(), 10)
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
str = strconv.FormatUint(field.Uint(), 10)
|
|
default:
|
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
}
|
|
size := len(str)
|
|
if size < 2 { // there has to be at least one digit that carries a meaning + the checksum
|
|
return false
|
|
}
|
|
digits := strings.Split(str, "")
|
|
return digitsHaveLuhnChecksum(digits)
|
|
}
|
|
|
|
// isCron is the validation function for validating if the current field's value is a valid cron expression
|
|
func isCron(fl FieldLevel) bool {
|
|
cronString := fl.Field().String()
|
|
return cronRegex.MatchString(cronString)
|
|
}
|