forked from ebhomengo/niki
1
0
Fork 0
niki/vendor/github.com/asaskevich/govalidator/validator.go

2684 lines
42 KiB
Go
Raw Normal View History

2024-02-18 10:42:21 +00:00
// Package govalidator is package of validators and sanitizers for strings, structs and collections.
2024-02-18 10:42:21 +00:00
package govalidator
import (
"bytes"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"net"
"net/url"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
)
var (
fieldsRequiredByDefault bool
2024-02-18 10:42:21 +00:00
nilPtrAllowedByRequired = false
notNumberRegexp = regexp.MustCompile("[^0-9]+")
whiteSpacesAndMinus = regexp.MustCompile(`[\s-]+`)
paramsRegexp = regexp.MustCompile(`\(.*\)$`)
2024-02-18 10:42:21 +00:00
)
const maxURLRuneCount = 2083
2024-02-18 10:42:21 +00:00
const minURLRuneCount = 3
2024-02-18 10:42:21 +00:00
const RF3339WithoutZone = "2006-01-02T15:04:05"
// SetFieldsRequiredByDefault causes validation to fail when struct fields
2024-02-18 10:42:21 +00:00
// do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`).
2024-02-18 10:42:21 +00:00
// This struct definition will fail govalidator.ValidateStruct() (and the field values do not matter):
2024-06-14 08:41:36 +00:00
//
2024-06-14 08:41:36 +00:00
// type exampleStruct struct {
2024-06-14 08:41:36 +00:00
// Name string ``
2024-06-14 08:41:36 +00:00
// Email string `valid:"email"`
2024-06-14 08:41:36 +00:00
//
2024-02-18 10:42:21 +00:00
// This, however, will only fail when Email is empty or an invalid email address:
2024-06-14 08:41:36 +00:00
//
2024-06-14 08:41:36 +00:00
// type exampleStruct2 struct {
2024-06-14 08:41:36 +00:00
// Name string `valid:"-"`
2024-06-14 08:41:36 +00:00
// Email string `valid:"email"`
2024-06-14 08:41:36 +00:00
//
2024-02-18 10:42:21 +00:00
// Lastly, this will only fail when Email is an invalid email address but not when it's empty:
2024-06-14 08:41:36 +00:00
//
2024-06-14 08:41:36 +00:00
// type exampleStruct2 struct {
2024-06-14 08:41:36 +00:00
// Name string `valid:"-"`
2024-06-14 08:41:36 +00:00
// Email string `valid:"email,optional"`
2024-02-18 10:42:21 +00:00
func SetFieldsRequiredByDefault(value bool) {
2024-02-18 10:42:21 +00:00
fieldsRequiredByDefault = value
2024-02-18 10:42:21 +00:00
}
// SetNilPtrAllowedByRequired causes validation to pass for nil ptrs when a field is set to required.
2024-02-18 10:42:21 +00:00
// The validation will still reject ptr fields in their zero value state. Example with this enabled:
2024-06-14 08:41:36 +00:00
//
2024-06-14 08:41:36 +00:00
// type exampleStruct struct {
2024-06-14 08:41:36 +00:00
// Name *string `valid:"required"`
2024-06-14 08:41:36 +00:00
//
2024-02-18 10:42:21 +00:00
// With `Name` set to "", this will be considered invalid input and will cause a validation error.
2024-02-18 10:42:21 +00:00
// With `Name` set to nil, this will be considered valid by validation.
2024-02-18 10:42:21 +00:00
// By default this is disabled.
2024-02-18 10:42:21 +00:00
func SetNilPtrAllowedByRequired(value bool) {
2024-02-18 10:42:21 +00:00
nilPtrAllowedByRequired = value
2024-02-18 10:42:21 +00:00
}
// IsEmail check if the string is an email.
2024-02-18 10:42:21 +00:00
func IsEmail(str string) bool {
2024-02-18 10:42:21 +00:00
// TODO uppercase letters are not supported
2024-02-18 10:42:21 +00:00
return rxEmail.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsExistingEmail check if the string is an email of existing domain
2024-02-18 10:42:21 +00:00
func IsExistingEmail(email string) bool {
if len(email) < 6 || len(email) > 254 {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
at := strings.LastIndex(email, "@")
2024-02-18 10:42:21 +00:00
if at <= 0 || at > len(email)-3 {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
user := email[:at]
2024-02-18 10:42:21 +00:00
host := email[at+1:]
2024-02-18 10:42:21 +00:00
if len(user) > 64 {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if userDotRegexp.MatchString(user) || !userRegexp.MatchString(user) || !hostRegexp.MatchString(host) {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
switch host {
2024-02-18 10:42:21 +00:00
case "localhost", "example.com":
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if _, err := net.LookupMX(host); err != nil {
2024-02-18 10:42:21 +00:00
if _, err := net.LookupIP(host); err != nil {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
return true
2024-02-18 10:42:21 +00:00
}
// IsURL check if the string is an URL.
2024-02-18 10:42:21 +00:00
func IsURL(str string) bool {
2024-02-18 10:42:21 +00:00
if str == "" || utf8.RuneCountInString(str) >= maxURLRuneCount || len(str) <= minURLRuneCount || strings.HasPrefix(str, ".") {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
strTemp := str
2024-02-18 10:42:21 +00:00
if strings.Contains(str, ":") && !strings.Contains(str, "://") {
2024-02-18 10:42:21 +00:00
// support no indicated urlscheme but with colon for port number
2024-02-18 10:42:21 +00:00
// http:// is appended so url.Parse will succeed, strTemp used so it does not impact rxURL.MatchString
2024-02-18 10:42:21 +00:00
strTemp = "http://" + str
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
u, err := url.Parse(strTemp)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if strings.HasPrefix(u.Host, ".") {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxURL.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsRequestURL check if the string rawurl, assuming
2024-02-18 10:42:21 +00:00
// it was received in an HTTP request, is a valid
2024-02-18 10:42:21 +00:00
// URL confirm to RFC 3986
2024-02-18 10:42:21 +00:00
func IsRequestURL(rawurl string) bool {
2024-02-18 10:42:21 +00:00
url, err := url.ParseRequestURI(rawurl)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return false //Couldn't even parse the rawurl
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if len(url.Scheme) == 0 {
2024-02-18 10:42:21 +00:00
return false //No Scheme found
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
// IsRequestURI check if the string rawurl, assuming
2024-02-18 10:42:21 +00:00
// it was received in an HTTP request, is an
2024-02-18 10:42:21 +00:00
// absolute URI or an absolute path.
2024-02-18 10:42:21 +00:00
func IsRequestURI(rawurl string) bool {
2024-02-18 10:42:21 +00:00
_, err := url.ParseRequestURI(rawurl)
2024-02-18 10:42:21 +00:00
return err == nil
2024-02-18 10:42:21 +00:00
}
// IsAlpha check if the string contains only letters (a-zA-Z). Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsAlpha(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxAlpha.MatchString(str)
2024-02-18 10:42:21 +00:00
}
2024-06-14 08:41:36 +00:00
// IsUTFLetter check if the string contains only unicode letter characters.
2024-06-14 08:41:36 +00:00
// Similar to IsAlpha but for all languages. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsUTFLetter(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
for _, c := range str {
2024-02-18 10:42:21 +00:00
if !unicode.IsLetter(c) {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return true
}
// IsAlphanumeric check if the string contains only letters and numbers. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsAlphanumeric(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxAlphanumeric.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsUTFLetterNumeric check if the string contains only unicode letters and numbers. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsUTFLetterNumeric(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
for _, c := range str {
2024-02-18 10:42:21 +00:00
if !unicode.IsLetter(c) && !unicode.IsNumber(c) { //letters && numbers are ok
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return true
}
// IsNumeric check if the string contains only numbers. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsNumeric(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxNumeric.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsUTFNumeric check if the string contains only unicode numbers of any kind.
2024-02-18 10:42:21 +00:00
// Numbers can be 0-9 but also Fractions ¾,Roman Ⅸ and Hangzhou 〩. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsUTFNumeric(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if strings.IndexAny(str, "+-") > 0 {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if len(str) > 1 {
2024-02-18 10:42:21 +00:00
str = strings.TrimPrefix(str, "-")
2024-02-18 10:42:21 +00:00
str = strings.TrimPrefix(str, "+")
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
for _, c := range str {
2024-02-18 10:42:21 +00:00
if !unicode.IsNumber(c) { //numbers && minus sign are ok
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return true
}
// IsUTFDigit check if the string contains only unicode radix-10 decimal digits. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsUTFDigit(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if strings.IndexAny(str, "+-") > 0 {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if len(str) > 1 {
2024-02-18 10:42:21 +00:00
str = strings.TrimPrefix(str, "-")
2024-02-18 10:42:21 +00:00
str = strings.TrimPrefix(str, "+")
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
for _, c := range str {
2024-02-18 10:42:21 +00:00
if !unicode.IsDigit(c) { //digits && minus sign are ok
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return true
}
// IsHexadecimal check if the string is a hexadecimal number.
2024-02-18 10:42:21 +00:00
func IsHexadecimal(str string) bool {
2024-02-18 10:42:21 +00:00
return rxHexadecimal.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsHexcolor check if the string is a hexadecimal color.
2024-02-18 10:42:21 +00:00
func IsHexcolor(str string) bool {
2024-02-18 10:42:21 +00:00
return rxHexcolor.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsRGBcolor check if the string is a valid RGB color in form rgb(RRR, GGG, BBB).
2024-02-18 10:42:21 +00:00
func IsRGBcolor(str string) bool {
2024-02-18 10:42:21 +00:00
return rxRGBcolor.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsLowerCase check if the string is lowercase. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsLowerCase(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return str == strings.ToLower(str)
2024-02-18 10:42:21 +00:00
}
// IsUpperCase check if the string is uppercase. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsUpperCase(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return str == strings.ToUpper(str)
2024-02-18 10:42:21 +00:00
}
// HasLowerCase check if the string contains at least 1 lowercase. Empty string is valid.
2024-02-18 10:42:21 +00:00
func HasLowerCase(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxHasLowerCase.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// HasUpperCase check if the string contians as least 1 uppercase. Empty string is valid.
2024-02-18 10:42:21 +00:00
func HasUpperCase(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxHasUpperCase.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsInt check if the string is an integer. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsInt(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxInt.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsFloat check if the string is a float.
2024-02-18 10:42:21 +00:00
func IsFloat(str string) bool {
2024-02-18 10:42:21 +00:00
return str != "" && rxFloat.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsDivisibleBy check if the string is a number that's divisible by another.
2024-02-18 10:42:21 +00:00
// If second argument is not valid integer or zero, it's return false.
2024-02-18 10:42:21 +00:00
// Otherwise, if first argument is not valid integer or zero, it's return true (Invalid string converts to zero).
2024-02-18 10:42:21 +00:00
func IsDivisibleBy(str, num string) bool {
2024-02-18 10:42:21 +00:00
f, _ := ToFloat(str)
2024-02-18 10:42:21 +00:00
p := int64(f)
2024-02-18 10:42:21 +00:00
q, _ := ToInt(num)
2024-02-18 10:42:21 +00:00
if q == 0 {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return (p == 0) || (p%q == 0)
2024-02-18 10:42:21 +00:00
}
// IsNull check if the string is null.
2024-02-18 10:42:21 +00:00
func IsNull(str string) bool {
2024-02-18 10:42:21 +00:00
return len(str) == 0
2024-02-18 10:42:21 +00:00
}
// IsNotNull check if the string is not null.
2024-02-18 10:42:21 +00:00
func IsNotNull(str string) bool {
2024-02-18 10:42:21 +00:00
return !IsNull(str)
2024-02-18 10:42:21 +00:00
}
// HasWhitespaceOnly checks the string only contains whitespace
2024-02-18 10:42:21 +00:00
func HasWhitespaceOnly(str string) bool {
2024-02-18 10:42:21 +00:00
return len(str) > 0 && rxHasWhitespaceOnly.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// HasWhitespace checks if the string contains any whitespace
2024-02-18 10:42:21 +00:00
func HasWhitespace(str string) bool {
2024-02-18 10:42:21 +00:00
return len(str) > 0 && rxHasWhitespace.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsByteLength check if the string's length (in bytes) falls in a range.
2024-02-18 10:42:21 +00:00
func IsByteLength(str string, min, max int) bool {
2024-02-18 10:42:21 +00:00
return len(str) >= min && len(str) <= max
2024-02-18 10:42:21 +00:00
}
// IsUUIDv3 check if the string is a UUID version 3.
2024-02-18 10:42:21 +00:00
func IsUUIDv3(str string) bool {
2024-02-18 10:42:21 +00:00
return rxUUID3.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsUUIDv4 check if the string is a UUID version 4.
2024-02-18 10:42:21 +00:00
func IsUUIDv4(str string) bool {
2024-02-18 10:42:21 +00:00
return rxUUID4.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsUUIDv5 check if the string is a UUID version 5.
2024-02-18 10:42:21 +00:00
func IsUUIDv5(str string) bool {
2024-02-18 10:42:21 +00:00
return rxUUID5.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsUUID check if the string is a UUID (version 3, 4 or 5).
2024-02-18 10:42:21 +00:00
func IsUUID(str string) bool {
2024-02-18 10:42:21 +00:00
return rxUUID.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsCreditCard check if the string is a credit card.
2024-02-18 10:42:21 +00:00
func IsCreditCard(str string) bool {
2024-02-18 10:42:21 +00:00
sanitized := notNumberRegexp.ReplaceAllString(str, "")
2024-02-18 10:42:21 +00:00
if !rxCreditCard.MatchString(sanitized) {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
var sum int64
2024-02-18 10:42:21 +00:00
var digit string
2024-02-18 10:42:21 +00:00
var tmpNum int64
2024-02-18 10:42:21 +00:00
var shouldDouble bool
2024-02-18 10:42:21 +00:00
for i := len(sanitized) - 1; i >= 0; i-- {
2024-02-18 10:42:21 +00:00
digit = sanitized[i:(i + 1)]
2024-02-18 10:42:21 +00:00
tmpNum, _ = ToInt(digit)
2024-02-18 10:42:21 +00:00
if shouldDouble {
2024-02-18 10:42:21 +00:00
tmpNum *= 2
2024-02-18 10:42:21 +00:00
if tmpNum >= 10 {
2024-02-18 10:42:21 +00:00
sum += ((tmpNum % 10) + 1)
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
sum += tmpNum
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
sum += tmpNum
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
shouldDouble = !shouldDouble
2024-02-18 10:42:21 +00:00
}
return sum%10 == 0
2024-02-18 10:42:21 +00:00
}
// IsISBN10 check if the string is an ISBN version 10.
2024-02-18 10:42:21 +00:00
func IsISBN10(str string) bool {
2024-02-18 10:42:21 +00:00
return IsISBN(str, 10)
2024-02-18 10:42:21 +00:00
}
// IsISBN13 check if the string is an ISBN version 13.
2024-02-18 10:42:21 +00:00
func IsISBN13(str string) bool {
2024-02-18 10:42:21 +00:00
return IsISBN(str, 13)
2024-02-18 10:42:21 +00:00
}
// IsISBN check if the string is an ISBN (version 10 or 13).
2024-02-18 10:42:21 +00:00
// If version value is not equal to 10 or 13, it will be check both variants.
2024-02-18 10:42:21 +00:00
func IsISBN(str string, version int) bool {
2024-02-18 10:42:21 +00:00
sanitized := whiteSpacesAndMinus.ReplaceAllString(str, "")
2024-02-18 10:42:21 +00:00
var checksum int32
2024-02-18 10:42:21 +00:00
var i int32
2024-02-18 10:42:21 +00:00
if version == 10 {
2024-02-18 10:42:21 +00:00
if !rxISBN10.MatchString(sanitized) {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
for i = 0; i < 9; i++ {
2024-02-18 10:42:21 +00:00
checksum += (i + 1) * int32(sanitized[i]-'0')
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if sanitized[9] == 'X' {
2024-02-18 10:42:21 +00:00
checksum += 10 * 10
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
checksum += 10 * int32(sanitized[9]-'0')
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if checksum%11 == 0 {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
} else if version == 13 {
2024-02-18 10:42:21 +00:00
if !rxISBN13.MatchString(sanitized) {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
factor := []int32{1, 3}
2024-02-18 10:42:21 +00:00
for i = 0; i < 12; i++ {
2024-02-18 10:42:21 +00:00
checksum += factor[i%2] * int32(sanitized[i]-'0')
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return (int32(sanitized[12]-'0'))-((10-(checksum%10))%10) == 0
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return IsISBN(str, 10) || IsISBN(str, 13)
2024-02-18 10:42:21 +00:00
}
// IsJSON check if the string is valid JSON (note: uses json.Unmarshal).
2024-02-18 10:42:21 +00:00
func IsJSON(str string) bool {
2024-02-18 10:42:21 +00:00
var js json.RawMessage
2024-02-18 10:42:21 +00:00
return json.Unmarshal([]byte(str), &js) == nil
2024-02-18 10:42:21 +00:00
}
// IsMultibyte check if the string contains one or more multibyte chars. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsMultibyte(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxMultibyte.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsASCII check if the string contains ASCII chars only. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsASCII(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxASCII.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsPrintableASCII check if the string contains printable ASCII chars only. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsPrintableASCII(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxPrintableASCII.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsFullWidth check if the string contains any full-width chars. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsFullWidth(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxFullWidth.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsHalfWidth check if the string contains any half-width chars. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsHalfWidth(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxHalfWidth.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsVariableWidth check if the string contains a mixture of full and half-width chars. Empty string is valid.
2024-02-18 10:42:21 +00:00
func IsVariableWidth(str string) bool {
2024-02-18 10:42:21 +00:00
if IsNull(str) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxHalfWidth.MatchString(str) && rxFullWidth.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsBase64 check if a string is base64 encoded.
2024-02-18 10:42:21 +00:00
func IsBase64(str string) bool {
2024-02-18 10:42:21 +00:00
return rxBase64.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsFilePath check is a string is Win or Unix file path and returns it's type.
2024-02-18 10:42:21 +00:00
func IsFilePath(str string) (bool, int) {
2024-02-18 10:42:21 +00:00
if rxWinPath.MatchString(str) {
2024-02-18 10:42:21 +00:00
//check windows path limit see:
2024-02-18 10:42:21 +00:00
// http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath
2024-02-18 10:42:21 +00:00
if len(str[3:]) > 32767 {
2024-02-18 10:42:21 +00:00
return false, Win
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return true, Win
2024-02-18 10:42:21 +00:00
} else if rxUnixPath.MatchString(str) {
2024-02-18 10:42:21 +00:00
return true, Unix
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false, Unknown
2024-02-18 10:42:21 +00:00
}
// IsDataURI checks if a string is base64 encoded data URI such as an image
2024-02-18 10:42:21 +00:00
func IsDataURI(str string) bool {
2024-02-18 10:42:21 +00:00
dataURI := strings.Split(str, ",")
2024-02-18 10:42:21 +00:00
if !rxDataURI.MatchString(dataURI[0]) {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return IsBase64(dataURI[1])
2024-02-18 10:42:21 +00:00
}
// IsMagnetURI checks if a string is valid magnet URI
2024-02-18 10:42:21 +00:00
func IsMagnetURI(str string) bool {
2024-02-18 10:42:21 +00:00
return rxMagnetURI.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsISO3166Alpha2 checks if a string is valid two-letter country code
2024-02-18 10:42:21 +00:00
func IsISO3166Alpha2(str string) bool {
2024-02-18 10:42:21 +00:00
for _, entry := range ISO3166List {
2024-02-18 10:42:21 +00:00
if str == entry.Alpha2Code {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
// IsISO3166Alpha3 checks if a string is valid three-letter country code
2024-02-18 10:42:21 +00:00
func IsISO3166Alpha3(str string) bool {
2024-02-18 10:42:21 +00:00
for _, entry := range ISO3166List {
2024-02-18 10:42:21 +00:00
if str == entry.Alpha3Code {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
// IsISO693Alpha2 checks if a string is valid two-letter language code
2024-02-18 10:42:21 +00:00
func IsISO693Alpha2(str string) bool {
2024-02-18 10:42:21 +00:00
for _, entry := range ISO693List {
2024-02-18 10:42:21 +00:00
if str == entry.Alpha2Code {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
// IsISO693Alpha3b checks if a string is valid three-letter language code
2024-02-18 10:42:21 +00:00
func IsISO693Alpha3b(str string) bool {
2024-02-18 10:42:21 +00:00
for _, entry := range ISO693List {
2024-02-18 10:42:21 +00:00
if str == entry.Alpha3bCode {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
// IsDNSName will validate the given string as a DNS name
2024-02-18 10:42:21 +00:00
func IsDNSName(str string) bool {
2024-02-18 10:42:21 +00:00
if str == "" || len(strings.Replace(str, ".", "", -1)) > 255 {
2024-02-18 10:42:21 +00:00
// constraints already violated
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return !IsIP(str) && rxDNSName.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsHash checks if a string is a hash of type algorithm.
2024-02-18 10:42:21 +00:00
// Algorithm is one of ['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128', 'tiger160', 'tiger192', 'crc32', 'crc32b']
2024-02-18 10:42:21 +00:00
func IsHash(str string, algorithm string) bool {
2024-02-18 10:42:21 +00:00
len := "0"
2024-02-18 10:42:21 +00:00
algo := strings.ToLower(algorithm)
if algo == "crc32" || algo == "crc32b" {
2024-02-18 10:42:21 +00:00
len = "8"
2024-02-18 10:42:21 +00:00
} else if algo == "md5" || algo == "md4" || algo == "ripemd128" || algo == "tiger128" {
2024-02-18 10:42:21 +00:00
len = "32"
2024-02-18 10:42:21 +00:00
} else if algo == "sha1" || algo == "ripemd160" || algo == "tiger160" {
2024-02-18 10:42:21 +00:00
len = "40"
2024-02-18 10:42:21 +00:00
} else if algo == "tiger192" {
2024-02-18 10:42:21 +00:00
len = "48"
2024-02-18 10:42:21 +00:00
} else if algo == "sha256" {
2024-02-18 10:42:21 +00:00
len = "64"
2024-02-18 10:42:21 +00:00
} else if algo == "sha384" {
2024-02-18 10:42:21 +00:00
len = "96"
2024-02-18 10:42:21 +00:00
} else if algo == "sha512" {
2024-02-18 10:42:21 +00:00
len = "128"
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
return Matches(str, "^[a-f0-9]{"+len+"}$")
2024-02-18 10:42:21 +00:00
}
// IsSHA512 checks is a string is a SHA512 hash. Alias for `IsHash(str, "sha512")`
2024-02-18 10:42:21 +00:00
func IsSHA512(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "sha512")
2024-02-18 10:42:21 +00:00
}
// IsSHA384 checks is a string is a SHA384 hash. Alias for `IsHash(str, "sha384")`
2024-02-18 10:42:21 +00:00
func IsSHA384(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "sha384")
2024-02-18 10:42:21 +00:00
}
// IsSHA256 checks is a string is a SHA256 hash. Alias for `IsHash(str, "sha256")`
2024-02-18 10:42:21 +00:00
func IsSHA256(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "sha256")
2024-02-18 10:42:21 +00:00
}
// IsTiger192 checks is a string is a Tiger192 hash. Alias for `IsHash(str, "tiger192")`
2024-02-18 10:42:21 +00:00
func IsTiger192(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "tiger192")
2024-02-18 10:42:21 +00:00
}
// IsTiger160 checks is a string is a Tiger160 hash. Alias for `IsHash(str, "tiger160")`
2024-02-18 10:42:21 +00:00
func IsTiger160(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "tiger160")
2024-02-18 10:42:21 +00:00
}
// IsRipeMD160 checks is a string is a RipeMD160 hash. Alias for `IsHash(str, "ripemd160")`
2024-02-18 10:42:21 +00:00
func IsRipeMD160(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "ripemd160")
2024-02-18 10:42:21 +00:00
}
// IsSHA1 checks is a string is a SHA-1 hash. Alias for `IsHash(str, "sha1")`
2024-02-18 10:42:21 +00:00
func IsSHA1(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "sha1")
2024-02-18 10:42:21 +00:00
}
// IsTiger128 checks is a string is a Tiger128 hash. Alias for `IsHash(str, "tiger128")`
2024-02-18 10:42:21 +00:00
func IsTiger128(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "tiger128")
2024-02-18 10:42:21 +00:00
}
// IsRipeMD128 checks is a string is a RipeMD128 hash. Alias for `IsHash(str, "ripemd128")`
2024-02-18 10:42:21 +00:00
func IsRipeMD128(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "ripemd128")
2024-02-18 10:42:21 +00:00
}
// IsCRC32 checks is a string is a CRC32 hash. Alias for `IsHash(str, "crc32")`
2024-02-18 10:42:21 +00:00
func IsCRC32(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "crc32")
2024-02-18 10:42:21 +00:00
}
// IsCRC32b checks is a string is a CRC32b hash. Alias for `IsHash(str, "crc32b")`
2024-02-18 10:42:21 +00:00
func IsCRC32b(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "crc32b")
2024-02-18 10:42:21 +00:00
}
// IsMD5 checks is a string is a MD5 hash. Alias for `IsHash(str, "md5")`
2024-02-18 10:42:21 +00:00
func IsMD5(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "md5")
2024-02-18 10:42:21 +00:00
}
// IsMD4 checks is a string is a MD4 hash. Alias for `IsHash(str, "md4")`
2024-02-18 10:42:21 +00:00
func IsMD4(str string) bool {
2024-02-18 10:42:21 +00:00
return IsHash(str, "md4")
2024-02-18 10:42:21 +00:00
}
// IsDialString validates the given string for usage with the various Dial() functions
2024-02-18 10:42:21 +00:00
func IsDialString(str string) bool {
2024-02-18 10:42:21 +00:00
if h, p, err := net.SplitHostPort(str); err == nil && h != "" && p != "" && (IsDNSName(h) || IsIP(h)) && IsPort(p) {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
return false
2024-02-18 10:42:21 +00:00
}
// IsIP checks if a string is either IP version 4 or 6. Alias for `net.ParseIP`
2024-02-18 10:42:21 +00:00
func IsIP(str string) bool {
2024-02-18 10:42:21 +00:00
return net.ParseIP(str) != nil
2024-02-18 10:42:21 +00:00
}
// IsPort checks if a string represents a valid port
2024-02-18 10:42:21 +00:00
func IsPort(str string) bool {
2024-02-18 10:42:21 +00:00
if i, err := strconv.Atoi(str); err == nil && i > 0 && i < 65536 {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
// IsIPv4 check if the string is an IP version 4.
2024-02-18 10:42:21 +00:00
func IsIPv4(str string) bool {
2024-02-18 10:42:21 +00:00
ip := net.ParseIP(str)
2024-02-18 10:42:21 +00:00
return ip != nil && strings.Contains(str, ".")
2024-02-18 10:42:21 +00:00
}
// IsIPv6 check if the string is an IP version 6.
2024-02-18 10:42:21 +00:00
func IsIPv6(str string) bool {
2024-02-18 10:42:21 +00:00
ip := net.ParseIP(str)
2024-02-18 10:42:21 +00:00
return ip != nil && strings.Contains(str, ":")
2024-02-18 10:42:21 +00:00
}
// IsCIDR check if the string is an valid CIDR notiation (IPV4 & IPV6)
2024-02-18 10:42:21 +00:00
func IsCIDR(str string) bool {
2024-02-18 10:42:21 +00:00
_, _, err := net.ParseCIDR(str)
2024-02-18 10:42:21 +00:00
return err == nil
2024-02-18 10:42:21 +00:00
}
// IsMAC check if a string is valid MAC address.
2024-02-18 10:42:21 +00:00
// Possible MAC formats:
2024-02-18 10:42:21 +00:00
// 01:23:45:67:89:ab
2024-02-18 10:42:21 +00:00
// 01:23:45:67:89:ab:cd:ef
2024-02-18 10:42:21 +00:00
// 01-23-45-67-89-ab
2024-02-18 10:42:21 +00:00
// 01-23-45-67-89-ab-cd-ef
2024-02-18 10:42:21 +00:00
// 0123.4567.89ab
2024-02-18 10:42:21 +00:00
// 0123.4567.89ab.cdef
2024-02-18 10:42:21 +00:00
func IsMAC(str string) bool {
2024-02-18 10:42:21 +00:00
_, err := net.ParseMAC(str)
2024-02-18 10:42:21 +00:00
return err == nil
2024-02-18 10:42:21 +00:00
}
// IsHost checks if the string is a valid IP (both v4 and v6) or a valid DNS name
2024-02-18 10:42:21 +00:00
func IsHost(str string) bool {
2024-02-18 10:42:21 +00:00
return IsIP(str) || IsDNSName(str)
2024-02-18 10:42:21 +00:00
}
// IsMongoID check if the string is a valid hex-encoded representation of a MongoDB ObjectId.
2024-02-18 10:42:21 +00:00
func IsMongoID(str string) bool {
2024-02-18 10:42:21 +00:00
return rxHexadecimal.MatchString(str) && (len(str) == 24)
2024-02-18 10:42:21 +00:00
}
// IsLatitude check if a string is valid latitude.
2024-02-18 10:42:21 +00:00
func IsLatitude(str string) bool {
2024-02-18 10:42:21 +00:00
return rxLatitude.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsLongitude check if a string is valid longitude.
2024-02-18 10:42:21 +00:00
func IsLongitude(str string) bool {
2024-02-18 10:42:21 +00:00
return rxLongitude.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsRsaPublicKey check if a string is valid public key with provided length
2024-02-18 10:42:21 +00:00
func IsRsaPublicKey(str string, keylen int) bool {
2024-02-18 10:42:21 +00:00
bb := bytes.NewBufferString(str)
2024-02-18 10:42:21 +00:00
pemBytes, err := ioutil.ReadAll(bb)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
block, _ := pem.Decode(pemBytes)
2024-02-18 10:42:21 +00:00
if block != nil && block.Type != "PUBLIC KEY" {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
var der []byte
if block != nil {
2024-02-18 10:42:21 +00:00
der = block.Bytes
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
der, err = base64.StdEncoding.DecodeString(str)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
key, err := x509.ParsePKIXPublicKey(der)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
pubkey, ok := key.(*rsa.PublicKey)
2024-02-18 10:42:21 +00:00
if !ok {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
bitlen := len(pubkey.N.Bytes()) * 8
2024-02-18 10:42:21 +00:00
return bitlen == int(keylen)
2024-02-18 10:42:21 +00:00
}
func toJSONName(tag string) string {
2024-02-18 10:42:21 +00:00
if tag == "" {
2024-02-18 10:42:21 +00:00
return ""
2024-02-18 10:42:21 +00:00
}
// JSON name always comes first. If there's no options then split[0] is
2024-02-18 10:42:21 +00:00
// JSON name, if JSON name is not set, then split[0] is an empty string.
2024-02-18 10:42:21 +00:00
split := strings.SplitN(tag, ",", 2)
name := split[0]
// However it is possible that the field is skipped when
2024-02-18 10:42:21 +00:00
// (de-)serializing from/to JSON, in which case assume that there is no
2024-02-18 10:42:21 +00:00
// tag name to use
2024-02-18 10:42:21 +00:00
if name == "-" {
2024-02-18 10:42:21 +00:00
return ""
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return name
2024-02-18 10:42:21 +00:00
}
func PrependPathToErrors(err error, path string) error {
2024-02-18 10:42:21 +00:00
switch err2 := err.(type) {
2024-02-18 10:42:21 +00:00
case Error:
2024-02-18 10:42:21 +00:00
err2.Path = append([]string{path}, err2.Path...)
2024-02-18 10:42:21 +00:00
return err2
2024-02-18 10:42:21 +00:00
case Errors:
2024-02-18 10:42:21 +00:00
errors := err2.Errors()
2024-02-18 10:42:21 +00:00
for i, err3 := range errors {
2024-02-18 10:42:21 +00:00
errors[i] = PrependPathToErrors(err3, path)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return err2
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return err
2024-02-18 10:42:21 +00:00
}
// ValidateMap use validation map for fields.
2024-02-18 10:42:21 +00:00
// result will be equal to `false` if there are any errors.
2024-02-18 10:42:21 +00:00
// m is the validation map in the form
2024-02-18 10:42:21 +00:00
// map[string]interface{}{"name":"required,alpha","address":map[string]interface{}{"line1":"required,alphanum"}}
2024-02-18 10:42:21 +00:00
func ValidateMap(s map[string]interface{}, m map[string]interface{}) (bool, error) {
2024-02-18 10:42:21 +00:00
if s == nil {
2024-02-18 10:42:21 +00:00
return true, nil
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
result := true
2024-02-18 10:42:21 +00:00
var err error
2024-02-18 10:42:21 +00:00
var errs Errors
2024-02-18 10:42:21 +00:00
var index int
2024-02-18 10:42:21 +00:00
val := reflect.ValueOf(s)
2024-02-18 10:42:21 +00:00
for key, value := range s {
2024-02-18 10:42:21 +00:00
presentResult := true
2024-02-18 10:42:21 +00:00
validator, ok := m[key]
2024-02-18 10:42:21 +00:00
if !ok {
2024-02-18 10:42:21 +00:00
presentResult = false
2024-02-18 10:42:21 +00:00
var err error
2024-02-18 10:42:21 +00:00
err = fmt.Errorf("all map keys has to be present in the validation map; got %s", key)
2024-02-18 10:42:21 +00:00
err = PrependPathToErrors(err, key)
2024-02-18 10:42:21 +00:00
errs = append(errs, err)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
valueField := reflect.ValueOf(value)
2024-02-18 10:42:21 +00:00
mapResult := true
2024-02-18 10:42:21 +00:00
typeResult := true
2024-02-18 10:42:21 +00:00
structResult := true
2024-02-18 10:42:21 +00:00
resultField := true
2024-02-18 10:42:21 +00:00
switch subValidator := validator.(type) {
2024-02-18 10:42:21 +00:00
case map[string]interface{}:
2024-02-18 10:42:21 +00:00
var err error
2024-02-18 10:42:21 +00:00
if v, ok := value.(map[string]interface{}); !ok {
2024-02-18 10:42:21 +00:00
mapResult = false
2024-02-18 10:42:21 +00:00
err = fmt.Errorf("map validator has to be for the map type only; got %s", valueField.Type().String())
2024-02-18 10:42:21 +00:00
err = PrependPathToErrors(err, key)
2024-02-18 10:42:21 +00:00
errs = append(errs, err)
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
mapResult, err = ValidateMap(v, subValidator)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
mapResult = false
2024-02-18 10:42:21 +00:00
err = PrependPathToErrors(err, key)
2024-02-18 10:42:21 +00:00
errs = append(errs, err)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
case string:
2024-02-18 10:42:21 +00:00
if (valueField.Kind() == reflect.Struct ||
2024-02-18 10:42:21 +00:00
(valueField.Kind() == reflect.Ptr && valueField.Elem().Kind() == reflect.Struct)) &&
2024-02-18 10:42:21 +00:00
subValidator != "-" {
2024-02-18 10:42:21 +00:00
var err error
2024-02-18 10:42:21 +00:00
structResult, err = ValidateStruct(valueField.Interface())
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
err = PrependPathToErrors(err, key)
2024-02-18 10:42:21 +00:00
errs = append(errs, err)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
resultField, err = typeCheck(valueField, reflect.StructField{
Name: key,
PkgPath: "",
Type: val.Type(),
Tag: reflect.StructTag(fmt.Sprintf("%s:%q", tagName, subValidator)),
Offset: 0,
Index: []int{index},
2024-02-18 10:42:21 +00:00
Anonymous: false,
}, val, nil)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
errs = append(errs, err)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
case nil:
2024-02-18 10:42:21 +00:00
// already handlerd when checked before
2024-02-18 10:42:21 +00:00
default:
2024-02-18 10:42:21 +00:00
typeResult = false
2024-02-18 10:42:21 +00:00
err = fmt.Errorf("map validator has to be either map[string]interface{} or string; got %s", valueField.Type().String())
2024-02-18 10:42:21 +00:00
err = PrependPathToErrors(err, key)
2024-02-18 10:42:21 +00:00
errs = append(errs, err)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
result = result && presentResult && typeResult && resultField && structResult && mapResult
2024-02-18 10:42:21 +00:00
index++
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
// check required keys
2024-02-18 10:42:21 +00:00
requiredResult := true
2024-02-18 10:42:21 +00:00
for key, value := range m {
2024-02-18 10:42:21 +00:00
if schema, ok := value.(string); ok {
2024-02-18 10:42:21 +00:00
tags := parseTagIntoMap(schema)
2024-02-18 10:42:21 +00:00
if required, ok := tags["required"]; ok {
2024-02-18 10:42:21 +00:00
if _, ok := s[key]; !ok {
2024-02-18 10:42:21 +00:00
requiredResult = false
2024-02-18 10:42:21 +00:00
if required.customErrorMessage != "" {
2024-02-18 10:42:21 +00:00
err = Error{key, fmt.Errorf(required.customErrorMessage), true, "required", []string{}}
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
err = Error{key, fmt.Errorf("required field missing"), false, "required", []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
errs = append(errs, err)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
if len(errs) > 0 {
2024-02-18 10:42:21 +00:00
err = errs
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return result && requiredResult, err
2024-02-18 10:42:21 +00:00
}
// ValidateStruct use tags for fields.
2024-02-18 10:42:21 +00:00
// result will be equal to `false` if there are any errors.
2024-02-18 10:42:21 +00:00
// todo currently there is no guarantee that errors will be returned in predictable order (tests may to fail)
2024-02-18 10:42:21 +00:00
func ValidateStruct(s interface{}) (bool, error) {
2024-02-18 10:42:21 +00:00
if s == nil {
2024-02-18 10:42:21 +00:00
return true, nil
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
result := true
2024-02-18 10:42:21 +00:00
var err error
2024-02-18 10:42:21 +00:00
val := reflect.ValueOf(s)
2024-02-18 10:42:21 +00:00
if val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr {
2024-02-18 10:42:21 +00:00
val = val.Elem()
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
// we only accept structs
2024-02-18 10:42:21 +00:00
if val.Kind() != reflect.Struct {
2024-02-18 10:42:21 +00:00
return false, fmt.Errorf("function only accepts structs; got %s", val.Kind())
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
var errs Errors
2024-02-18 10:42:21 +00:00
for i := 0; i < val.NumField(); i++ {
2024-02-18 10:42:21 +00:00
valueField := val.Field(i)
2024-02-18 10:42:21 +00:00
typeField := val.Type().Field(i)
2024-02-18 10:42:21 +00:00
if typeField.PkgPath != "" {
2024-02-18 10:42:21 +00:00
continue // Private field
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
structResult := true
2024-02-18 10:42:21 +00:00
if valueField.Kind() == reflect.Interface {
2024-02-18 10:42:21 +00:00
valueField = valueField.Elem()
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if (valueField.Kind() == reflect.Struct ||
2024-02-18 10:42:21 +00:00
(valueField.Kind() == reflect.Ptr && valueField.Elem().Kind() == reflect.Struct)) &&
2024-02-18 10:42:21 +00:00
typeField.Tag.Get(tagName) != "-" {
2024-02-18 10:42:21 +00:00
var err error
2024-02-18 10:42:21 +00:00
structResult, err = ValidateStruct(valueField.Interface())
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
err = PrependPathToErrors(err, typeField.Name)
2024-02-18 10:42:21 +00:00
errs = append(errs, err)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
resultField, err2 := typeCheck(valueField, typeField, val, nil)
2024-02-18 10:42:21 +00:00
if err2 != nil {
// Replace structure name with JSON name if there is a tag on the variable
2024-02-18 10:42:21 +00:00
jsonTag := toJSONName(typeField.Tag.Get("json"))
2024-02-18 10:42:21 +00:00
if jsonTag != "" {
2024-02-18 10:42:21 +00:00
switch jsonError := err2.(type) {
2024-02-18 10:42:21 +00:00
case Error:
2024-02-18 10:42:21 +00:00
jsonError.Name = jsonTag
2024-02-18 10:42:21 +00:00
err2 = jsonError
2024-02-18 10:42:21 +00:00
case Errors:
2024-02-18 10:42:21 +00:00
for i2, err3 := range jsonError {
2024-02-18 10:42:21 +00:00
switch customErr := err3.(type) {
2024-02-18 10:42:21 +00:00
case Error:
2024-02-18 10:42:21 +00:00
customErr.Name = jsonTag
2024-02-18 10:42:21 +00:00
jsonError[i2] = customErr
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
err2 = jsonError
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
errs = append(errs, err2)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
result = result && resultField && structResult
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if len(errs) > 0 {
2024-02-18 10:42:21 +00:00
err = errs
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return result, err
2024-02-18 10:42:21 +00:00
}
// parseTagIntoMap parses a struct tag `valid:required~Some error message,length(2|3)` into map[string]string{"required": "Some error message", "length(2|3)": ""}
2024-02-18 10:42:21 +00:00
func parseTagIntoMap(tag string) tagOptionsMap {
2024-02-18 10:42:21 +00:00
optionsMap := make(tagOptionsMap)
2024-02-18 10:42:21 +00:00
options := strings.Split(tag, ",")
for i, option := range options {
2024-02-18 10:42:21 +00:00
option = strings.TrimSpace(option)
validationOptions := strings.Split(option, "~")
2024-02-18 10:42:21 +00:00
if !isValidTag(validationOptions[0]) {
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if len(validationOptions) == 2 {
2024-02-18 10:42:21 +00:00
optionsMap[validationOptions[0]] = tagOption{validationOptions[0], validationOptions[1], i}
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
optionsMap[validationOptions[0]] = tagOption{validationOptions[0], "", i}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return optionsMap
2024-02-18 10:42:21 +00:00
}
func isValidTag(s string) bool {
2024-02-18 10:42:21 +00:00
if s == "" {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
for _, c := range s {
2024-02-18 10:42:21 +00:00
switch {
2024-02-18 10:42:21 +00:00
case strings.ContainsRune("\\'\"!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
2024-02-18 10:42:21 +00:00
// Backslash and quote chars are reserved, but
2024-02-18 10:42:21 +00:00
// otherwise any punctuation chars are allowed
2024-02-18 10:42:21 +00:00
// in a tag name.
2024-02-18 10:42:21 +00:00
default:
2024-02-18 10:42:21 +00:00
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
// IsSSN will validate the given string as a U.S. Social Security Number
2024-02-18 10:42:21 +00:00
func IsSSN(str string) bool {
2024-02-18 10:42:21 +00:00
if str == "" || len(str) != 11 {
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return rxSSN.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsSemver check if string is valid semantic version
2024-02-18 10:42:21 +00:00
func IsSemver(str string) bool {
2024-02-18 10:42:21 +00:00
return rxSemver.MatchString(str)
2024-02-18 10:42:21 +00:00
}
// IsType check if interface is of some type
2024-02-18 10:42:21 +00:00
func IsType(v interface{}, params ...string) bool {
2024-02-18 10:42:21 +00:00
if len(params) == 1 {
2024-02-18 10:42:21 +00:00
typ := params[0]
2024-02-18 10:42:21 +00:00
return strings.Replace(reflect.TypeOf(v).String(), " ", "", -1) == strings.Replace(typ, " ", "", -1)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
// IsTime check if string is valid according to given format
2024-02-18 10:42:21 +00:00
func IsTime(str string, format string) bool {
2024-02-18 10:42:21 +00:00
_, err := time.Parse(format, str)
2024-02-18 10:42:21 +00:00
return err == nil
2024-02-18 10:42:21 +00:00
}
// IsUnixTime check if string is valid unix timestamp value
2024-02-18 10:42:21 +00:00
func IsUnixTime(str string) bool {
2024-02-18 10:42:21 +00:00
if _, err := strconv.Atoi(str); err == nil {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
// IsRFC3339 check if string is valid timestamp value according to RFC3339
2024-02-18 10:42:21 +00:00
func IsRFC3339(str string) bool {
2024-02-18 10:42:21 +00:00
return IsTime(str, time.RFC3339)
2024-02-18 10:42:21 +00:00
}
// IsRFC3339WithoutZone check if string is valid timestamp value according to RFC3339 which excludes the timezone.
2024-02-18 10:42:21 +00:00
func IsRFC3339WithoutZone(str string) bool {
2024-02-18 10:42:21 +00:00
return IsTime(str, RF3339WithoutZone)
2024-02-18 10:42:21 +00:00
}
// IsISO4217 check if string is valid ISO currency code
2024-02-18 10:42:21 +00:00
func IsISO4217(str string) bool {
2024-02-18 10:42:21 +00:00
for _, currency := range ISO4217List {
2024-02-18 10:42:21 +00:00
if str == currency {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
return false
2024-02-18 10:42:21 +00:00
}
// ByteLength check string's length
2024-02-18 10:42:21 +00:00
func ByteLength(str string, params ...string) bool {
2024-02-18 10:42:21 +00:00
if len(params) == 2 {
2024-02-18 10:42:21 +00:00
min, _ := ToInt(params[0])
2024-02-18 10:42:21 +00:00
max, _ := ToInt(params[1])
2024-02-18 10:42:21 +00:00
return len(str) >= int(min) && len(str) <= int(max)
2024-02-18 10:42:21 +00:00
}
return false
2024-02-18 10:42:21 +00:00
}
// RuneLength check string's length
2024-02-18 10:42:21 +00:00
// Alias for StringLength
2024-02-18 10:42:21 +00:00
func RuneLength(str string, params ...string) bool {
2024-02-18 10:42:21 +00:00
return StringLength(str, params...)
2024-02-18 10:42:21 +00:00
}
// IsRsaPub check whether string is valid RSA key
2024-02-18 10:42:21 +00:00
// Alias for IsRsaPublicKey
2024-02-18 10:42:21 +00:00
func IsRsaPub(str string, params ...string) bool {
2024-02-18 10:42:21 +00:00
if len(params) == 1 {
2024-02-18 10:42:21 +00:00
len, _ := ToInt(params[0])
2024-02-18 10:42:21 +00:00
return IsRsaPublicKey(str, int(len))
2024-02-18 10:42:21 +00:00
}
return false
2024-02-18 10:42:21 +00:00
}
// StringMatches checks if a string matches a given pattern.
2024-02-18 10:42:21 +00:00
func StringMatches(s string, params ...string) bool {
2024-02-18 10:42:21 +00:00
if len(params) == 1 {
2024-02-18 10:42:21 +00:00
pattern := params[0]
2024-02-18 10:42:21 +00:00
return Matches(s, pattern)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
// StringLength check string's length (including multi byte strings)
2024-02-18 10:42:21 +00:00
func StringLength(str string, params ...string) bool {
if len(params) == 2 {
2024-02-18 10:42:21 +00:00
strLength := utf8.RuneCountInString(str)
2024-02-18 10:42:21 +00:00
min, _ := ToInt(params[0])
2024-02-18 10:42:21 +00:00
max, _ := ToInt(params[1])
2024-02-18 10:42:21 +00:00
return strLength >= int(min) && strLength <= int(max)
2024-02-18 10:42:21 +00:00
}
return false
2024-02-18 10:42:21 +00:00
}
// MinStringLength check string's minimum length (including multi byte strings)
2024-02-18 10:42:21 +00:00
func MinStringLength(str string, params ...string) bool {
if len(params) == 1 {
2024-02-18 10:42:21 +00:00
strLength := utf8.RuneCountInString(str)
2024-02-18 10:42:21 +00:00
min, _ := ToInt(params[0])
2024-02-18 10:42:21 +00:00
return strLength >= int(min)
2024-02-18 10:42:21 +00:00
}
return false
2024-02-18 10:42:21 +00:00
}
// MaxStringLength check string's maximum length (including multi byte strings)
2024-02-18 10:42:21 +00:00
func MaxStringLength(str string, params ...string) bool {
if len(params) == 1 {
2024-02-18 10:42:21 +00:00
strLength := utf8.RuneCountInString(str)
2024-02-18 10:42:21 +00:00
max, _ := ToInt(params[0])
2024-02-18 10:42:21 +00:00
return strLength <= int(max)
2024-02-18 10:42:21 +00:00
}
return false
2024-02-18 10:42:21 +00:00
}
// Range check string's length
2024-02-18 10:42:21 +00:00
func Range(str string, params ...string) bool {
2024-02-18 10:42:21 +00:00
if len(params) == 2 {
2024-02-18 10:42:21 +00:00
value, _ := ToFloat(str)
2024-02-18 10:42:21 +00:00
min, _ := ToFloat(params[0])
2024-02-18 10:42:21 +00:00
max, _ := ToFloat(params[1])
2024-02-18 10:42:21 +00:00
return InRange(value, min, max)
2024-02-18 10:42:21 +00:00
}
return false
2024-02-18 10:42:21 +00:00
}
func IsInRaw(str string, params ...string) bool {
2024-02-18 10:42:21 +00:00
if len(params) == 1 {
2024-02-18 10:42:21 +00:00
rawParams := params[0]
parsedParams := strings.Split(rawParams, "|")
return IsIn(str, parsedParams...)
2024-02-18 10:42:21 +00:00
}
return false
2024-02-18 10:42:21 +00:00
}
// IsIn check if string str is a member of the set of strings params
2024-02-18 10:42:21 +00:00
func IsIn(str string, params ...string) bool {
2024-02-18 10:42:21 +00:00
for _, param := range params {
2024-02-18 10:42:21 +00:00
if str == param {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
return false
2024-02-18 10:42:21 +00:00
}
func checkRequired(v reflect.Value, t reflect.StructField, options tagOptionsMap) (bool, error) {
2024-02-18 10:42:21 +00:00
if nilPtrAllowedByRequired {
2024-02-18 10:42:21 +00:00
k := v.Kind()
2024-02-18 10:42:21 +00:00
if (k == reflect.Ptr || k == reflect.Interface) && v.IsNil() {
2024-02-18 10:42:21 +00:00
return true, nil
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
if requiredOption, isRequired := options["required"]; isRequired {
2024-02-18 10:42:21 +00:00
if len(requiredOption.customErrorMessage) > 0 {
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, fmt.Errorf(requiredOption.customErrorMessage), true, "required", []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, fmt.Errorf("non zero value required"), false, "required", []string{}}
2024-02-18 10:42:21 +00:00
} else if _, isOptional := options["optional"]; fieldsRequiredByDefault && !isOptional {
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, fmt.Errorf("Missing required field"), false, "required", []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
// not required and empty is valid
2024-02-18 10:42:21 +00:00
return true, nil
2024-02-18 10:42:21 +00:00
}
func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options tagOptionsMap) (isValid bool, resultErr error) {
2024-02-18 10:42:21 +00:00
if !v.IsValid() {
2024-02-18 10:42:21 +00:00
return false, nil
2024-02-18 10:42:21 +00:00
}
tag := t.Tag.Get(tagName)
// Check if the field should be ignored
2024-02-18 10:42:21 +00:00
switch tag {
2024-02-18 10:42:21 +00:00
case "":
2024-02-18 10:42:21 +00:00
if v.Kind() != reflect.Slice && v.Kind() != reflect.Map {
2024-02-18 10:42:21 +00:00
if !fieldsRequiredByDefault {
2024-02-18 10:42:21 +00:00
return true, nil
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, fmt.Errorf("All fields are required to at least have one validation defined"), false, "required", []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
case "-":
2024-02-18 10:42:21 +00:00
return true, nil
2024-02-18 10:42:21 +00:00
}
isRootType := false
2024-02-18 10:42:21 +00:00
if options == nil {
2024-02-18 10:42:21 +00:00
isRootType = true
2024-02-18 10:42:21 +00:00
options = parseTagIntoMap(tag)
2024-02-18 10:42:21 +00:00
}
if !isFieldSet(v) {
2024-02-18 10:42:21 +00:00
// an empty value is not validated, check only required
2024-02-18 10:42:21 +00:00
isValid, resultErr = checkRequired(v, t, options)
2024-02-18 10:42:21 +00:00
for key := range options {
2024-02-18 10:42:21 +00:00
delete(options, key)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return isValid, resultErr
2024-02-18 10:42:21 +00:00
}
var customTypeErrors Errors
2024-02-18 10:42:21 +00:00
optionsOrder := options.orderedKeys()
2024-02-18 10:42:21 +00:00
for _, validatorName := range optionsOrder {
2024-02-18 10:42:21 +00:00
validatorStruct := options[validatorName]
2024-02-18 10:42:21 +00:00
if validatefunc, ok := CustomTypeTagMap.Get(validatorName); ok {
2024-02-18 10:42:21 +00:00
delete(options, validatorName)
if result := validatefunc(v.Interface(), o.Interface()); !result {
2024-02-18 10:42:21 +00:00
if len(validatorStruct.customErrorMessage) > 0 {
2024-02-18 10:42:21 +00:00
customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: TruncatingErrorf(validatorStruct.customErrorMessage, fmt.Sprint(v), validatorName), CustomErrorMessageExists: true, Validator: stripParams(validatorName)})
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: fmt.Errorf("%s does not validate as %s", fmt.Sprint(v), validatorName), CustomErrorMessageExists: false, Validator: stripParams(validatorName)})
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
if len(customTypeErrors.Errors()) > 0 {
2024-02-18 10:42:21 +00:00
return false, customTypeErrors
2024-02-18 10:42:21 +00:00
}
if isRootType {
2024-02-18 10:42:21 +00:00
// Ensure that we've checked the value by all specified validators before report that the value is valid
2024-02-18 10:42:21 +00:00
defer func() {
2024-02-18 10:42:21 +00:00
delete(options, "optional")
2024-02-18 10:42:21 +00:00
delete(options, "required")
if isValid && resultErr == nil && len(options) != 0 {
2024-02-18 10:42:21 +00:00
optionsOrder := options.orderedKeys()
2024-02-18 10:42:21 +00:00
for _, validator := range optionsOrder {
2024-02-18 10:42:21 +00:00
isValid = false
2024-02-18 10:42:21 +00:00
resultErr = Error{t.Name, fmt.Errorf(
2024-02-18 10:42:21 +00:00
"The following validator is invalid or can't be applied to the field: %q", validator), false, stripParams(validator), []string{}}
2024-02-18 10:42:21 +00:00
return
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}()
2024-02-18 10:42:21 +00:00
}
for _, validatorSpec := range optionsOrder {
2024-02-18 10:42:21 +00:00
validatorStruct := options[validatorSpec]
2024-02-18 10:42:21 +00:00
var negate bool
2024-02-18 10:42:21 +00:00
validator := validatorSpec
2024-02-18 10:42:21 +00:00
customMsgExists := len(validatorStruct.customErrorMessage) > 0
// Check whether the tag looks like '!something' or 'something'
2024-02-18 10:42:21 +00:00
if validator[0] == '!' {
2024-02-18 10:42:21 +00:00
validator = validator[1:]
2024-02-18 10:42:21 +00:00
negate = true
2024-02-18 10:42:21 +00:00
}
// Check for interface param validators
2024-02-18 10:42:21 +00:00
for key, value := range InterfaceParamTagRegexMap {
2024-02-18 10:42:21 +00:00
ps := value.FindStringSubmatch(validator)
2024-02-18 10:42:21 +00:00
if len(ps) == 0 {
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
validatefunc, ok := InterfaceParamTagMap[key]
2024-02-18 10:42:21 +00:00
if !ok {
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
delete(options, validatorSpec)
field := fmt.Sprint(v)
2024-02-18 10:42:21 +00:00
if result := validatefunc(v.Interface(), ps[1:]...); (!result && !negate) || (result && negate) {
2024-02-18 10:42:21 +00:00
if customMsgExists {
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, TruncatingErrorf(validatorStruct.customErrorMessage, field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if negate {
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
switch v.Kind() {
2024-02-18 10:42:21 +00:00
case reflect.Bool,
2024-02-18 10:42:21 +00:00
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
2024-02-18 10:42:21 +00:00
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
2024-02-18 10:42:21 +00:00
reflect.Float32, reflect.Float64,
2024-02-18 10:42:21 +00:00
reflect.String:
2024-02-18 10:42:21 +00:00
// for each tag option check the map of validator functions
2024-02-18 10:42:21 +00:00
for _, validatorSpec := range optionsOrder {
2024-02-18 10:42:21 +00:00
validatorStruct := options[validatorSpec]
2024-02-18 10:42:21 +00:00
var negate bool
2024-02-18 10:42:21 +00:00
validator := validatorSpec
2024-02-18 10:42:21 +00:00
customMsgExists := len(validatorStruct.customErrorMessage) > 0
// Check whether the tag looks like '!something' or 'something'
2024-02-18 10:42:21 +00:00
if validator[0] == '!' {
2024-02-18 10:42:21 +00:00
validator = validator[1:]
2024-02-18 10:42:21 +00:00
negate = true
2024-02-18 10:42:21 +00:00
}
// Check for param validators
2024-02-18 10:42:21 +00:00
for key, value := range ParamTagRegexMap {
2024-02-18 10:42:21 +00:00
ps := value.FindStringSubmatch(validator)
2024-02-18 10:42:21 +00:00
if len(ps) == 0 {
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
validatefunc, ok := ParamTagMap[key]
2024-02-18 10:42:21 +00:00
if !ok {
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
delete(options, validatorSpec)
switch v.Kind() {
2024-02-18 10:42:21 +00:00
case reflect.String,
2024-02-18 10:42:21 +00:00
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
2024-02-18 10:42:21 +00:00
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
2024-02-18 10:42:21 +00:00
reflect.Float32, reflect.Float64:
field := fmt.Sprint(v) // make value into string, then validate with regex
2024-02-18 10:42:21 +00:00
if result := validatefunc(field, ps[1:]...); (!result && !negate) || (result && negate) {
2024-02-18 10:42:21 +00:00
if customMsgExists {
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, TruncatingErrorf(validatorStruct.customErrorMessage, field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if negate {
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
default:
2024-02-18 10:42:21 +00:00
// type not yet supported, fail
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, fmt.Errorf("Validator %s doesn't support kind %s", validator, v.Kind()), false, stripParams(validatorSpec), []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
if validatefunc, ok := TagMap[validator]; ok {
2024-02-18 10:42:21 +00:00
delete(options, validatorSpec)
switch v.Kind() {
2024-02-18 10:42:21 +00:00
case reflect.String,
2024-02-18 10:42:21 +00:00
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
2024-02-18 10:42:21 +00:00
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
2024-02-18 10:42:21 +00:00
reflect.Float32, reflect.Float64:
2024-02-18 10:42:21 +00:00
field := fmt.Sprint(v) // make value into string, then validate with regex
2024-02-18 10:42:21 +00:00
if result := validatefunc(field); !result && !negate || result && negate {
2024-02-18 10:42:21 +00:00
if customMsgExists {
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, TruncatingErrorf(validatorStruct.customErrorMessage, field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if negate {
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
default:
2024-02-18 10:42:21 +00:00
//Not Yet Supported Types (Fail here!)
2024-02-18 10:42:21 +00:00
err := fmt.Errorf("Validator %s doesn't support kind %s for value %v", validator, v.Kind(), v)
2024-02-18 10:42:21 +00:00
return false, Error{t.Name, err, false, stripParams(validatorSpec), []string{}}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return true, nil
2024-02-18 10:42:21 +00:00
case reflect.Map:
2024-02-18 10:42:21 +00:00
if v.Type().Key().Kind() != reflect.String {
2024-02-18 10:42:21 +00:00
return false, &UnsupportedTypeError{v.Type()}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
var sv stringValues
2024-02-18 10:42:21 +00:00
sv = v.MapKeys()
2024-02-18 10:42:21 +00:00
sort.Sort(sv)
2024-02-18 10:42:21 +00:00
result := true
2024-02-18 10:42:21 +00:00
for i, k := range sv {
2024-02-18 10:42:21 +00:00
var resultItem bool
2024-02-18 10:42:21 +00:00
var err error
2024-02-18 10:42:21 +00:00
if v.MapIndex(k).Kind() != reflect.Struct {
2024-02-18 10:42:21 +00:00
resultItem, err = typeCheck(v.MapIndex(k), t, o, options)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return false, err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
resultItem, err = ValidateStruct(v.MapIndex(k).Interface())
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
err = PrependPathToErrors(err, t.Name+"."+sv[i].Interface().(string))
2024-02-18 10:42:21 +00:00
return false, err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
result = result && resultItem
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return result, nil
2024-02-18 10:42:21 +00:00
case reflect.Slice, reflect.Array:
2024-02-18 10:42:21 +00:00
result := true
2024-02-18 10:42:21 +00:00
for i := 0; i < v.Len(); i++ {
2024-02-18 10:42:21 +00:00
var resultItem bool
2024-02-18 10:42:21 +00:00
var err error
2024-02-18 10:42:21 +00:00
if v.Index(i).Kind() != reflect.Struct {
2024-02-18 10:42:21 +00:00
resultItem, err = typeCheck(v.Index(i), t, o, options)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return false, err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
resultItem, err = ValidateStruct(v.Index(i).Interface())
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
err = PrependPathToErrors(err, t.Name+"."+strconv.Itoa(i))
2024-02-18 10:42:21 +00:00
return false, err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
result = result && resultItem
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return result, nil
2024-02-18 10:42:21 +00:00
case reflect.Interface:
2024-02-18 10:42:21 +00:00
// If the value is an interface then encode its element
2024-02-18 10:42:21 +00:00
if v.IsNil() {
2024-02-18 10:42:21 +00:00
return true, nil
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return ValidateStruct(v.Interface())
2024-02-18 10:42:21 +00:00
case reflect.Ptr:
2024-02-18 10:42:21 +00:00
// If the value is a pointer then check its element
2024-02-18 10:42:21 +00:00
if v.IsNil() {
2024-02-18 10:42:21 +00:00
return true, nil
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return typeCheck(v.Elem(), t, o, options)
2024-02-18 10:42:21 +00:00
case reflect.Struct:
2024-02-18 10:42:21 +00:00
return true, nil
2024-02-18 10:42:21 +00:00
default:
2024-02-18 10:42:21 +00:00
return false, &UnsupportedTypeError{v.Type()}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
func stripParams(validatorString string) string {
2024-02-18 10:42:21 +00:00
return paramsRegexp.ReplaceAllString(validatorString, "")
2024-02-18 10:42:21 +00:00
}
// isFieldSet returns false for nil pointers, interfaces, maps, and slices. For all other values, it returns true.
2024-02-18 10:42:21 +00:00
func isFieldSet(v reflect.Value) bool {
2024-02-18 10:42:21 +00:00
switch v.Kind() {
2024-02-18 10:42:21 +00:00
case reflect.Map, reflect.Slice, reflect.Interface, reflect.Ptr:
2024-02-18 10:42:21 +00:00
return !v.IsNil()
2024-02-18 10:42:21 +00:00
}
return true
2024-02-18 10:42:21 +00:00
}
// ErrorByField returns error for specified field of the struct
2024-02-18 10:42:21 +00:00
// validated by ValidateStruct or empty string if there are no errors
2024-02-18 10:42:21 +00:00
// or this field doesn't exists or doesn't have any errors.
2024-02-18 10:42:21 +00:00
func ErrorByField(e error, field string) string {
2024-02-18 10:42:21 +00:00
if e == nil {
2024-02-18 10:42:21 +00:00
return ""
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return ErrorsByField(e)[field]
2024-02-18 10:42:21 +00:00
}
// ErrorsByField returns map of errors of the struct validated
2024-02-18 10:42:21 +00:00
// by ValidateStruct or empty map if there are no errors.
2024-02-18 10:42:21 +00:00
func ErrorsByField(e error) map[string]string {
2024-02-18 10:42:21 +00:00
m := make(map[string]string)
2024-02-18 10:42:21 +00:00
if e == nil {
2024-02-18 10:42:21 +00:00
return m
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
// prototype for ValidateStruct
switch e.(type) {
2024-02-18 10:42:21 +00:00
case Error:
2024-02-18 10:42:21 +00:00
m[e.(Error).Name] = e.(Error).Err.Error()
2024-02-18 10:42:21 +00:00
case Errors:
2024-02-18 10:42:21 +00:00
for _, item := range e.(Errors).Errors() {
2024-02-18 10:42:21 +00:00
n := ErrorsByField(item)
2024-02-18 10:42:21 +00:00
for k, v := range n {
2024-02-18 10:42:21 +00:00
m[k] = v
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
return m
2024-02-18 10:42:21 +00:00
}
// Error returns string equivalent for reflect.Type
2024-02-18 10:42:21 +00:00
func (e *UnsupportedTypeError) Error() string {
2024-02-18 10:42:21 +00:00
return "validator: unsupported type: " + e.Type.String()
2024-02-18 10:42:21 +00:00
}
func (sv stringValues) Len() int { return len(sv) }
func (sv stringValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] }
2024-02-18 10:42:21 +00:00
func (sv stringValues) Less(i, j int) bool { return sv.get(i) < sv.get(j) }
func (sv stringValues) get(i int) string { return sv[i].String() }