Last active
January 7, 2025 09:35
-
-
Save xigang/98504205d86803baedc5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "regexp" | |
// Basic regular expressions for validating strings | |
const ( | |
Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" | |
CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$" | |
ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$" | |
ISBN13 string = "^(?:[0-9]{13})$" | |
UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" | |
UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" | |
UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" | |
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" | |
Alpha string = "^[a-zA-Z]+$" | |
Alphanumeric string = "^[a-zA-Z0-9]+$" | |
Numeric string = "^[-+]?[0-9]+$" | |
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$" | |
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$" | |
Hexadecimal string = "^[0-9a-fA-F]+$" | |
Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" | |
RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$" | |
ASCII string = "^[\x00-\x7F]+$" | |
Multibyte string = "[^\x00-\x7F]" | |
FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]" | |
HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]" | |
Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" | |
PrintableASCII string = "^[\x20-\x7E]+$" | |
DataURI string = "^data:.+\\/(.+);base64$" | |
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" | |
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" | |
DNSName string = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{1,62}){1}(.[a-zA-Z0-9]{1}[a-zA-Z0-9_-]{1,62})*$` | |
URL string = `^((ftp|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$` | |
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` | |
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$` | |
UnixPath string = `^((?:\/[a-zA-Z0-9\.\:]+(?:_[a-zA-Z0-9\:\.]+)*(?:\-[\:a-zA-Z0-9\.]+)*)+\/?)$` | |
Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$" | |
tagName string = "valid" | |
) | |
// Used by IsFilePath func | |
const ( | |
// Unknown is unresolved OS type | |
Unknown = iota | |
// Win is Windows type | |
Win | |
// Unix is *nix OS types | |
Unix | |
) | |
var ( | |
rxEmail = regexp.MustCompile(Email) | |
rxCreditCard = regexp.MustCompile(CreditCard) | |
rxISBN10 = regexp.MustCompile(ISBN10) | |
rxISBN13 = regexp.MustCompile(ISBN13) | |
rxUUID3 = regexp.MustCompile(UUID3) | |
rxUUID4 = regexp.MustCompile(UUID4) | |
rxUUID5 = regexp.MustCompile(UUID5) | |
rxUUID = regexp.MustCompile(UUID) | |
rxAlpha = regexp.MustCompile(Alpha) | |
rxAlphanumeric = regexp.MustCompile(Alphanumeric) | |
rxNumeric = regexp.MustCompile(Numeric) | |
rxInt = regexp.MustCompile(Int) | |
rxFloat = regexp.MustCompile(Float) | |
rxHexadecimal = regexp.MustCompile(Hexadecimal) | |
rxHexcolor = regexp.MustCompile(Hexcolor) | |
rxRGBcolor = regexp.MustCompile(RGBcolor) | |
rxASCII = regexp.MustCompile(ASCII) | |
rxPrintableASCII = regexp.MustCompile(PrintableASCII) | |
rxMultibyte = regexp.MustCompile(Multibyte) | |
rxFullWidth = regexp.MustCompile(FullWidth) | |
rxHalfWidth = regexp.MustCompile(HalfWidth) | |
rxBase64 = regexp.MustCompile(Base64) | |
rxDataURI = regexp.MustCompile(DataURI) | |
rxLatitude = regexp.MustCompile(Latitude) | |
rxLongitude = regexp.MustCompile(Longitude) | |
rxDNSName = regexp.MustCompile(DNSName) | |
rxURL = regexp.MustCompile(URL) | |
rxSSN = regexp.MustCompile(SSN) | |
rxWinPath = regexp.MustCompile(WinPath) | |
rxUnixPath = regexp.MustCompile(UnixPath) | |
rxSemver = regexp.MustCompile(Semver) | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ( | |
"encoding/json" | |
"fmt" | |
"net" | |
"net/url" | |
"reflect" | |
"regexp" | |
"sort" | |
"strconv" | |
"strings" | |
"unicode" | |
"unicode/utf8" | |
) | |
var fieldsRequiredByDefault bool | |
// SetFieldsRequiredByDefault causes validation to fail when struct fields | |
// do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`). | |
// This struct definition will fail govalidator.ValidateStruct() (and the field values do not matter): | |
// type exampleStruct struct { | |
// Name string `` | |
// Email string `valid:"email"` | |
// This, however, will only fail when Email is empty or an invalid email address: | |
// type exampleStruct2 struct { | |
// Name string `valid:"-"` | |
// Email string `valid:"email"` | |
// Lastly, this will only fail when Email is an invalid email address but not when it's empty: | |
// type exampleStruct2 struct { | |
// Name string `valid:"-"` | |
// Email string `valid:"email,optional"` | |
func SetFieldsRequiredByDefault(value bool) { | |
fieldsRequiredByDefault = value | |
} | |
// IsEmail check if the string is an email. | |
func IsEmail(str string) bool { | |
// TODO uppercase letters are not supported | |
return rxEmail.MatchString(str) | |
} | |
// IsURL check if the string is an URL. | |
func IsURL(str string) bool { | |
if str == "" || len(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") { | |
return false | |
} | |
u, err := url.Parse(str) | |
if err != nil { | |
return false | |
} | |
if strings.HasPrefix(u.Host, ".") { | |
return false | |
} | |
if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) { | |
return false | |
} | |
return rxURL.MatchString(str) | |
} | |
// IsRequestURL check if the string rawurl, assuming | |
// it was recieved in an HTTP request, is a valid | |
// URL confirm to RFC 3986 | |
func IsRequestURL(rawurl string) bool { | |
url, err := url.ParseRequestURI(rawurl) | |
if err != nil { | |
return false //Couldn't even parse the rawurl | |
} | |
if len(url.Scheme) == 0 { | |
return false //No Scheme found | |
} | |
return true | |
} | |
// IsRequestURI check if the string rawurl, assuming | |
// it was recieved in an HTTP request, is an | |
// absolute URI or an absolute path. | |
func IsRequestURI(rawurl string) bool { | |
_, err := url.ParseRequestURI(rawurl) | |
return err == nil | |
} | |
// IsAlpha check if the string contains only letters (a-zA-Z). Empty string is valid. | |
func IsAlpha(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return rxAlpha.MatchString(str) | |
} | |
//IsUTFLetter check if the string contains only unicode letter characters. | |
//Similar to IsAlpha but for all languages. Empty string is valid. | |
func IsUTFLetter(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
for _, c := range str { | |
if !unicode.IsLetter(c) { | |
return false | |
} | |
} | |
return true | |
} | |
// IsAlphanumeric check if the string contains only letters and numbers. Empty string is valid. | |
func IsAlphanumeric(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return rxAlphanumeric.MatchString(str) | |
} | |
// IsUTFLetterNumeric check if the string contains only unicode letters and numbers. Empty string is valid. | |
func IsUTFLetterNumeric(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
for _, c := range str { | |
if !unicode.IsLetter(c) && !unicode.IsNumber(c) { //letters && numbers are ok | |
return false | |
} | |
} | |
return true | |
} | |
// IsNumeric check if the string contains only numbers. Empty string is valid. | |
func IsNumeric(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return rxNumeric.MatchString(str) | |
} | |
// IsUTFNumeric check if the string contains only unicode numbers of any kind. | |
// Numbers can be 0-9 but also Fractions ¾,Roman Ⅸ and Hangzhou 〩. Empty string is valid. | |
func IsUTFNumeric(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
if strings.IndexAny(str, "+-") > 0 { | |
return false | |
} | |
if len(str) > 1 { | |
str = strings.TrimPrefix(str, "-") | |
str = strings.TrimPrefix(str, "+") | |
} | |
for _, c := range str { | |
if unicode.IsNumber(c) == false { //numbers && minus sign are ok | |
return false | |
} | |
} | |
return true | |
} | |
// IsUTFDigit check if the string contains only unicode radix-10 decimal digits. Empty string is valid. | |
func IsUTFDigit(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
if strings.IndexAny(str, "+-") > 0 { | |
return false | |
} | |
if len(str) > 1 { | |
str = strings.TrimPrefix(str, "-") | |
str = strings.TrimPrefix(str, "+") | |
} | |
for _, c := range str { | |
if !unicode.IsDigit(c) { //digits && minus sign are ok | |
return false | |
} | |
} | |
return true | |
} | |
// IsHexadecimal check if the string is a hexadecimal number. | |
func IsHexadecimal(str string) bool { | |
return rxHexadecimal.MatchString(str) | |
} | |
// IsHexcolor check if the string is a hexadecimal color. | |
func IsHexcolor(str string) bool { | |
return rxHexcolor.MatchString(str) | |
} | |
// IsRGBcolor check if the string is a valid RGB color in form rgb(RRR, GGG, BBB). | |
func IsRGBcolor(str string) bool { | |
return rxRGBcolor.MatchString(str) | |
} | |
// IsLowerCase check if the string is lowercase. Empty string is valid. | |
func IsLowerCase(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return str == strings.ToLower(str) | |
} | |
// IsUpperCase check if the string is uppercase. Empty string is valid. | |
func IsUpperCase(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return str == strings.ToUpper(str) | |
} | |
// IsInt check if the string is an integer. Empty string is valid. | |
func IsInt(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return rxInt.MatchString(str) | |
} | |
// IsFloat check if the string is a float. | |
func IsFloat(str string) bool { | |
return str != "" && rxFloat.MatchString(str) | |
} | |
// IsDivisibleBy check if the string is a number that's divisible by another. | |
// If second argument is not valid integer or zero, it's return false. | |
// Otherwise, if first argument is not valid integer or zero, it's return true (Invalid string converts to zero). | |
func IsDivisibleBy(str, num string) bool { | |
f, _ := ToFloat(str) | |
p := int64(f) | |
q, _ := ToInt(num) | |
if q == 0 { | |
return false | |
} | |
return (p == 0) || (p%q == 0) | |
} | |
// IsNull check if the string is null. | |
func IsNull(str string) bool { | |
return len(str) == 0 | |
} | |
// IsByteLength check if the string's length (in bytes) falls in a range. | |
func IsByteLength(str string, min, max int) bool { | |
return len(str) >= min && len(str) <= max | |
} | |
// IsUUIDv3 check if the string is a UUID version 3. | |
func IsUUIDv3(str string) bool { | |
return rxUUID3.MatchString(str) | |
} | |
// IsUUIDv4 check if the string is a UUID version 4. | |
func IsUUIDv4(str string) bool { | |
return rxUUID4.MatchString(str) | |
} | |
// IsUUIDv5 check if the string is a UUID version 5. | |
func IsUUIDv5(str string) bool { | |
return rxUUID5.MatchString(str) | |
} | |
// IsUUID check if the string is a UUID (version 3, 4 or 5). | |
func IsUUID(str string) bool { | |
return rxUUID.MatchString(str) | |
} | |
// IsCreditCard check if the string is a credit card. | |
func IsCreditCard(str string) bool { | |
r, _ := regexp.Compile("[^0-9]+") | |
sanitized := r.ReplaceAll([]byte(str), []byte("")) | |
if !rxCreditCard.MatchString(string(sanitized)) { | |
return false | |
} | |
var sum int64 | |
var digit string | |
var tmpNum int64 | |
var shouldDouble bool | |
for i := len(sanitized) - 1; i >= 0; i-- { | |
digit = string(sanitized[i:(i + 1)]) | |
tmpNum, _ = ToInt(digit) | |
if shouldDouble { | |
tmpNum *= 2 | |
if tmpNum >= 10 { | |
sum += ((tmpNum % 10) + 1) | |
} else { | |
sum += tmpNum | |
} | |
} else { | |
sum += tmpNum | |
} | |
shouldDouble = !shouldDouble | |
} | |
if sum%10 == 0 { | |
return true | |
} | |
return false | |
} | |
// IsISBN10 check if the string is an ISBN version 10. | |
func IsISBN10(str string) bool { | |
return IsISBN(str, 10) | |
} | |
// IsISBN13 check if the string is an ISBN version 13. | |
func IsISBN13(str string) bool { | |
return IsISBN(str, 13) | |
} | |
// IsISBN check if the string is an ISBN (version 10 or 13). | |
// If version value is not equal to 10 or 13, it will be check both variants. | |
func IsISBN(str string, version int) bool { | |
r, _ := regexp.Compile("[\\s-]+") | |
sanitized := r.ReplaceAll([]byte(str), []byte("")) | |
var checksum int32 | |
var i int32 | |
if version == 10 { | |
if !rxISBN10.MatchString(string(sanitized)) { | |
return false | |
} | |
for i = 0; i < 9; i++ { | |
checksum += (i + 1) * int32(sanitized[i]-'0') | |
} | |
if sanitized[9] == 'X' { | |
checksum += 10 * 10 | |
} else { | |
checksum += 10 * int32(sanitized[9]-'0') | |
} | |
if checksum%11 == 0 { | |
return true | |
} | |
return false | |
} else if version == 13 { | |
if !rxISBN13.MatchString(string(sanitized)) { | |
return false | |
} | |
factor := []int32{1, 3} | |
for i = 0; i < 12; i++ { | |
checksum += factor[i%2] * int32(sanitized[i]-'0') | |
} | |
if (int32(sanitized[12]-'0'))-((10-(checksum%10))%10) == 0 { | |
return true | |
} | |
return false | |
} | |
return IsISBN(str, 10) || IsISBN(str, 13) | |
} | |
// IsJSON check if the string is valid JSON (note: uses json.Unmarshal). | |
func IsJSON(str string) bool { | |
var js json.RawMessage | |
return json.Unmarshal([]byte(str), &js) == nil | |
} | |
// IsMultibyte check if the string contains one or more multibyte chars. Empty string is valid. | |
func IsMultibyte(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return rxMultibyte.MatchString(str) | |
} | |
// IsASCII check if the string contains ASCII chars only. Empty string is valid. | |
func IsASCII(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return rxASCII.MatchString(str) | |
} | |
// IsPrintableASCII check if the string contains printable ASCII chars only. Empty string is valid. | |
func IsPrintableASCII(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return rxPrintableASCII.MatchString(str) | |
} | |
// IsFullWidth check if the string contains any full-width chars. Empty string is valid. | |
func IsFullWidth(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return rxFullWidth.MatchString(str) | |
} | |
// IsHalfWidth check if the string contains any half-width chars. Empty string is valid. | |
func IsHalfWidth(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return rxHalfWidth.MatchString(str) | |
} | |
// IsVariableWidth check if the string contains a mixture of full and half-width chars. Empty string is valid. | |
func IsVariableWidth(str string) bool { | |
if IsNull(str) { | |
return true | |
} | |
return rxHalfWidth.MatchString(str) && rxFullWidth.MatchString(str) | |
} | |
// IsBase64 check if a string is base64 encoded. | |
func IsBase64(str string) bool { | |
return rxBase64.MatchString(str) | |
} | |
// IsFilePath check is a string is Win or Unix file path and returns it's type. | |
func IsFilePath(str string) (bool, int) { | |
if rxWinPath.MatchString(str) { | |
//check windows path limit see: | |
// http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath | |
if len(str[3:]) > 32767 { | |
return false, Win | |
} | |
return true, Win | |
} else if rxUnixPath.MatchString(str) { | |
return true, Unix | |
} | |
return false, Unknown | |
} | |
// IsDataURI checks if a string is base64 encoded data URI such as an image | |
func IsDataURI(str string) bool { | |
dataURI := strings.Split(str, ",") | |
if !rxDataURI.MatchString(dataURI[0]) { | |
return false | |
} | |
return IsBase64(dataURI[1]) | |
} | |
// IsISO3166Alpha2 checks if a string is valid two-letter country code | |
func IsISO3166Alpha2(str string) bool { | |
for _, entry := range ISO3166List { | |
if str == entry.Alpha2Code { | |
return true | |
} | |
} | |
return false | |
} | |
// IsISO3166Alpha3 checks if a string is valid three-letter country code | |
func IsISO3166Alpha3(str string) bool { | |
for _, entry := range ISO3166List { | |
if str == entry.Alpha3Code { | |
return true | |
} | |
} | |
return false | |
} | |
// IsDNSName will validate the given string as a DNS name | |
func IsDNSName(str string) bool { | |
if str == "" || len(strings.Replace(str,".","",-1)) > 255 { | |
// constraints already violated | |
return false | |
} | |
return rxDNSName.MatchString(str) | |
} | |
// IsDialString validates the given string for usage with the various Dial() functions | |
func IsDialString(str string) bool { | |
if h, p, err := net.SplitHostPort(str); err == nil && h != "" && p != "" && (IsDNSName(h) || IsIP(h)) && IsPort(p) { | |
return true | |
} | |
return false | |
} | |
// IsIP checks if a string is either IP version 4 or 6. | |
func IsIP(str string) bool { | |
return net.ParseIP(str) != nil | |
} | |
// IsPort checks if a string represents a valid port | |
func IsPort(str string) bool { | |
if i, err := strconv.Atoi(str); err == nil && i > 0 && i < 65536 { | |
return true | |
} | |
return false | |
} | |
// IsIPv4 check if the string is an IP version 4. | |
func IsIPv4(str string) bool { | |
ip := net.ParseIP(str) | |
return ip != nil && strings.Contains(str, ".") | |
} | |
// IsIPv6 check if the string is an IP version 6. | |
func IsIPv6(str string) bool { | |
ip := net.ParseIP(str) | |
return ip != nil && strings.Contains(str, ":") | |
} | |
// IsMAC check if a string is valid MAC address. | |
// Possible MAC formats: | |
// 01:23:45:67:89:ab | |
// 01:23:45:67:89:ab:cd:ef | |
// 01-23-45-67-89-ab | |
// 01-23-45-67-89-ab-cd-ef | |
// 0123.4567.89ab | |
// 0123.4567.89ab.cdef | |
func IsMAC(str string) bool { | |
_, err := net.ParseMAC(str) | |
return err == nil | |
} | |
// IsMongoID check if the string is a valid hex-encoded representation of a MongoDB ObjectId. | |
func IsMongoID(str string) bool { | |
return rxHexadecimal.MatchString(str) && (len(str) == 24) | |
} | |
// IsLatitude check if a string is valid latitude. | |
func IsLatitude(str string) bool { | |
return rxLatitude.MatchString(str) | |
} | |
// IsLongitude check if a string is valid longitude. | |
func IsLongitude(str string) bool { | |
return rxLongitude.MatchString(str) | |
} | |
// ValidateStruct use tags for fields | |
func ValidateStruct(s interface{}) (bool, error) { | |
if s == nil { | |
return true, nil | |
} | |
result := true | |
var err error | |
val := reflect.ValueOf(s) | |
if val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr { | |
val = val.Elem() | |
} | |
// we only accept structs | |
if val.Kind() != reflect.Struct { | |
return false, fmt.Errorf("function only accepts structs; got %s", val.Kind()) | |
} | |
var errs Errors | |
for i := 0; i < val.NumField(); i++ { | |
valueField := val.Field(i) | |
typeField := val.Type().Field(i) | |
if typeField.PkgPath != "" { | |
continue // Private field | |
} | |
resultField, err := typeCheck(valueField, typeField) | |
if err != nil { | |
errs = append(errs, err) | |
} | |
result = result && resultField | |
} | |
if len(errs) > 0 { | |
err = errs | |
} | |
return result, err | |
} | |
// parseTag splits a struct field's tag into its | |
// comma-separated options. | |
func parseTag(tag string) tagOptions { | |
split := strings.SplitN(tag, ",", -1) | |
return tagOptions(split) | |
} | |
func isValidTag(s string) bool { | |
if s == "" { | |
return false | |
} | |
for _, c := range s { | |
switch { | |
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): | |
// Backslash and quote chars are reserved, but | |
// otherwise any punctuation chars are allowed | |
// in a tag name. | |
default: | |
if !unicode.IsLetter(c) && !unicode.IsDigit(c) { | |
return false | |
} | |
} | |
} | |
return true | |
} | |
// IsSSN will validate the given string as a U.S. Social Security Number | |
func IsSSN(str string) bool { | |
if str == "" || len(str) != 11 { | |
return false | |
} | |
return rxSSN.MatchString(str) | |
} | |
// IsSemver check if string is valid semantic version | |
func IsSemver(str string) bool { | |
return rxSemver.MatchString(str) | |
} | |
// ByteLength check string's length | |
func ByteLength(str string, params ...string) bool { | |
if len(params) == 2 { | |
min, _ := ToInt(params[0]) | |
max, _ := ToInt(params[1]) | |
return len(str) >= int(min) && len(str) <= int(max) | |
} | |
return false | |
} | |
// StringMatches checks if a string matches a given pattern. | |
func StringMatches(s string, params ...string) bool { | |
if len(params) == 1 { | |
pattern := params[0] | |
return Matches(s, pattern) | |
} | |
return false | |
} | |
// StringLength check string's length (including multi byte strings) | |
func StringLength(str string, params ...string) bool { | |
if len(params) == 2 { | |
strLength := utf8.RuneCountInString(str) | |
min, _ := ToInt(params[0]) | |
max, _ := ToInt(params[1]) | |
return strLength >= int(min) && strLength <= int(max) | |
} | |
return false | |
} | |
// Contains returns whether checks that a comma-separated list of options | |
// contains a particular substr flag. substr must be surrounded by a | |
// string boundary or commas. | |
func (opts tagOptions) contains(optionName string) bool { | |
for i := range opts { | |
tagOpt := opts[i] | |
if tagOpt == optionName { | |
return true | |
} | |
} | |
return false | |
} | |
func checkRequired(v reflect.Value, t reflect.StructField, options tagOptions) (bool, error) { | |
if options.contains("required") { | |
err := fmt.Errorf("non zero value required") | |
return false, Error{t.Name, err} | |
} else if fieldsRequiredByDefault && !options.contains("optional") { | |
err := fmt.Errorf("All fields are required to at least have one validation defined") | |
return false, Error{t.Name, err} | |
} | |
// not required and empty is valid | |
return true, nil | |
} | |
func typeCheck(v reflect.Value, t reflect.StructField) (bool, error) { | |
if !v.IsValid() { | |
return false, nil | |
} | |
tag := t.Tag.Get(tagName) | |
// Check if the field should be ignored | |
switch tag { | |
case "": | |
if !fieldsRequiredByDefault { | |
return true, nil | |
} | |
err := fmt.Errorf("All fields are required to at least have one validation defined") | |
return false, Error{t.Name, err} | |
case "-": | |
return true, nil | |
} | |
options := parseTag(tag) | |
for i := range options { | |
tagOpt := options[i] | |
if ok := isValidTag(tagOpt); !ok { | |
continue | |
} | |
if validatefunc, ok := CustomTypeTagMap[tagOpt]; ok { | |
options = append(options[:i], options[i+1:]...) // we found our custom validator, so remove it from the options | |
if result := validatefunc(v.Interface()); !result { | |
return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", fmt.Sprint(v), tagOpt)} | |
} | |
return true, nil | |
} | |
} | |
if isEmptyValue(v) { | |
// an empty value is not validated, check only required | |
return checkRequired(v, t, options) | |
} | |
switch v.Kind() { | |
case reflect.Bool, | |
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, | |
reflect.String: | |
// for each tag option check the map of validator functions | |
for i := range options { | |
tagOpt := options[i] | |
negate := false | |
// Check wether the tag looks like '!something' or 'something' | |
if len(tagOpt) > 0 && tagOpt[0] == '!' { | |
tagOpt = string(tagOpt[1:]) | |
negate = true | |
} | |
if ok := isValidTag(tagOpt); !ok { | |
err := fmt.Errorf("Unknown Validator %s", tagOpt) | |
return false, Error{t.Name, err} | |
} | |
// Check for param validators | |
for key, value := range ParamTagRegexMap { | |
ps := value.FindStringSubmatch(tagOpt) | |
if len(ps) > 0 { | |
if validatefunc, ok := ParamTagMap[key]; ok { | |
switch v.Kind() { | |
case reflect.String: | |
field := fmt.Sprint(v) // make value into string, then validate with regex | |
if result := validatefunc(field, ps[1:]...); !result && !negate || result && negate { | |
var err error | |
if !negate { | |
err = fmt.Errorf("%s does not validate as %s", field, tagOpt) | |
} else { | |
err = fmt.Errorf("%s does validate as %s", field, tagOpt) | |
} | |
return false, Error{t.Name, err} | |
} | |
default: | |
//Not Yet Supported Types (Fail here!) | |
err := fmt.Errorf("Validator %s doesn't support kind %s", tagOpt, v.Kind()) | |
return false, Error{t.Name, err} | |
} | |
} | |
} | |
} | |
if validatefunc, ok := TagMap[tagOpt]; ok { | |
switch v.Kind() { | |
case reflect.String: | |
field := fmt.Sprint(v) // make value into string, then validate with regex | |
if result := validatefunc(field); !result && !negate || result && negate { | |
var err error | |
if !negate { | |
err = fmt.Errorf("%s does not validate as %s", field, tagOpt) | |
} else { | |
err = fmt.Errorf("%s does validate as %s", field, tagOpt) | |
} | |
return false, Error{t.Name, err} | |
} | |
default: | |
//Not Yet Supported Types (Fail here!) | |
err := fmt.Errorf("Validator %s doesn't support kind %s for value %v", tagOpt, v.Kind(), v) | |
return false, Error{t.Name, err} | |
} | |
} | |
} | |
return true, nil | |
case reflect.Map: | |
if v.Type().Key().Kind() != reflect.String { | |
return false, &UnsupportedTypeError{v.Type()} | |
} | |
var sv stringValues | |
sv = v.MapKeys() | |
sort.Sort(sv) | |
result := true | |
for _, k := range sv { | |
resultItem, err := ValidateStruct(v.MapIndex(k).Interface()) | |
if err != nil { | |
return false, err | |
} | |
result = result && resultItem | |
} | |
return result, nil | |
case reflect.Slice: | |
result := true | |
for i := 0; i < v.Len(); i++ { | |
var resultItem bool | |
var err error | |
if v.Index(i).Kind() != reflect.Struct { | |
resultItem, err = typeCheck(v.Index(i), t) | |
if err != nil { | |
return false, err | |
} | |
} else { | |
resultItem, err = ValidateStruct(v.Index(i).Interface()) | |
if err != nil { | |
return false, err | |
} | |
} | |
result = result && resultItem | |
} | |
return result, nil | |
case reflect.Array: | |
result := true | |
for i := 0; i < v.Len(); i++ { | |
var resultItem bool | |
var err error | |
if v.Index(i).Kind() != reflect.Struct { | |
resultItem, err = typeCheck(v.Index(i), t) | |
if err != nil { | |
return false, err | |
} | |
} else { | |
resultItem, err = ValidateStruct(v.Index(i).Interface()) | |
if err != nil { | |
return false, err | |
} | |
} | |
result = result && resultItem | |
} | |
return result, nil | |
case reflect.Interface: | |
// If the value is an interface then encode its element | |
if v.IsNil() { | |
return true, nil | |
} | |
return ValidateStruct(v.Interface()) | |
case reflect.Ptr: | |
// If the value is a pointer then check its element | |
if v.IsNil() { | |
return true, nil | |
} | |
return typeCheck(v.Elem(), t) | |
case reflect.Struct: | |
return ValidateStruct(v.Interface()) | |
default: | |
return false, &UnsupportedTypeError{v.Type()} | |
} | |
} | |
func isEmptyValue(v reflect.Value) bool { | |
switch v.Kind() { | |
case reflect.String, reflect.Array: | |
return v.Len() == 0 | |
case reflect.Map, reflect.Slice: | |
return v.Len() == 0 || v.IsNil() | |
case reflect.Bool: | |
return !v.Bool() | |
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
return v.Int() == 0 | |
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | |
return v.Uint() == 0 | |
case reflect.Float32, reflect.Float64: | |
return v.Float() == 0 | |
case reflect.Interface, reflect.Ptr: | |
return v.IsNil() | |
} | |
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) | |
} | |
// ErrorByField returns error for specified field of the struct | |
// validated by ValidateStruct or empty string if there are no errors | |
// or this field doesn't exists or doesn't have any errors. | |
func ErrorByField(e error, field string) string { | |
if e == nil { | |
return "" | |
} | |
return ErrorsByField(e)[field] | |
} | |
// ErrorsByField returns map of errors of the struct validated | |
// by ValidateStruct or empty map if there are no errors. | |
func ErrorsByField(e error) map[string]string { | |
m := make(map[string]string) | |
if e == nil { | |
return m | |
} | |
// prototype for ValidateStruct | |
switch e.(type) { | |
case Error: | |
m[e.(Error).Name] = e.(Error).Err.Error() | |
case Errors: | |
for _, item := range e.(Errors).Errors() { | |
m[item.(Error).Name] = item.(Error).Err.Error() | |
} | |
} | |
return m | |
} | |
// Error returns string equivalent for reflect.Type | |
func (e *UnsupportedTypeError) Error() string { | |
return "validator: unsupported type: " + e.Type.String() | |
} | |
func (sv stringValues) Len() int { return len(sv) } | |
func (sv stringValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } | |
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() } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Do you have any info regarding performance with such big regular expressions?