2021-08-12 21:03:24 +02:00
package validator
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
2023-03-20 11:05:46 +01:00
"io/fs"
2021-08-12 21:03:24 +02:00
"net"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"sync"
2023-03-20 11:05:46 +01:00
"syscall"
2021-08-12 21:03:24 +02:00
"time"
"unicode/utf8"
"golang.org/x/crypto/sha3"
"golang.org/x/text/language"
2023-05-22 09:17:48 +02:00
"github.com/gabriel-vasile/mimetype"
2023-11-27 14:15:03 +01:00
urn "github.com/leodido/go-urn"
2021-08-12 21:03:24 +02:00
)
// 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
2023-05-01 11:02:44 +02:00
// wrapFunc wraps normal Func makes it compatible with FuncCtx
2021-08-12 21:03:24 +02:00
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 : { } ,
2024-03-11 11:12:06 +01:00
omitnil : { } ,
2021-08-12 21:03:24 +02:00
skipValidationTag : { } ,
utf8HexComma : { } ,
utf8Pipe : { } ,
noStructLevelTag : { } ,
requiredTag : { } ,
isdefault : { } ,
}
2021-09-10 14:42:14 +02:00
// bakedInAliases is a default mapping of a single validation tag that
2021-08-12 21:03:24 +02:00
// 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" ,
}
2021-09-10 14:42:14 +02:00
// bakedInValidators is the default map of ValidationFunc
2021-08-12 21:03:24 +02:00
// 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 ,
2023-05-01 11:02:44 +02:00
"skip_unless" : skipUnless ,
2021-08-12 21:03:24 +02:00
"required_with" : requiredWith ,
"required_with_all" : requiredWithAll ,
"required_without" : requiredWithout ,
"required_without_all" : requiredWithoutAll ,
2022-05-02 15:05:18 +02:00
"excluded_if" : excludedIf ,
"excluded_unless" : excludedUnless ,
2021-08-12 21:03:24 +02:00
"excluded_with" : excludedWith ,
"excluded_with_all" : excludedWithAll ,
"excluded_without" : excludedWithout ,
"excluded_without_all" : excludedWithoutAll ,
"isdefault" : isDefault ,
"len" : hasLengthOf ,
"min" : hasMinOf ,
"max" : hasMaxOf ,
"eq" : isEq ,
2023-03-20 11:05:46 +01:00
"eq_ignore_case" : isEqIgnoreCase ,
2021-08-12 21:03:24 +02:00
"ne" : isNe ,
2023-03-20 11:05:46 +01:00
"ne_ignore_case" : isNeIgnoreCase ,
2021-08-12 21:03:24 +02:00
"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 ,
2021-09-10 14:42:14 +02:00
"boolean" : isBoolean ,
2021-08-12 21:03:24 +02:00
"numeric" : isNumeric ,
"number" : isNumber ,
"hexadecimal" : isHexadecimal ,
"hexcolor" : isHEXColor ,
"rgb" : isRGB ,
"rgba" : isRGBA ,
"hsl" : isHSL ,
"hsla" : isHSLA ,
"e164" : isE164 ,
"email" : isEmail ,
"url" : isURL ,
2023-03-20 11:05:46 +01:00
"http_url" : isHttpURL ,
2021-08-12 21:03:24 +02:00
"uri" : isURI ,
"urn_rfc2141" : isUrnRFC2141 , // RFC 2141
"file" : isFile ,
2023-03-20 11:05:46 +01:00
"filepath" : isFilePath ,
2021-08-12 21:03:24 +02:00
"base64" : isBase64 ,
"base64url" : isBase64URL ,
2023-03-20 11:05:46 +01:00
"base64rawurl" : isBase64RawURL ,
2021-08-12 21:03:24 +02:00
"contains" : contains ,
"containsany" : containsAny ,
"containsrune" : containsRune ,
"excludes" : excludes ,
"excludesall" : excludesAll ,
"excludesrune" : excludesRune ,
"startswith" : startsWith ,
"endswith" : endsWith ,
"startsnotwith" : startsNotWith ,
"endsnotwith" : endsNotWith ,
2023-05-22 09:17:48 +02:00
"image" : isImage ,
2021-08-12 21:03:24 +02:00
"isbn" : isISBN ,
"isbn10" : isISBN10 ,
"isbn13" : isISBN13 ,
2024-03-11 11:12:06 +01:00
"issn" : isISSN ,
2021-08-12 21:03:24 +02:00
"eth_addr" : isEthereumAddress ,
2023-03-20 11:05:46 +01:00
"eth_addr_checksum" : isEthereumAddressChecksum ,
2021-08-12 21:03:24 +02:00
"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 ,
2022-05-02 15:05:18 +02:00
"ulid" : isULID ,
"md4" : isMD4 ,
"md5" : isMD5 ,
"sha256" : isSHA256 ,
"sha384" : isSHA384 ,
"sha512" : isSHA512 ,
"ripemd128" : isRIPEMD128 ,
"ripemd160" : isRIPEMD160 ,
"tiger128" : isTIGER128 ,
"tiger160" : isTIGER160 ,
"tiger192" : isTIGER192 ,
2021-08-12 21:03:24 +02:00
"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 ,
2023-03-20 11:05:46 +01:00
"dirpath" : isDirPath ,
2021-08-12 21:03:24 +02:00
"json" : isJSON ,
2021-09-10 14:42:14 +02:00
"jwt" : isJWT ,
2021-08-12 21:03:24 +02:00
"hostname_port" : isHostnamePort ,
"lowercase" : isLowercase ,
"uppercase" : isUppercase ,
"datetime" : isDatetime ,
"timezone" : isTimeZone ,
"iso3166_1_alpha2" : isIso3166Alpha2 ,
"iso3166_1_alpha3" : isIso3166Alpha3 ,
"iso3166_1_alpha_numeric" : isIso3166AlphaNumeric ,
2021-09-10 14:42:14 +02:00
"iso3166_2" : isIso31662 ,
"iso4217" : isIso4217 ,
"iso4217_numeric" : isIso4217Numeric ,
2021-08-12 21:03:24 +02:00
"bcp47_language_tag" : isBCP47LanguageTag ,
"postcode_iso3166_alpha2" : isPostcodeByIso3166Alpha2 ,
"postcode_iso3166_alpha2_field" : isPostcodeByIso3166Alpha2Field ,
"bic" : isIsoBicFormat ,
2022-05-02 15:05:18 +02:00
"semver" : isSemverFormat ,
"dns_rfc1035_label" : isDnsRFC1035LabelFormat ,
"credit_card" : isCreditCard ,
2023-03-20 11:05:46 +01:00
"cve" : isCveFormat ,
"luhn_checksum" : hasLuhnChecksum ,
"mongodb" : isMongoDB ,
"cron" : isCron ,
2023-11-27 14:15:03 +01:00
"spicedb" : isSpiceDB ,
2021-08-12 21:03:24 +02:00
}
)
2022-05-02 15:05:18 +02:00
var (
oneofValsCache = map [ string ] [ ] string { }
oneofValsCacheRWLock = sync . RWMutex { }
)
2021-08-12 21:03:24 +02:00
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 ( ) ) )
2023-03-20 11:05:46 +01:00
var fieldlen int
2021-08-12 21:03:24 +02:00
for i := 0 ; i < field . Len ( ) ; i ++ {
2023-03-20 11:05:46 +01:00
key := reflect . Indirect ( reflect . Indirect ( field . Index ( i ) ) . FieldByName ( param ) )
if key . IsValid ( ) {
fieldlen ++
m . SetMapIndex ( key , v )
}
2021-08-12 21:03:24 +02:00
}
2023-03-20 11:05:46 +01:00
return fieldlen == m . Len ( )
2021-08-12 21:03:24 +02:00
case reflect . Map :
2023-03-20 11:05:46 +01:00
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 ( ) ) )
}
2021-08-12 21:03:24 +02:00
for _ , k := range field . MapKeys ( ) {
2023-03-20 11:05:46 +01:00
m . SetMapIndex ( reflect . Indirect ( field . MapIndex ( k ) ) , v )
2021-08-12 21:03:24 +02:00
}
2023-03-20 11:05:46 +01:00
2021-08-12 21:03:24 +02:00
return field . Len ( ) == m . Len ( )
default :
2023-03-20 11:05:46 +01:00
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 ( )
}
2021-08-12 21:03:24 +02:00
panic ( fmt . Sprintf ( "Bad field type %T" , field . Interface ( ) ) )
}
}
2021-09-10 14:42:14 +02:00
// isMAC is the validation function for validating if the field's value is a valid MAC address.
2021-08-12 21:03:24 +02:00
func isMAC ( fl FieldLevel ) bool {
_ , err := net . ParseMAC ( fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
// isCIDRv4 is the validation function for validating if the field's value is a valid v4 CIDR address.
2021-08-12 21:03:24 +02:00
func isCIDRv4 ( fl FieldLevel ) bool {
2023-11-27 14:15:03 +01:00
ip , net , err := net . ParseCIDR ( fl . Field ( ) . String ( ) )
2021-08-12 21:03:24 +02:00
2023-11-27 14:15:03 +01:00
return err == nil && ip . To4 ( ) != nil && net . IP . Equal ( ip )
2021-08-12 21:03:24 +02:00
}
2021-09-10 14:42:14 +02:00
// isCIDRv6 is the validation function for validating if the field's value is a valid v6 CIDR address.
2021-08-12 21:03:24 +02:00
func isCIDRv6 ( fl FieldLevel ) bool {
ip , _ , err := net . ParseCIDR ( fl . Field ( ) . String ( ) )
return err == nil && ip . To4 ( ) == nil
}
2021-09-10 14:42:14 +02:00
// isCIDR is the validation function for validating if the field's value is a valid v4 or v6 CIDR address.
2021-08-12 21:03:24 +02:00
func isCIDR ( fl FieldLevel ) bool {
_ , _ , err := net . ParseCIDR ( fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
// isIPv4 is the validation function for validating if a value is a valid v4 IP address.
2021-08-12 21:03:24 +02:00
func isIPv4 ( fl FieldLevel ) bool {
ip := net . ParseIP ( fl . Field ( ) . String ( ) )
return ip != nil && ip . To4 ( ) != nil
}
2021-09-10 14:42:14 +02:00
// isIPv6 is the validation function for validating if the field's value is a valid v6 IP address.
2021-08-12 21:03:24 +02:00
func isIPv6 ( fl FieldLevel ) bool {
ip := net . ParseIP ( fl . Field ( ) . String ( ) )
return ip != nil && ip . To4 ( ) == nil
}
2021-09-10 14:42:14 +02:00
// isIP is the validation function for validating if the field's value is a valid v4 or v6 IP address.
2021-08-12 21:03:24 +02:00
func isIP ( fl FieldLevel ) bool {
ip := net . ParseIP ( fl . Field ( ) . String ( ) )
return ip != nil
}
2021-09-10 14:42:14 +02:00
// isSSN is the validation function for validating if the field's value is a valid SSN.
2021-08-12 21:03:24 +02:00
func isSSN ( fl FieldLevel ) bool {
field := fl . Field ( )
if field . Len ( ) != 11 {
return false
}
return sSNRegex . MatchString ( field . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isLongitude is the validation function for validating if the field's value is a valid longitude coordinate.
2021-08-12 21:03:24 +02:00
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 )
}
2021-09-10 14:42:14 +02:00
// isLatitude is the validation function for validating if the field's value is a valid latitude coordinate.
2021-08-12 21:03:24 +02:00
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 )
}
2021-09-10 14:42:14 +02:00
// isDataURI is the validation function for validating if the field's value is a valid data URI.
2021-08-12 21:03:24 +02:00
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 ] )
}
2021-09-10 14:42:14 +02:00
// hasMultiByteCharacter is the validation function for validating if the field's value has a multi byte character.
2021-08-12 21:03:24 +02:00
func hasMultiByteCharacter ( fl FieldLevel ) bool {
field := fl . Field ( )
if field . Len ( ) == 0 {
return true
}
return multibyteRegex . MatchString ( field . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isPrintableASCII is the validation function for validating if the field's value is a valid printable ASCII character.
2021-08-12 21:03:24 +02:00
func isPrintableASCII ( fl FieldLevel ) bool {
return printableASCIIRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isASCII is the validation function for validating if the field's value is a valid ASCII character.
2021-08-12 21:03:24 +02:00
func isASCII ( fl FieldLevel ) bool {
return aSCIIRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isUUID5 is the validation function for validating if the field's value is a valid v5 UUID.
2021-08-12 21:03:24 +02:00
func isUUID5 ( fl FieldLevel ) bool {
2024-03-11 11:12:06 +01:00
return fieldMatchesRegexByStringerValOrString ( uUID5Regex , fl )
2021-08-12 21:03:24 +02:00
}
2021-09-10 14:42:14 +02:00
// isUUID4 is the validation function for validating if the field's value is a valid v4 UUID.
2021-08-12 21:03:24 +02:00
func isUUID4 ( fl FieldLevel ) bool {
2024-03-11 11:12:06 +01:00
return fieldMatchesRegexByStringerValOrString ( uUID4Regex , fl )
2021-08-12 21:03:24 +02:00
}
2021-09-10 14:42:14 +02:00
// isUUID3 is the validation function for validating if the field's value is a valid v3 UUID.
2021-08-12 21:03:24 +02:00
func isUUID3 ( fl FieldLevel ) bool {
2024-03-11 11:12:06 +01:00
return fieldMatchesRegexByStringerValOrString ( uUID3Regex , fl )
2021-08-12 21:03:24 +02:00
}
2021-09-10 14:42:14 +02:00
// isUUID is the validation function for validating if the field's value is a valid UUID of any version.
2021-08-12 21:03:24 +02:00
func isUUID ( fl FieldLevel ) bool {
2024-03-11 11:12:06 +01:00
return fieldMatchesRegexByStringerValOrString ( uUIDRegex , fl )
2021-08-12 21:03:24 +02:00
}
2021-09-10 14:42:14 +02:00
// isUUID5RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v5 UUID.
2021-08-12 21:03:24 +02:00
func isUUID5RFC4122 ( fl FieldLevel ) bool {
2024-03-11 11:12:06 +01:00
return fieldMatchesRegexByStringerValOrString ( uUID5RFC4122Regex , fl )
2021-08-12 21:03:24 +02:00
}
2021-09-10 14:42:14 +02:00
// isUUID4RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v4 UUID.
2021-08-12 21:03:24 +02:00
func isUUID4RFC4122 ( fl FieldLevel ) bool {
2024-03-11 11:12:06 +01:00
return fieldMatchesRegexByStringerValOrString ( uUID4RFC4122Regex , fl )
2021-08-12 21:03:24 +02:00
}
2021-09-10 14:42:14 +02:00
// isUUID3RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v3 UUID.
2021-08-12 21:03:24 +02:00
func isUUID3RFC4122 ( fl FieldLevel ) bool {
2024-03-11 11:12:06 +01:00
return fieldMatchesRegexByStringerValOrString ( uUID3RFC4122Regex , fl )
2021-08-12 21:03:24 +02:00
}
2021-09-10 14:42:14 +02:00
// isUUIDRFC4122 is the validation function for validating if the field's value is a valid RFC4122 UUID of any version.
2021-08-12 21:03:24 +02:00
func isUUIDRFC4122 ( fl FieldLevel ) bool {
2024-03-11 11:12:06 +01:00
return fieldMatchesRegexByStringerValOrString ( uUIDRFC4122Regex , fl )
2021-08-12 21:03:24 +02:00
}
2022-05-02 15:05:18 +02:00
// isULID is the validation function for validating if the field's value is a valid ULID.
func isULID ( fl FieldLevel ) bool {
2024-03-11 11:12:06 +01:00
return fieldMatchesRegexByStringerValOrString ( uLIDRegex , fl )
2022-05-02 15:05:18 +02:00
}
// 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 ( ) )
}
2021-09-10 14:42:14 +02:00
// isISBN is the validation function for validating if the field's value is a valid v10 or v13 ISBN.
2021-08-12 21:03:24 +02:00
func isISBN ( fl FieldLevel ) bool {
return isISBN10 ( fl ) || isISBN13 ( fl )
}
2021-09-10 14:42:14 +02:00
// isISBN13 is the validation function for validating if the field's value is a valid v13 ISBN.
2021-08-12 21:03:24 +02:00
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
}
2021-09-10 14:42:14 +02:00
// isISBN10 is the validation function for validating if the field's value is a valid v10 ISBN.
2021-08-12 21:03:24 +02:00
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
}
2024-03-11 11:12:06 +01:00
// 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
}
2021-09-10 14:42:14 +02:00
// isEthereumAddress is the validation function for validating if the field's value is a valid Ethereum address.
2021-08-12 21:03:24 +02:00
func isEthereumAddress ( fl FieldLevel ) bool {
address := fl . Field ( ) . String ( )
2023-03-20 11:05:46 +01:00
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 ( )
2021-08-12 21:03:24 +02:00
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
}
2021-09-10 14:42:14 +02:00
// isBitcoinAddress is the validation function for validating if the field's value is a valid btc address
2021-08-12 21:03:24 +02:00
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
}
2021-09-10 14:42:14 +02:00
// isBitcoinBech32Address is the validation function for validating if the field's value is a valid bech32 btc address
2021-08-12 21:03:24 +02:00
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
}
2021-09-10 14:42:14 +02:00
// excludesRune is the validation function for validating that the field's value does not contain the rune specified within the param.
2021-08-12 21:03:24 +02:00
func excludesRune ( fl FieldLevel ) bool {
return ! containsRune ( fl )
}
2021-09-10 14:42:14 +02:00
// excludesAll is the validation function for validating that the field's value does not contain any of the characters specified within the param.
2021-08-12 21:03:24 +02:00
func excludesAll ( fl FieldLevel ) bool {
return ! containsAny ( fl )
}
2021-09-10 14:42:14 +02:00
// excludes is the validation function for validating that the field's value does not contain the text specified within the param.
2021-08-12 21:03:24 +02:00
func excludes ( fl FieldLevel ) bool {
return ! contains ( fl )
}
2021-09-10 14:42:14 +02:00
// containsRune is the validation function for validating that the field's value contains the rune specified within the param.
2021-08-12 21:03:24 +02:00
func containsRune ( fl FieldLevel ) bool {
r , _ := utf8 . DecodeRuneInString ( fl . Param ( ) )
return strings . ContainsRune ( fl . Field ( ) . String ( ) , r )
}
2021-09-10 14:42:14 +02:00
// containsAny is the validation function for validating that the field's value contains any of the characters specified within the param.
2021-08-12 21:03:24 +02:00
func containsAny ( fl FieldLevel ) bool {
return strings . ContainsAny ( fl . Field ( ) . String ( ) , fl . Param ( ) )
}
2021-09-10 14:42:14 +02:00
// contains is the validation function for validating that the field's value contains the text specified within the param.
2021-08-12 21:03:24 +02:00
func contains ( fl FieldLevel ) bool {
return strings . Contains ( fl . Field ( ) . String ( ) , fl . Param ( ) )
}
2021-09-10 14:42:14 +02:00
// startsWith is the validation function for validating that the field's value starts with the text specified within the param.
2021-08-12 21:03:24 +02:00
func startsWith ( fl FieldLevel ) bool {
return strings . HasPrefix ( fl . Field ( ) . String ( ) , fl . Param ( ) )
}
2021-09-10 14:42:14 +02:00
// endsWith is the validation function for validating that the field's value ends with the text specified within the param.
2021-08-12 21:03:24 +02:00
func endsWith ( fl FieldLevel ) bool {
return strings . HasSuffix ( fl . Field ( ) . String ( ) , fl . Param ( ) )
}
2021-09-10 14:42:14 +02:00
// startsNotWith is the validation function for validating that the field's value does not start with the text specified within the param.
2021-08-12 21:03:24 +02:00
func startsNotWith ( fl FieldLevel ) bool {
return ! startsWith ( fl )
}
2021-09-10 14:42:14 +02:00
// endsNotWith is the validation function for validating that the field's value does not end with the text specified within the param.
2021-08-12 21:03:24 +02:00
func endsNotWith ( fl FieldLevel ) bool {
return ! endsWith ( fl )
}
2021-09-10 14:42:14 +02:00
// fieldContains is the validation function for validating if the current field's value contains the field specified by the param's value.
2021-08-12 21:03:24 +02:00
func fieldContains ( fl FieldLevel ) bool {
field := fl . Field ( )
currentField , _ , ok := fl . GetStructFieldOK ( )
if ! ok {
return false
}
return strings . Contains ( field . String ( ) , currentField . String ( ) )
}
2021-09-10 14:42:14 +02:00
// fieldExcludes is the validation function for validating if the current field's value excludes the field specified by the param's value.
2021-08-12 21:03:24 +02:00
func fieldExcludes ( fl FieldLevel ) bool {
field := fl . Field ( )
currentField , _ , ok := fl . GetStructFieldOK ( )
if ! ok {
return true
}
return ! strings . Contains ( field . String ( ) , currentField . String ( ) )
}
2021-09-10 14:42:14 +02:00
// 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.
2021-08-12 21:03:24 +02:00
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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && currentField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
t := currentField . Interface ( ) . ( time . Time )
fieldTime := field . Interface ( ) . ( time . Time )
return ! fieldTime . Equal ( t )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != currentField . Type ( ) {
return true
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String:
return field . String ( ) != currentField . String ( )
}
2021-09-10 14:42:14 +02:00
// isNe is the validation function for validating that the field's value does not equal the provided param value.
2021-08-12 21:03:24 +02:00
func isNe ( fl FieldLevel ) bool {
return ! isEq ( fl )
}
2023-05-01 11:02:44 +02:00
// isNeIgnoreCase is the validation function for validating that the field's string value does not equal the
2023-03-20 11:05:46 +01:00
// provided param value. The comparison is case-insensitive
func isNeIgnoreCase ( fl FieldLevel ) bool {
return ! isEqIgnoreCase ( fl )
}
2021-09-10 14:42:14 +02:00
// 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.
2021-08-12 21:03:24 +02:00
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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && topField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
fieldTime := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
topTime := topField . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return fieldTime . Before ( topTime ) || fieldTime . Equal ( topTime )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != topField . Type ( ) {
return false
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String:
return field . String ( ) <= topField . String ( )
}
2021-09-10 14:42:14 +02:00
// 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.
2021-08-12 21:03:24 +02:00
// 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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && topField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
fieldTime := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
topTime := topField . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return fieldTime . Before ( topTime )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != topField . Type ( ) {
return false
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String:
return field . String ( ) < topField . String ( )
}
2021-09-10 14:42:14 +02:00
// 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.
2021-08-12 21:03:24 +02:00
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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && topField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
fieldTime := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
topTime := topField . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return fieldTime . After ( topTime ) || fieldTime . Equal ( topTime )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != topField . Type ( ) {
return false
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String:
return field . String ( ) >= topField . String ( )
}
2021-09-10 14:42:14 +02:00
// 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.
2021-08-12 21:03:24 +02:00
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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && topField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
fieldTime := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
topTime := topField . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return fieldTime . After ( topTime )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != topField . Type ( ) {
return false
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String:
return field . String ( ) > topField . String ( )
}
2021-09-10 14:42:14 +02:00
// 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.
2021-08-12 21:03:24 +02:00
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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && topField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
t := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
fieldTime := topField . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return ! fieldTime . Equal ( t )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != topField . Type ( ) {
return true
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String:
return topField . String ( ) != field . String ( )
}
2021-09-10 14:42:14 +02:00
// 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.
2021-08-12 21:03:24 +02:00
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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && topField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
t := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
fieldTime := topField . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return fieldTime . Equal ( t )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != topField . Type ( ) {
return false
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String:
return topField . String ( ) == field . String ( )
}
2021-09-10 14:42:14 +02:00
// isEqField is the validation function for validating if the current field's value is equal to the field specified by the param's value.
2021-08-12 21:03:24 +02:00
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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && currentField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
t := currentField . Convert ( timeType ) . Interface ( ) . ( time . Time )
fieldTime := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return fieldTime . Equal ( t )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != currentField . Type ( ) {
return false
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String:
return field . String ( ) == currentField . String ( )
}
2021-09-10 14:42:14 +02:00
// isEq is the validation function for validating if the current field's value is equal to the param's value.
2021-08-12 21:03:24 +02:00
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
2023-11-27 14:15:03 +01:00
case reflect . Float32 :
p := asFloat32 ( param )
return field . Float ( ) == p
case reflect . Float64 :
p := asFloat64 ( param )
2021-08-12 21:03:24 +02:00
return field . Float ( ) == p
case reflect . Bool :
p := asBool ( param )
return field . Bool ( ) == p
}
panic ( fmt . Sprintf ( "Bad field type %T" , field . Interface ( ) ) )
}
2023-03-20 11:05:46 +01:00
// 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 ( ) ) )
}
2021-08-12 21:03:24 +02:00
// 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 ( ) )
}
2022-05-02 15:05:18 +02:00
// isPostcodeByIso3166Alpha2Field validates by field which represents for a value of country code in iso 3166 alpha 2
2021-08-12 21:03:24 +02:00
// 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 ( ) )
}
2021-09-10 14:42:14 +02:00
// isBase64 is the validation function for validating if the current field's value is a valid base 64.
2021-08-12 21:03:24 +02:00
func isBase64 ( fl FieldLevel ) bool {
return base64Regex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isBase64URL is the validation function for validating if the current field's value is a valid base64 URL safe string.
2021-08-12 21:03:24 +02:00
func isBase64URL ( fl FieldLevel ) bool {
return base64URLRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2023-03-20 11:05:46 +01:00
// 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 ( ) )
}
2021-09-10 14:42:14 +02:00
// isURI is the validation function for validating if the current field's value is a valid URI.
2021-08-12 21:03:24 +02:00
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 ( ) ) )
}
2024-03-11 11:12:06 +01:00
// 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
}
2021-09-10 14:42:14 +02:00
// isURL is the validation function for validating if the current field's value is a valid URL.
2021-08-12 21:03:24 +02:00
func isURL ( fl FieldLevel ) bool {
field := fl . Field ( )
switch field . Kind ( ) {
case reflect . String :
2024-03-11 11:12:06 +01:00
s := strings . ToLower ( field . String ( ) )
2021-08-12 21:03:24 +02:00
if len ( s ) == 0 {
return false
}
2024-03-11 11:12:06 +01:00
if isFileURL ( s ) {
return true
}
2023-06-05 10:15:05 +02:00
url , err := url . Parse ( s )
2021-08-12 21:03:24 +02:00
if err != nil || url . Scheme == "" {
return false
}
2023-06-05 10:15:05 +02:00
if url . Host == "" && url . Fragment == "" && url . Opaque == "" {
return false
}
2021-08-12 21:03:24 +02:00
return true
}
panic ( fmt . Sprintf ( "Bad field type %T" , field . Interface ( ) ) )
}
2023-03-20 11:05:46 +01:00
// 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 ( ) )
2023-06-05 10:15:05 +02:00
url , err := url . Parse ( s )
if err != nil || url . Host == "" {
return false
}
return url . Scheme == "http" || url . Scheme == "https"
2023-03-20 11:05:46 +01:00
}
panic ( fmt . Sprintf ( "Bad field type %T" , field . Interface ( ) ) )
}
2021-08-12 21:03:24 +02:00
// 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 ( ) ) )
}
2023-03-20 11:05:46 +01:00
// isFile is the validation function for validating if the current field's value is a valid existing file path.
2021-08-12 21:03:24 +02:00
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 ( ) ) )
}
2023-05-22 09:17:48 +02:00
// 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
}
2023-03-20 11:05:46 +01:00
// 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 ( )
2023-11-27 14:15:03 +01:00
// Not valid if it is a directory.
if isDir ( fl ) {
return false
}
2023-03-20 11:05:46 +01:00
// 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 ( ) ) )
}
2021-09-10 14:42:14 +02:00
// isE164 is the validation function for validating if the current field's value is a valid e.164 formatted phone number.
2021-08-12 21:03:24 +02:00
func isE164 ( fl FieldLevel ) bool {
return e164Regex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isEmail is the validation function for validating if the current field's value is a valid email address.
2021-08-12 21:03:24 +02:00
func isEmail ( fl FieldLevel ) bool {
return emailRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isHSLA is the validation function for validating if the current field's value is a valid HSLA color.
2021-08-12 21:03:24 +02:00
func isHSLA ( fl FieldLevel ) bool {
return hslaRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isHSL is the validation function for validating if the current field's value is a valid HSL color.
2021-08-12 21:03:24 +02:00
func isHSL ( fl FieldLevel ) bool {
return hslRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isRGBA is the validation function for validating if the current field's value is a valid RGBA color.
2021-08-12 21:03:24 +02:00
func isRGBA ( fl FieldLevel ) bool {
return rgbaRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isRGB is the validation function for validating if the current field's value is a valid RGB color.
2021-08-12 21:03:24 +02:00
func isRGB ( fl FieldLevel ) bool {
return rgbRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isHEXColor is the validation function for validating if the current field's value is a valid HEX color.
2021-08-12 21:03:24 +02:00
func isHEXColor ( fl FieldLevel ) bool {
return hexColorRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isHexadecimal is the validation function for validating if the current field's value is a valid hexadecimal.
2021-08-12 21:03:24 +02:00
func isHexadecimal ( fl FieldLevel ) bool {
return hexadecimalRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isNumber is the validation function for validating if the current field's value is a valid number.
2021-08-12 21:03:24 +02:00
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 ( ) )
}
}
2021-09-10 14:42:14 +02:00
// isNumeric is the validation function for validating if the current field's value is a valid numeric value.
2021-08-12 21:03:24 +02:00
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 ( ) )
}
}
2021-09-10 14:42:14 +02:00
// isAlphanum is the validation function for validating if the current field's value is a valid alphanumeric value.
2021-08-12 21:03:24 +02:00
func isAlphanum ( fl FieldLevel ) bool {
return alphaNumericRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isAlpha is the validation function for validating if the current field's value is a valid alpha value.
2021-08-12 21:03:24 +02:00
func isAlpha ( fl FieldLevel ) bool {
return alphaRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isAlphanumUnicode is the validation function for validating if the current field's value is a valid alphanumeric unicode value.
2021-08-12 21:03:24 +02:00
func isAlphanumUnicode ( fl FieldLevel ) bool {
return alphaUnicodeNumericRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isAlphaUnicode is the validation function for validating if the current field's value is a valid alpha unicode value.
2021-08-12 21:03:24 +02:00
func isAlphaUnicode ( fl FieldLevel ) bool {
return alphaUnicodeRegex . MatchString ( fl . Field ( ) . String ( ) )
}
2022-11-07 11:19:50 +01:00
// 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.
2021-09-10 14:42:14 +02:00
func isBoolean ( fl FieldLevel ) bool {
2022-11-07 11:19:50 +01:00
switch fl . Field ( ) . Kind ( ) {
case reflect . Bool :
return true
default :
_ , err := strconv . ParseBool ( fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
}
2021-08-12 21:03:24 +02:00
// isDefault is the opposite of required aka hasValue
func isDefault ( fl FieldLevel ) bool {
return ! hasValue ( fl )
}
2021-09-10 14:42:14 +02:00
// hasValue is the validation function for validating if the current field's value is not the default static value.
2021-08-12 21:03:24 +02:00
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
}
2023-11-27 14:15:03 +01:00
return field . IsValid ( ) && ! field . IsZero ( )
2021-08-12 21:03:24 +02:00
}
}
2023-05-01 11:02:44 +02:00
// requireCheckFieldKind is a func for check field kind
2021-08-12 21:03:24 +02:00
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
}
2023-11-27 14:15:03 +01:00
return field . IsValid ( ) && field . IsZero ( )
2021-08-12 21:03:24 +02:00
}
}
// requireCheckFieldValue is a func for check field value
2023-03-20 11:05:46 +01:00
func requireCheckFieldValue (
fl FieldLevel , param string , value string , defaultNotFoundValue bool ,
) bool {
2021-08-12 21:03:24 +02:00
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 )
2023-11-27 14:15:03 +01:00
case reflect . Float32 :
return field . Float ( ) == asFloat32 ( value )
case reflect . Float64 :
return field . Float ( ) == asFloat64 ( value )
2021-08-12 21:03:24 +02:00
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 )
}
2022-05-02 15:05:18 +02:00
// 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 ) {
2023-05-01 11:02:44 +02:00
return true
2022-05-02 15:05:18 +02:00
}
}
2023-05-01 11:02:44 +02:00
return ! hasValue ( fl )
2022-05-02 15:05:18 +02:00
}
2021-08-12 21:03:24 +02:00
// 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 )
}
2023-05-01 11:02:44 +02:00
// 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 )
}
2022-05-02 15:05:18 +02:00
// 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 ) {
2023-03-20 11:05:46 +01:00
return ! hasValue ( fl )
2022-05-02 15:05:18 +02:00
}
}
2023-03-20 11:05:46 +01:00
return true
2022-05-02 15:05:18 +02:00
}
2021-09-10 14:42:14 +02:00
// excludedWith is the validation function
2021-08-12 21:03:24 +02:00
// 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
}
2021-09-10 14:42:14 +02:00
// requiredWith is the validation function
2021-08-12 21:03:24 +02:00
// 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
}
2021-09-10 14:42:14 +02:00
// excludedWithAll is the validation function
2021-08-12 21:03:24 +02:00
// 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 )
}
2021-09-10 14:42:14 +02:00
// requiredWithAll is the validation function
2021-08-12 21:03:24 +02:00
// 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 )
}
2021-09-10 14:42:14 +02:00
// excludedWithout is the validation function
2021-08-12 21:03:24 +02:00
// 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
}
2021-09-10 14:42:14 +02:00
// requiredWithout is the validation function
2021-08-12 21:03:24 +02:00
// 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
}
2021-09-10 14:42:14 +02:00
// excludedWithoutAll is the validation function
2021-08-12 21:03:24 +02:00
// 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 )
}
2021-09-10 14:42:14 +02:00
// requiredWithoutAll is the validation function
2021-08-12 21:03:24 +02:00
// 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 )
}
2021-09-10 14:42:14 +02:00
// 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.
2021-08-12 21:03:24 +02:00
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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && currentField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
t := currentField . Convert ( timeType ) . Interface ( ) . ( time . Time )
fieldTime := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return fieldTime . After ( t ) || fieldTime . Equal ( t )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != currentField . Type ( ) {
return false
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String
return len ( field . String ( ) ) >= len ( currentField . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isGtField is the validation function for validating if the current field's value is greater than the field specified by the param's value.
2021-08-12 21:03:24 +02:00
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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && currentField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
t := currentField . Convert ( timeType ) . Interface ( ) . ( time . Time )
fieldTime := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return fieldTime . After ( t )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != currentField . Type ( ) {
return false
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String
return len ( field . String ( ) ) > len ( currentField . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isGte is the validation function for validating if the current field's value is greater than or equal to the param's value.
2021-08-12 21:03:24 +02:00
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
2023-11-27 14:15:03 +01:00
case reflect . Float32 :
p := asFloat32 ( param )
return field . Float ( ) >= p
case reflect . Float64 :
p := asFloat64 ( param )
2021-08-12 21:03:24 +02:00
return field . Float ( ) >= p
case reflect . Struct :
2022-05-02 15:05:18 +02:00
if field . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
now := time . Now ( ) . UTC ( )
2022-05-02 15:05:18 +02:00
t := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return t . After ( now ) || t . Equal ( now )
}
}
panic ( fmt . Sprintf ( "Bad field type %T" , field . Interface ( ) ) )
}
2021-09-10 14:42:14 +02:00
// isGt is the validation function for validating if the current field's value is greater than the param's value.
2021-08-12 21:03:24 +02:00
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
2023-11-27 14:15:03 +01:00
case reflect . Float32 :
p := asFloat32 ( param )
return field . Float ( ) > p
case reflect . Float64 :
p := asFloat64 ( param )
2021-08-12 21:03:24 +02:00
return field . Float ( ) > p
2023-11-27 14:15:03 +01:00
2021-08-12 21:03:24 +02:00
case reflect . Struct :
2022-05-02 15:05:18 +02:00
if field . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
return field . Convert ( timeType ) . Interface ( ) . ( time . Time ) . After ( time . Now ( ) . UTC ( ) )
2021-08-12 21:03:24 +02:00
}
}
panic ( fmt . Sprintf ( "Bad field type %T" , field . Interface ( ) ) )
}
2021-09-10 14:42:14 +02:00
// hasLengthOf is the validation function for validating if the current field's value is equal to the param's value.
2021-08-12 21:03:24 +02:00
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
2023-11-27 14:15:03 +01:00
case reflect . Float32 :
p := asFloat32 ( param )
return field . Float ( ) == p
case reflect . Float64 :
p := asFloat64 ( param )
2021-08-12 21:03:24 +02:00
return field . Float ( ) == p
}
panic ( fmt . Sprintf ( "Bad field type %T" , field . Interface ( ) ) )
}
2021-09-10 14:42:14 +02:00
// hasMinOf is the validation function for validating if the current field's value is greater than or equal to the param's value.
2021-08-12 21:03:24 +02:00
func hasMinOf ( fl FieldLevel ) bool {
return isGte ( fl )
}
2021-09-10 14:42:14 +02:00
// 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.
2021-08-12 21:03:24 +02:00
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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && currentField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
t := currentField . Convert ( timeType ) . Interface ( ) . ( time . Time )
fieldTime := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return fieldTime . Before ( t ) || fieldTime . Equal ( t )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != currentField . Type ( ) {
return false
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String
return len ( field . String ( ) ) <= len ( currentField . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isLtField is the validation function for validating if the current field's value is less than the field specified by the param's value.
2021-08-12 21:03:24 +02:00
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 ( )
2022-05-02 15:05:18 +02:00
if fieldType . ConvertibleTo ( timeType ) && currentField . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
t := currentField . Convert ( timeType ) . Interface ( ) . ( time . Time )
fieldTime := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return fieldTime . Before ( t )
}
2022-05-02 15:05:18 +02:00
// Not Same underlying type i.e. struct and time
if fieldType != currentField . Type ( ) {
return false
}
2021-08-12 21:03:24 +02:00
}
// default reflect.String
return len ( field . String ( ) ) < len ( currentField . String ( ) )
}
2021-09-10 14:42:14 +02:00
// isLte is the validation function for validating if the current field's value is less than or equal to the param's value.
2021-08-12 21:03:24 +02:00
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
2023-11-27 14:15:03 +01:00
case reflect . Float32 :
p := asFloat32 ( param )
return field . Float ( ) <= p
case reflect . Float64 :
p := asFloat64 ( param )
2021-08-12 21:03:24 +02:00
return field . Float ( ) <= p
case reflect . Struct :
2022-05-02 15:05:18 +02:00
if field . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
now := time . Now ( ) . UTC ( )
2022-05-02 15:05:18 +02:00
t := field . Convert ( timeType ) . Interface ( ) . ( time . Time )
2021-08-12 21:03:24 +02:00
return t . Before ( now ) || t . Equal ( now )
}
}
panic ( fmt . Sprintf ( "Bad field type %T" , field . Interface ( ) ) )
}
2021-09-10 14:42:14 +02:00
// isLt is the validation function for validating if the current field's value is less than the param's value.
2021-08-12 21:03:24 +02:00
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
2023-11-27 14:15:03 +01:00
case reflect . Float32 :
p := asFloat32 ( param )
return field . Float ( ) < p
case reflect . Float64 :
p := asFloat64 ( param )
2021-08-12 21:03:24 +02:00
return field . Float ( ) < p
case reflect . Struct :
2022-05-02 15:05:18 +02:00
if field . Type ( ) . ConvertibleTo ( timeType ) {
2021-08-12 21:03:24 +02:00
2022-05-02 15:05:18 +02:00
return field . Convert ( timeType ) . Interface ( ) . ( time . Time ) . Before ( time . Now ( ) . UTC ( ) )
2021-08-12 21:03:24 +02:00
}
}
panic ( fmt . Sprintf ( "Bad field type %T" , field . Interface ( ) ) )
}
2021-09-10 14:42:14 +02:00
// hasMaxOf is the validation function for validating if the current field's value is less than or equal to the param's value.
2021-08-12 21:03:24 +02:00
func hasMaxOf ( fl FieldLevel ) bool {
return isLte ( fl )
}
2021-09-10 14:42:14 +02:00
// isTCP4AddrResolvable is the validation function for validating if the field's value is a resolvable tcp4 address.
2021-08-12 21:03:24 +02:00
func isTCP4AddrResolvable ( fl FieldLevel ) bool {
if ! isIP4Addr ( fl ) {
return false
}
_ , err := net . ResolveTCPAddr ( "tcp4" , fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
// isTCP6AddrResolvable is the validation function for validating if the field's value is a resolvable tcp6 address.
2021-08-12 21:03:24 +02:00
func isTCP6AddrResolvable ( fl FieldLevel ) bool {
if ! isIP6Addr ( fl ) {
return false
}
_ , err := net . ResolveTCPAddr ( "tcp6" , fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
// isTCPAddrResolvable is the validation function for validating if the field's value is a resolvable tcp address.
2021-08-12 21:03:24 +02:00
func isTCPAddrResolvable ( fl FieldLevel ) bool {
if ! isIP4Addr ( fl ) && ! isIP6Addr ( fl ) {
return false
}
_ , err := net . ResolveTCPAddr ( "tcp" , fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
// isUDP4AddrResolvable is the validation function for validating if the field's value is a resolvable udp4 address.
2021-08-12 21:03:24 +02:00
func isUDP4AddrResolvable ( fl FieldLevel ) bool {
if ! isIP4Addr ( fl ) {
return false
}
_ , err := net . ResolveUDPAddr ( "udp4" , fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
// isUDP6AddrResolvable is the validation function for validating if the field's value is a resolvable udp6 address.
2021-08-12 21:03:24 +02:00
func isUDP6AddrResolvable ( fl FieldLevel ) bool {
if ! isIP6Addr ( fl ) {
return false
}
_ , err := net . ResolveUDPAddr ( "udp6" , fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
// isUDPAddrResolvable is the validation function for validating if the field's value is a resolvable udp address.
2021-08-12 21:03:24 +02:00
func isUDPAddrResolvable ( fl FieldLevel ) bool {
if ! isIP4Addr ( fl ) && ! isIP6Addr ( fl ) {
return false
}
_ , err := net . ResolveUDPAddr ( "udp" , fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
// isIP4AddrResolvable is the validation function for validating if the field's value is a resolvable ip4 address.
2021-08-12 21:03:24 +02:00
func isIP4AddrResolvable ( fl FieldLevel ) bool {
if ! isIPv4 ( fl ) {
return false
}
_ , err := net . ResolveIPAddr ( "ip4" , fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
// isIP6AddrResolvable is the validation function for validating if the field's value is a resolvable ip6 address.
2021-08-12 21:03:24 +02:00
func isIP6AddrResolvable ( fl FieldLevel ) bool {
if ! isIPv6 ( fl ) {
return false
}
_ , err := net . ResolveIPAddr ( "ip6" , fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
// isIPAddrResolvable is the validation function for validating if the field's value is a resolvable ip address.
2021-08-12 21:03:24 +02:00
func isIPAddrResolvable ( fl FieldLevel ) bool {
if ! isIP ( fl ) {
return false
}
_ , err := net . ResolveIPAddr ( "ip" , fl . Field ( ) . String ( ) )
return err == nil
}
2021-09-10 14:42:14 +02:00
// isUnixAddrResolvable is the validation function for validating if the field's value is a resolvable unix address.
2021-08-12 21:03:24 +02:00
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 )
}
2023-03-20 11:05:46 +01:00
// isDir is the validation function for validating if the current field's value is a valid existing directory.
2021-08-12 21:03:24 +02:00
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 ( ) ) )
}
2023-03-20 11:05:46 +01:00
// 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 ( ) ) )
}
2021-08-12 21:03:24 +02:00
// 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 ( )
2023-06-05 10:15:05 +02:00
switch field . Kind ( ) {
case reflect . String :
2021-08-12 21:03:24 +02:00
val := field . String ( )
return json . Valid ( [ ] byte ( val ) )
2023-06-05 10:15:05 +02:00
case reflect . Slice :
fieldType := field . Type ( )
if fieldType . ConvertibleTo ( byteSliceType ) {
b := field . Convert ( byteSliceType ) . Interface ( ) . ( [ ] byte )
return json . Valid ( b )
}
2021-08-12 21:03:24 +02:00
}
panic ( fmt . Sprintf ( "Bad field type %T" , field . Interface ( ) ) )
}
2021-09-10 14:42:14 +02:00
// 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 ( ) )
}
2021-08-12 21:03:24 +02:00
// 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.
2023-03-20 11:05:46 +01:00
if portNum , err := strconv . ParseInt (
port , 10 , 32 ,
) ; err != nil || portNum > 65535 || portNum < 1 {
2021-08-12 21:03:24 +02:00
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 ]
}
2023-05-01 11:02:44 +02:00
// isIso3166Alpha3 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-3 country code.
2021-08-12 21:03:24 +02:00
func isIso3166Alpha3 ( fl FieldLevel ) bool {
val := fl . Field ( ) . String ( )
return iso3166_1_alpha3 [ val ]
}
2023-05-01 11:02:44 +02:00
// isIso3166AlphaNumeric is the validation function for validating if the current field's value is a valid iso3166-1 alpha-numeric country code.
2021-08-12 21:03:24 +02:00
func isIso3166AlphaNumeric ( fl FieldLevel ) bool {
field := fl . Field ( )
var code int
switch field . Kind ( ) {
2022-05-02 15:05:18 +02:00
case reflect . String :
i , err := strconv . Atoi ( field . String ( ) )
if err != nil {
return false
}
code = i % 1000
2021-08-12 21:03:24 +02:00
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 ]
}
2021-09-10 14:42:14 +02:00
// 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 ]
}
2021-08-12 21:03:24 +02:00
// 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 )
}
2022-05-02 15:05:18 +02:00
// 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 )
}
2023-03-20 11:05:46 +01:00
// 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 )
}
2022-05-02 15:05:18 +02:00
// 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 )
}
2023-03-20 11:05:46 +01:00
// 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 )
}
2023-11-27 14:15:03 +01:00
// 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 )
}
2022-05-02 15:05:18 +02:00
// 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
}
2023-03-20 11:05:46 +01:00
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 ( ) ) )
2022-05-02 15:05:18 +02:00
}
2023-03-20 11:05:46 +01:00
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 )
2022-05-02 15:05:18 +02:00
}