Skip to content

Instantly share code, notes, and snippets.

@avevlad
Forked from xigang/golang-patterns
Created June 25, 2023 15:55
Show Gist options
  • Save avevlad/bc1dcf33085f2bf35d0985bf0ba9ccc6 to your computer and use it in GitHub Desktop.
Save avevlad/bc1dcf33085f2bf35d0985bf0ba9ccc6 to your computer and use it in GitHub Desktop.

Revisions

  1. @xigang xigang revised this gist Mar 4, 2016. 2 changed files with 895 additions and 898 deletions.
    899 changes: 1 addition & 898 deletions golang-patterns
    Original file line number Diff line number Diff line change
    @@ -78,901 +78,4 @@ var (
    rxWinPath = regexp.MustCompile(WinPath)
    rxUnixPath = regexp.MustCompile(UnixPath)
    rxSemver = regexp.MustCompile(Semver)
    )

    //---------------------------------------------------------------------------------
    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() }

    )
    894 changes: 894 additions & 0 deletions validator.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,894 @@
    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() }
  2. @xigang xigang revised this gist Mar 4, 2016. 1 changed file with 897 additions and 0 deletions.
    897 changes: 897 additions & 0 deletions golang-patterns
    Original file line number Diff line number Diff line change
    @@ -79,3 +79,900 @@ var (
    rxUnixPath = regexp.MustCompile(UnixPath)
    rxSemver = regexp.MustCompile(Semver)
    )

    //---------------------------------------------------------------------------------
    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() }

  3. @xigang xigang created this gist Mar 4, 2016.
    81 changes: 81 additions & 0 deletions golang-patterns
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,81 @@
    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)
    )