1// Package govalidator is package of validators and sanitizers for strings, structs and collections. 2package govalidator 3 4import ( 5 "bytes" 6 "crypto/rsa" 7 "crypto/x509" 8 "encoding/base64" 9 "encoding/json" 10 "encoding/pem" 11 "fmt" 12 "io/ioutil" 13 "net" 14 "net/url" 15 "reflect" 16 "regexp" 17 "sort" 18 "strconv" 19 "strings" 20 "time" 21 "unicode" 22 "unicode/utf8" 23) 24 25var ( 26 fieldsRequiredByDefault bool 27 nilPtrAllowedByRequired = false 28 notNumberRegexp = regexp.MustCompile("[^0-9]+") 29 whiteSpacesAndMinus = regexp.MustCompile(`[\s-]+`) 30 paramsRegexp = regexp.MustCompile(`\(.*\)$`) 31) 32 33const maxURLRuneCount = 2083 34const minURLRuneCount = 3 35const RF3339WithoutZone = "2006-01-02T15:04:05" 36 37// SetFieldsRequiredByDefault causes validation to fail when struct fields 38// do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`). 39// This struct definition will fail govalidator.ValidateStruct() (and the field values do not matter): 40// type exampleStruct struct { 41// Name string `` 42// Email string `valid:"email"` 43// This, however, will only fail when Email is empty or an invalid email address: 44// type exampleStruct2 struct { 45// Name string `valid:"-"` 46// Email string `valid:"email"` 47// Lastly, this will only fail when Email is an invalid email address but not when it's empty: 48// type exampleStruct2 struct { 49// Name string `valid:"-"` 50// Email string `valid:"email,optional"` 51func SetFieldsRequiredByDefault(value bool) { 52 fieldsRequiredByDefault = value 53} 54 55// SetNilPtrAllowedByRequired causes validation to pass for nil ptrs when a field is set to required. 56// The validation will still reject ptr fields in their zero value state. Example with this enabled: 57// type exampleStruct struct { 58// Name *string `valid:"required"` 59// With `Name` set to "", this will be considered invalid input and will cause a validation error. 60// With `Name` set to nil, this will be considered valid by validation. 61// By default this is disabled. 62func SetNilPtrAllowedByRequired(value bool) { 63 nilPtrAllowedByRequired = value 64} 65 66// IsEmail check if the string is an email. 67func IsEmail(str string) bool { 68 // TODO uppercase letters are not supported 69 return rxEmail.MatchString(str) 70} 71 72// IsExistingEmail check if the string is an email of existing domain 73func IsExistingEmail(email string) bool { 74 75 if len(email) < 6 || len(email) > 254 { 76 return false 77 } 78 at := strings.LastIndex(email, "@") 79 if at <= 0 || at > len(email)-3 { 80 return false 81 } 82 user := email[:at] 83 host := email[at+1:] 84 if len(user) > 64 { 85 return false 86 } 87 if userDotRegexp.MatchString(user) || !userRegexp.MatchString(user) || !hostRegexp.MatchString(host) { 88 return false 89 } 90 switch host { 91 case "localhost", "example.com": 92 return true 93 } 94 if _, err := net.LookupMX(host); err != nil { 95 if _, err := net.LookupIP(host); err != nil { 96 return false 97 } 98 } 99 100 return true 101} 102 103// IsURL check if the string is an URL. 104func IsURL(str string) bool { 105 if str == "" || utf8.RuneCountInString(str) >= maxURLRuneCount || len(str) <= minURLRuneCount || strings.HasPrefix(str, ".") { 106 return false 107 } 108 strTemp := str 109 if strings.Contains(str, ":") && !strings.Contains(str, "://") { 110 // support no indicated urlscheme but with colon for port number 111 // http:// is appended so url.Parse will succeed, strTemp used so it does not impact rxURL.MatchString 112 strTemp = "http://" + str 113 } 114 u, err := url.Parse(strTemp) 115 if err != nil { 116 return false 117 } 118 if strings.HasPrefix(u.Host, ".") { 119 return false 120 } 121 if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) { 122 return false 123 } 124 return rxURL.MatchString(str) 125} 126 127// IsRequestURL check if the string rawurl, assuming 128// it was received in an HTTP request, is a valid 129// URL confirm to RFC 3986 130func IsRequestURL(rawurl string) bool { 131 url, err := url.ParseRequestURI(rawurl) 132 if err != nil { 133 return false //Couldn't even parse the rawurl 134 } 135 if len(url.Scheme) == 0 { 136 return false //No Scheme found 137 } 138 return true 139} 140 141// IsRequestURI check if the string rawurl, assuming 142// it was received in an HTTP request, is an 143// absolute URI or an absolute path. 144func IsRequestURI(rawurl string) bool { 145 _, err := url.ParseRequestURI(rawurl) 146 return err == nil 147} 148 149// IsAlpha check if the string contains only letters (a-zA-Z). Empty string is valid. 150func IsAlpha(str string) bool { 151 if IsNull(str) { 152 return true 153 } 154 return rxAlpha.MatchString(str) 155} 156 157//IsUTFLetter check if the string contains only unicode letter characters. 158//Similar to IsAlpha but for all languages. Empty string is valid. 159func IsUTFLetter(str string) bool { 160 if IsNull(str) { 161 return true 162 } 163 164 for _, c := range str { 165 if !unicode.IsLetter(c) { 166 return false 167 } 168 } 169 return true 170 171} 172 173// IsAlphanumeric check if the string contains only letters and numbers. Empty string is valid. 174func IsAlphanumeric(str string) bool { 175 if IsNull(str) { 176 return true 177 } 178 return rxAlphanumeric.MatchString(str) 179} 180 181// IsUTFLetterNumeric check if the string contains only unicode letters and numbers. Empty string is valid. 182func IsUTFLetterNumeric(str string) bool { 183 if IsNull(str) { 184 return true 185 } 186 for _, c := range str { 187 if !unicode.IsLetter(c) && !unicode.IsNumber(c) { //letters && numbers are ok 188 return false 189 } 190 } 191 return true 192 193} 194 195// IsNumeric check if the string contains only numbers. Empty string is valid. 196func IsNumeric(str string) bool { 197 if IsNull(str) { 198 return true 199 } 200 return rxNumeric.MatchString(str) 201} 202 203// IsUTFNumeric check if the string contains only unicode numbers of any kind. 204// Numbers can be 0-9 but also Fractions ¾,Roman Ⅸ and Hangzhou 〩. Empty string is valid. 205func IsUTFNumeric(str string) bool { 206 if IsNull(str) { 207 return true 208 } 209 if strings.IndexAny(str, "+-") > 0 { 210 return false 211 } 212 if len(str) > 1 { 213 str = strings.TrimPrefix(str, "-") 214 str = strings.TrimPrefix(str, "+") 215 } 216 for _, c := range str { 217 if !unicode.IsNumber(c) { //numbers && minus sign are ok 218 return false 219 } 220 } 221 return true 222 223} 224 225// IsUTFDigit check if the string contains only unicode radix-10 decimal digits. Empty string is valid. 226func IsUTFDigit(str string) bool { 227 if IsNull(str) { 228 return true 229 } 230 if strings.IndexAny(str, "+-") > 0 { 231 return false 232 } 233 if len(str) > 1 { 234 str = strings.TrimPrefix(str, "-") 235 str = strings.TrimPrefix(str, "+") 236 } 237 for _, c := range str { 238 if !unicode.IsDigit(c) { //digits && minus sign are ok 239 return false 240 } 241 } 242 return true 243 244} 245 246// IsHexadecimal check if the string is a hexadecimal number. 247func IsHexadecimal(str string) bool { 248 return rxHexadecimal.MatchString(str) 249} 250 251// IsHexcolor check if the string is a hexadecimal color. 252func IsHexcolor(str string) bool { 253 return rxHexcolor.MatchString(str) 254} 255 256// IsRGBcolor check if the string is a valid RGB color in form rgb(RRR, GGG, BBB). 257func IsRGBcolor(str string) bool { 258 return rxRGBcolor.MatchString(str) 259} 260 261// IsLowerCase check if the string is lowercase. Empty string is valid. 262func IsLowerCase(str string) bool { 263 if IsNull(str) { 264 return true 265 } 266 return str == strings.ToLower(str) 267} 268 269// IsUpperCase check if the string is uppercase. Empty string is valid. 270func IsUpperCase(str string) bool { 271 if IsNull(str) { 272 return true 273 } 274 return str == strings.ToUpper(str) 275} 276 277// HasLowerCase check if the string contains at least 1 lowercase. Empty string is valid. 278func HasLowerCase(str string) bool { 279 if IsNull(str) { 280 return true 281 } 282 return rxHasLowerCase.MatchString(str) 283} 284 285// HasUpperCase check if the string contians as least 1 uppercase. Empty string is valid. 286func HasUpperCase(str string) bool { 287 if IsNull(str) { 288 return true 289 } 290 return rxHasUpperCase.MatchString(str) 291} 292 293// IsInt check if the string is an integer. Empty string is valid. 294func IsInt(str string) bool { 295 if IsNull(str) { 296 return true 297 } 298 return rxInt.MatchString(str) 299} 300 301// IsFloat check if the string is a float. 302func IsFloat(str string) bool { 303 return str != "" && rxFloat.MatchString(str) 304} 305 306// IsDivisibleBy check if the string is a number that's divisible by another. 307// If second argument is not valid integer or zero, it's return false. 308// Otherwise, if first argument is not valid integer or zero, it's return true (Invalid string converts to zero). 309func IsDivisibleBy(str, num string) bool { 310 f, _ := ToFloat(str) 311 p := int64(f) 312 q, _ := ToInt(num) 313 if q == 0 { 314 return false 315 } 316 return (p == 0) || (p%q == 0) 317} 318 319// IsNull check if the string is null. 320func IsNull(str string) bool { 321 return len(str) == 0 322} 323 324// HasWhitespaceOnly checks the string only contains whitespace 325func HasWhitespaceOnly(str string) bool { 326 return len(str) > 0 && rxHasWhitespaceOnly.MatchString(str) 327} 328 329// HasWhitespace checks if the string contains any whitespace 330func HasWhitespace(str string) bool { 331 return len(str) > 0 && rxHasWhitespace.MatchString(str) 332} 333 334// IsByteLength check if the string's length (in bytes) falls in a range. 335func IsByteLength(str string, min, max int) bool { 336 return len(str) >= min && len(str) <= max 337} 338 339// IsUUIDv3 check if the string is a UUID version 3. 340func IsUUIDv3(str string) bool { 341 return rxUUID3.MatchString(str) 342} 343 344// IsUUIDv4 check if the string is a UUID version 4. 345func IsUUIDv4(str string) bool { 346 return rxUUID4.MatchString(str) 347} 348 349// IsUUIDv5 check if the string is a UUID version 5. 350func IsUUIDv5(str string) bool { 351 return rxUUID5.MatchString(str) 352} 353 354// IsUUID check if the string is a UUID (version 3, 4 or 5). 355func IsUUID(str string) bool { 356 return rxUUID.MatchString(str) 357} 358 359// IsCreditCard check if the string is a credit card. 360func IsCreditCard(str string) bool { 361 sanitized := notNumberRegexp.ReplaceAllString(str, "") 362 if !rxCreditCard.MatchString(sanitized) { 363 return false 364 } 365 var sum int64 366 var digit string 367 var tmpNum int64 368 var shouldDouble bool 369 for i := len(sanitized) - 1; i >= 0; i-- { 370 digit = sanitized[i:(i + 1)] 371 tmpNum, _ = ToInt(digit) 372 if shouldDouble { 373 tmpNum *= 2 374 if tmpNum >= 10 { 375 sum += ((tmpNum % 10) + 1) 376 } else { 377 sum += tmpNum 378 } 379 } else { 380 sum += tmpNum 381 } 382 shouldDouble = !shouldDouble 383 } 384 385 return sum%10 == 0 386} 387 388// IsISBN10 check if the string is an ISBN version 10. 389func IsISBN10(str string) bool { 390 return IsISBN(str, 10) 391} 392 393// IsISBN13 check if the string is an ISBN version 13. 394func IsISBN13(str string) bool { 395 return IsISBN(str, 13) 396} 397 398// IsISBN check if the string is an ISBN (version 10 or 13). 399// If version value is not equal to 10 or 13, it will be check both variants. 400func IsISBN(str string, version int) bool { 401 sanitized := whiteSpacesAndMinus.ReplaceAllString(str, "") 402 var checksum int32 403 var i int32 404 if version == 10 { 405 if !rxISBN10.MatchString(sanitized) { 406 return false 407 } 408 for i = 0; i < 9; i++ { 409 checksum += (i + 1) * int32(sanitized[i]-'0') 410 } 411 if sanitized[9] == 'X' { 412 checksum += 10 * 10 413 } else { 414 checksum += 10 * int32(sanitized[9]-'0') 415 } 416 if checksum%11 == 0 { 417 return true 418 } 419 return false 420 } else if version == 13 { 421 if !rxISBN13.MatchString(sanitized) { 422 return false 423 } 424 factor := []int32{1, 3} 425 for i = 0; i < 12; i++ { 426 checksum += factor[i%2] * int32(sanitized[i]-'0') 427 } 428 return (int32(sanitized[12]-'0'))-((10-(checksum%10))%10) == 0 429 } 430 return IsISBN(str, 10) || IsISBN(str, 13) 431} 432 433// IsJSON check if the string is valid JSON (note: uses json.Unmarshal). 434func IsJSON(str string) bool { 435 var js json.RawMessage 436 return json.Unmarshal([]byte(str), &js) == nil 437} 438 439// IsMultibyte check if the string contains one or more multibyte chars. Empty string is valid. 440func IsMultibyte(str string) bool { 441 if IsNull(str) { 442 return true 443 } 444 return rxMultibyte.MatchString(str) 445} 446 447// IsASCII check if the string contains ASCII chars only. Empty string is valid. 448func IsASCII(str string) bool { 449 if IsNull(str) { 450 return true 451 } 452 return rxASCII.MatchString(str) 453} 454 455// IsPrintableASCII check if the string contains printable ASCII chars only. Empty string is valid. 456func IsPrintableASCII(str string) bool { 457 if IsNull(str) { 458 return true 459 } 460 return rxPrintableASCII.MatchString(str) 461} 462 463// IsFullWidth check if the string contains any full-width chars. Empty string is valid. 464func IsFullWidth(str string) bool { 465 if IsNull(str) { 466 return true 467 } 468 return rxFullWidth.MatchString(str) 469} 470 471// IsHalfWidth check if the string contains any half-width chars. Empty string is valid. 472func IsHalfWidth(str string) bool { 473 if IsNull(str) { 474 return true 475 } 476 return rxHalfWidth.MatchString(str) 477} 478 479// IsVariableWidth check if the string contains a mixture of full and half-width chars. Empty string is valid. 480func IsVariableWidth(str string) bool { 481 if IsNull(str) { 482 return true 483 } 484 return rxHalfWidth.MatchString(str) && rxFullWidth.MatchString(str) 485} 486 487// IsBase64 check if a string is base64 encoded. 488func IsBase64(str string) bool { 489 return rxBase64.MatchString(str) 490} 491 492// IsFilePath check is a string is Win or Unix file path and returns it's type. 493func IsFilePath(str string) (bool, int) { 494 if rxWinPath.MatchString(str) { 495 //check windows path limit see: 496 // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath 497 if len(str[3:]) > 32767 { 498 return false, Win 499 } 500 return true, Win 501 } else if rxUnixPath.MatchString(str) { 502 return true, Unix 503 } 504 return false, Unknown 505} 506 507// IsDataURI checks if a string is base64 encoded data URI such as an image 508func IsDataURI(str string) bool { 509 dataURI := strings.Split(str, ",") 510 if !rxDataURI.MatchString(dataURI[0]) { 511 return false 512 } 513 return IsBase64(dataURI[1]) 514} 515 516// IsISO3166Alpha2 checks if a string is valid two-letter country code 517func IsISO3166Alpha2(str string) bool { 518 for _, entry := range ISO3166List { 519 if str == entry.Alpha2Code { 520 return true 521 } 522 } 523 return false 524} 525 526// IsISO3166Alpha3 checks if a string is valid three-letter country code 527func IsISO3166Alpha3(str string) bool { 528 for _, entry := range ISO3166List { 529 if str == entry.Alpha3Code { 530 return true 531 } 532 } 533 return false 534} 535 536// IsISO693Alpha2 checks if a string is valid two-letter language code 537func IsISO693Alpha2(str string) bool { 538 for _, entry := range ISO693List { 539 if str == entry.Alpha2Code { 540 return true 541 } 542 } 543 return false 544} 545 546// IsISO693Alpha3b checks if a string is valid three-letter language code 547func IsISO693Alpha3b(str string) bool { 548 for _, entry := range ISO693List { 549 if str == entry.Alpha3bCode { 550 return true 551 } 552 } 553 return false 554} 555 556// IsDNSName will validate the given string as a DNS name 557func IsDNSName(str string) bool { 558 if str == "" || len(strings.Replace(str, ".", "", -1)) > 255 { 559 // constraints already violated 560 return false 561 } 562 return !IsIP(str) && rxDNSName.MatchString(str) 563} 564 565// IsHash checks if a string is a hash of type algorithm. 566// Algorithm is one of ['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128', 'tiger160', 'tiger192', 'crc32', 'crc32b'] 567func IsHash(str string, algorithm string) bool { 568 len := "0" 569 algo := strings.ToLower(algorithm) 570 571 if algo == "crc32" || algo == "crc32b" { 572 len = "8" 573 } else if algo == "md5" || algo == "md4" || algo == "ripemd128" || algo == "tiger128" { 574 len = "32" 575 } else if algo == "sha1" || algo == "ripemd160" || algo == "tiger160" { 576 len = "40" 577 } else if algo == "tiger192" { 578 len = "48" 579 } else if algo == "sha256" { 580 len = "64" 581 } else if algo == "sha384" { 582 len = "96" 583 } else if algo == "sha512" { 584 len = "128" 585 } else { 586 return false 587 } 588 589 return Matches(str, "^[a-f0-9]{"+len+"}$") 590} 591 592// IsDialString validates the given string for usage with the various Dial() functions 593func IsDialString(str string) bool { 594 595 if h, p, err := net.SplitHostPort(str); err == nil && h != "" && p != "" && (IsDNSName(h) || IsIP(h)) && IsPort(p) { 596 return true 597 } 598 599 return false 600} 601 602// IsIP checks if a string is either IP version 4 or 6. 603func IsIP(str string) bool { 604 return net.ParseIP(str) != nil 605} 606 607// IsPort checks if a string represents a valid port 608func IsPort(str string) bool { 609 if i, err := strconv.Atoi(str); err == nil && i > 0 && i < 65536 { 610 return true 611 } 612 return false 613} 614 615// IsIPv4 check if the string is an IP version 4. 616func IsIPv4(str string) bool { 617 ip := net.ParseIP(str) 618 return ip != nil && strings.Contains(str, ".") 619} 620 621// IsIPv6 check if the string is an IP version 6. 622func IsIPv6(str string) bool { 623 ip := net.ParseIP(str) 624 return ip != nil && strings.Contains(str, ":") 625} 626 627// IsCIDR check if the string is an valid CIDR notiation (IPV4 & IPV6) 628func IsCIDR(str string) bool { 629 _, _, err := net.ParseCIDR(str) 630 return err == nil 631} 632 633// IsMAC check if a string is valid MAC address. 634// Possible MAC formats: 635// 01:23:45:67:89:ab 636// 01:23:45:67:89:ab:cd:ef 637// 01-23-45-67-89-ab 638// 01-23-45-67-89-ab-cd-ef 639// 0123.4567.89ab 640// 0123.4567.89ab.cdef 641func IsMAC(str string) bool { 642 _, err := net.ParseMAC(str) 643 return err == nil 644} 645 646// IsHost checks if the string is a valid IP (both v4 and v6) or a valid DNS name 647func IsHost(str string) bool { 648 return IsIP(str) || IsDNSName(str) 649} 650 651// IsMongoID check if the string is a valid hex-encoded representation of a MongoDB ObjectId. 652func IsMongoID(str string) bool { 653 return rxHexadecimal.MatchString(str) && (len(str) == 24) 654} 655 656// IsLatitude check if a string is valid latitude. 657func IsLatitude(str string) bool { 658 return rxLatitude.MatchString(str) 659} 660 661// IsLongitude check if a string is valid longitude. 662func IsLongitude(str string) bool { 663 return rxLongitude.MatchString(str) 664} 665 666// IsRsaPublicKey check if a string is valid public key with provided length 667func IsRsaPublicKey(str string, keylen int) bool { 668 bb := bytes.NewBufferString(str) 669 pemBytes, err := ioutil.ReadAll(bb) 670 if err != nil { 671 return false 672 } 673 block, _ := pem.Decode(pemBytes) 674 if block != nil && block.Type != "PUBLIC KEY" { 675 return false 676 } 677 var der []byte 678 679 if block != nil { 680 der = block.Bytes 681 } else { 682 der, err = base64.StdEncoding.DecodeString(str) 683 if err != nil { 684 return false 685 } 686 } 687 688 key, err := x509.ParsePKIXPublicKey(der) 689 if err != nil { 690 return false 691 } 692 pubkey, ok := key.(*rsa.PublicKey) 693 if !ok { 694 return false 695 } 696 bitlen := len(pubkey.N.Bytes()) * 8 697 return bitlen == int(keylen) 698} 699 700func toJSONName(tag string) string { 701 if tag == "" { 702 return "" 703 } 704 705 // JSON name always comes first. If there's no options then split[0] is 706 // JSON name, if JSON name is not set, then split[0] is an empty string. 707 split := strings.SplitN(tag, ",", 2) 708 709 name := split[0] 710 711 // However it is possible that the field is skipped when 712 // (de-)serializing from/to JSON, in which case assume that there is no 713 // tag name to use 714 if name == "-" { 715 return "" 716 } 717 return name 718} 719 720func PrependPathToErrors(err error, path string) error { 721 switch err2 := err.(type) { 722 case Error: 723 err2.Path = append([]string{path}, err2.Path...) 724 return err2 725 case Errors: 726 errors := err2.Errors() 727 for i, err3 := range errors { 728 errors[i] = PrependPathToErrors(err3, path) 729 } 730 return err2 731 } 732 fmt.Println(err) 733 return err 734} 735 736// ValidateStruct use tags for fields. 737// result will be equal to `false` if there are any errors. 738func ValidateStruct(s interface{}) (bool, error) { 739 if s == nil { 740 return true, nil 741 } 742 result := true 743 var err error 744 val := reflect.ValueOf(s) 745 if val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr { 746 val = val.Elem() 747 } 748 // we only accept structs 749 if val.Kind() != reflect.Struct { 750 return false, fmt.Errorf("function only accepts structs; got %s", val.Kind()) 751 } 752 var errs Errors 753 for i := 0; i < val.NumField(); i++ { 754 valueField := val.Field(i) 755 typeField := val.Type().Field(i) 756 if typeField.PkgPath != "" { 757 continue // Private field 758 } 759 structResult := true 760 if valueField.Kind() == reflect.Interface { 761 valueField = valueField.Elem() 762 } 763 if (valueField.Kind() == reflect.Struct || 764 (valueField.Kind() == reflect.Ptr && valueField.Elem().Kind() == reflect.Struct)) && 765 typeField.Tag.Get(tagName) != "-" { 766 var err error 767 structResult, err = ValidateStruct(valueField.Interface()) 768 if err != nil { 769 err = PrependPathToErrors(err, typeField.Name) 770 errs = append(errs, err) 771 } 772 } 773 resultField, err2 := typeCheck(valueField, typeField, val, nil) 774 if err2 != nil { 775 776 // Replace structure name with JSON name if there is a tag on the variable 777 jsonTag := toJSONName(typeField.Tag.Get("json")) 778 if jsonTag != "" { 779 switch jsonError := err2.(type) { 780 case Error: 781 jsonError.Name = jsonTag 782 err2 = jsonError 783 case Errors: 784 for i2, err3 := range jsonError { 785 switch customErr := err3.(type) { 786 case Error: 787 customErr.Name = jsonTag 788 jsonError[i2] = customErr 789 } 790 } 791 792 err2 = jsonError 793 } 794 } 795 796 errs = append(errs, err2) 797 } 798 result = result && resultField && structResult 799 } 800 if len(errs) > 0 { 801 err = errs 802 } 803 return result, err 804} 805 806// parseTagIntoMap parses a struct tag `valid:required~Some error message,length(2|3)` into map[string]string{"required": "Some error message", "length(2|3)": ""} 807func parseTagIntoMap(tag string) tagOptionsMap { 808 optionsMap := make(tagOptionsMap) 809 options := strings.Split(tag, ",") 810 811 for i, option := range options { 812 option = strings.TrimSpace(option) 813 814 validationOptions := strings.Split(option, "~") 815 if !isValidTag(validationOptions[0]) { 816 continue 817 } 818 if len(validationOptions) == 2 { 819 optionsMap[validationOptions[0]] = tagOption{validationOptions[0], validationOptions[1], i} 820 } else { 821 optionsMap[validationOptions[0]] = tagOption{validationOptions[0], "", i} 822 } 823 } 824 return optionsMap 825} 826 827func isValidTag(s string) bool { 828 if s == "" { 829 return false 830 } 831 for _, c := range s { 832 switch { 833 case strings.ContainsRune("\\'\"!#$%&()*+-./:<=>?@[]^_{|}~ ", c): 834 // Backslash and quote chars are reserved, but 835 // otherwise any punctuation chars are allowed 836 // in a tag name. 837 default: 838 if !unicode.IsLetter(c) && !unicode.IsDigit(c) { 839 return false 840 } 841 } 842 } 843 return true 844} 845 846// IsSSN will validate the given string as a U.S. Social Security Number 847func IsSSN(str string) bool { 848 if str == "" || len(str) != 11 { 849 return false 850 } 851 return rxSSN.MatchString(str) 852} 853 854// IsSemver check if string is valid semantic version 855func IsSemver(str string) bool { 856 return rxSemver.MatchString(str) 857} 858 859// IsTime check if string is valid according to given format 860func IsTime(str string, format string) bool { 861 _, err := time.Parse(format, str) 862 return err == nil 863} 864 865// IsRFC3339 check if string is valid timestamp value according to RFC3339 866func IsRFC3339(str string) bool { 867 return IsTime(str, time.RFC3339) 868} 869 870// IsRFC3339WithoutZone check if string is valid timestamp value according to RFC3339 which excludes the timezone. 871func IsRFC3339WithoutZone(str string) bool { 872 return IsTime(str, RF3339WithoutZone) 873} 874 875// IsISO4217 check if string is valid ISO currency code 876func IsISO4217(str string) bool { 877 for _, currency := range ISO4217List { 878 if str == currency { 879 return true 880 } 881 } 882 883 return false 884} 885 886// ByteLength check string's length 887func ByteLength(str string, params ...string) bool { 888 if len(params) == 2 { 889 min, _ := ToInt(params[0]) 890 max, _ := ToInt(params[1]) 891 return len(str) >= int(min) && len(str) <= int(max) 892 } 893 894 return false 895} 896 897// RuneLength check string's length 898// Alias for StringLength 899func RuneLength(str string, params ...string) bool { 900 return StringLength(str, params...) 901} 902 903// IsRsaPub check whether string is valid RSA key 904// Alias for IsRsaPublicKey 905func IsRsaPub(str string, params ...string) bool { 906 if len(params) == 1 { 907 len, _ := ToInt(params[0]) 908 return IsRsaPublicKey(str, int(len)) 909 } 910 911 return false 912} 913 914// StringMatches checks if a string matches a given pattern. 915func StringMatches(s string, params ...string) bool { 916 if len(params) == 1 { 917 pattern := params[0] 918 return Matches(s, pattern) 919 } 920 return false 921} 922 923// StringLength check string's length (including multi byte strings) 924func StringLength(str string, params ...string) bool { 925 926 if len(params) == 2 { 927 strLength := utf8.RuneCountInString(str) 928 min, _ := ToInt(params[0]) 929 max, _ := ToInt(params[1]) 930 return strLength >= int(min) && strLength <= int(max) 931 } 932 933 return false 934} 935 936// Range check string's length 937func Range(str string, params ...string) bool { 938 if len(params) == 2 { 939 value, _ := ToFloat(str) 940 min, _ := ToFloat(params[0]) 941 max, _ := ToFloat(params[1]) 942 return InRange(value, min, max) 943 } 944 945 return false 946} 947 948func isInRaw(str string, params ...string) bool { 949 if len(params) == 1 { 950 rawParams := params[0] 951 952 parsedParams := strings.Split(rawParams, "|") 953 954 return IsIn(str, parsedParams...) 955 } 956 957 return false 958} 959 960// IsIn check if string str is a member of the set of strings params 961func IsIn(str string, params ...string) bool { 962 for _, param := range params { 963 if str == param { 964 return true 965 } 966 } 967 968 return false 969} 970 971func checkRequired(v reflect.Value, t reflect.StructField, options tagOptionsMap) (bool, error) { 972 if nilPtrAllowedByRequired { 973 k := v.Kind() 974 if (k == reflect.Ptr || k == reflect.Interface) && v.IsNil() { 975 return true, nil 976 } 977 } 978 979 if requiredOption, isRequired := options["required"]; isRequired { 980 if len(requiredOption.customErrorMessage) > 0 { 981 return false, Error{t.Name, fmt.Errorf(requiredOption.customErrorMessage), true, "required", []string{}} 982 } 983 return false, Error{t.Name, fmt.Errorf("non zero value required"), false, "required", []string{}} 984 } else if _, isOptional := options["optional"]; fieldsRequiredByDefault && !isOptional { 985 return false, Error{t.Name, fmt.Errorf("Missing required field"), false, "required", []string{}} 986 } 987 // not required and empty is valid 988 return true, nil 989} 990 991func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options tagOptionsMap) (isValid bool, resultErr error) { 992 if !v.IsValid() { 993 return false, nil 994 } 995 996 tag := t.Tag.Get(tagName) 997 998 // Check if the field should be ignored 999 switch tag { 1000 case "": 1001 if v.Kind() != reflect.Slice && v.Kind() != reflect.Map { 1002 if !fieldsRequiredByDefault { 1003 return true, nil 1004 } 1005 return false, Error{t.Name, fmt.Errorf("All fields are required to at least have one validation defined"), false, "required", []string{}} 1006 } 1007 case "-": 1008 return true, nil 1009 } 1010 1011 isRootType := false 1012 if options == nil { 1013 isRootType = true 1014 options = parseTagIntoMap(tag) 1015 } 1016 1017 if isEmptyValue(v) { 1018 // an empty value is not validated, check only required 1019 isValid, resultErr = checkRequired(v, t, options) 1020 for key := range options { 1021 delete(options, key) 1022 } 1023 return isValid, resultErr 1024 } 1025 1026 var customTypeErrors Errors 1027 optionsOrder := options.orderedKeys() 1028 for _, validatorName := range optionsOrder { 1029 validatorStruct := options[validatorName] 1030 if validatefunc, ok := CustomTypeTagMap.Get(validatorName); ok { 1031 delete(options, validatorName) 1032 1033 if result := validatefunc(v.Interface(), o.Interface()); !result { 1034 if len(validatorStruct.customErrorMessage) > 0 { 1035 customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: TruncatingErrorf(validatorStruct.customErrorMessage, fmt.Sprint(v), validatorName), CustomErrorMessageExists: true, Validator: stripParams(validatorName)}) 1036 continue 1037 } 1038 customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: fmt.Errorf("%s does not validate as %s", fmt.Sprint(v), validatorName), CustomErrorMessageExists: false, Validator: stripParams(validatorName)}) 1039 } 1040 } 1041 } 1042 1043 if len(customTypeErrors.Errors()) > 0 { 1044 return false, customTypeErrors 1045 } 1046 1047 if isRootType { 1048 // Ensure that we've checked the value by all specified validators before report that the value is valid 1049 defer func() { 1050 delete(options, "optional") 1051 delete(options, "required") 1052 1053 if isValid && resultErr == nil && len(options) != 0 { 1054 optionsOrder := options.orderedKeys() 1055 for _, validator := range optionsOrder { 1056 isValid = false 1057 resultErr = Error{t.Name, fmt.Errorf( 1058 "The following validator is invalid or can't be applied to the field: %q", validator), false, stripParams(validator), []string{}} 1059 return 1060 } 1061 } 1062 }() 1063 } 1064 1065 switch v.Kind() { 1066 case reflect.Bool, 1067 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 1068 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 1069 reflect.Float32, reflect.Float64, 1070 reflect.String: 1071 // for each tag option check the map of validator functions 1072 for _, validatorSpec := range optionsOrder { 1073 validatorStruct := options[validatorSpec] 1074 var negate bool 1075 validator := validatorSpec 1076 customMsgExists := len(validatorStruct.customErrorMessage) > 0 1077 1078 // Check whether the tag looks like '!something' or 'something' 1079 if validator[0] == '!' { 1080 validator = validator[1:] 1081 negate = true 1082 } 1083 1084 // Check for param validators 1085 for key, value := range ParamTagRegexMap { 1086 ps := value.FindStringSubmatch(validator) 1087 if len(ps) == 0 { 1088 continue 1089 } 1090 1091 validatefunc, ok := ParamTagMap[key] 1092 if !ok { 1093 continue 1094 } 1095 1096 delete(options, validatorSpec) 1097 1098 switch v.Kind() { 1099 case reflect.String, 1100 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 1101 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 1102 reflect.Float32, reflect.Float64: 1103 1104 field := fmt.Sprint(v) // make value into string, then validate with regex 1105 if result := validatefunc(field, ps[1:]...); (!result && !negate) || (result && negate) { 1106 if customMsgExists { 1107 return false, Error{t.Name, TruncatingErrorf(validatorStruct.customErrorMessage, field, validator), customMsgExists, stripParams(validatorSpec), []string{}} 1108 } 1109 if negate { 1110 return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} 1111 } 1112 return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} 1113 } 1114 default: 1115 // type not yet supported, fail 1116 return false, Error{t.Name, fmt.Errorf("Validator %s doesn't support kind %s", validator, v.Kind()), false, stripParams(validatorSpec), []string{}} 1117 } 1118 } 1119 1120 if validatefunc, ok := TagMap[validator]; ok { 1121 delete(options, validatorSpec) 1122 1123 switch v.Kind() { 1124 case reflect.String, 1125 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 1126 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 1127 reflect.Float32, reflect.Float64: 1128 field := fmt.Sprint(v) // make value into string, then validate with regex 1129 if result := validatefunc(field); !result && !negate || result && negate { 1130 if customMsgExists { 1131 return false, Error{t.Name, TruncatingErrorf(validatorStruct.customErrorMessage, field, validator), customMsgExists, stripParams(validatorSpec), []string{}} 1132 } 1133 if negate { 1134 return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} 1135 } 1136 return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} 1137 } 1138 default: 1139 //Not Yet Supported Types (Fail here!) 1140 err := fmt.Errorf("Validator %s doesn't support kind %s for value %v", validator, v.Kind(), v) 1141 return false, Error{t.Name, err, false, stripParams(validatorSpec), []string{}} 1142 } 1143 } 1144 } 1145 return true, nil 1146 case reflect.Map: 1147 if v.Type().Key().Kind() != reflect.String { 1148 return false, &UnsupportedTypeError{v.Type()} 1149 } 1150 var sv stringValues 1151 sv = v.MapKeys() 1152 sort.Sort(sv) 1153 result := true 1154 for i, k := range sv { 1155 var resultItem bool 1156 var err error 1157 if v.MapIndex(k).Kind() != reflect.Struct { 1158 resultItem, err = typeCheck(v.MapIndex(k), t, o, options) 1159 if err != nil { 1160 return false, err 1161 } 1162 } else { 1163 resultItem, err = ValidateStruct(v.MapIndex(k).Interface()) 1164 if err != nil { 1165 err = PrependPathToErrors(err, t.Name+"."+sv[i].Interface().(string)) 1166 return false, err 1167 } 1168 } 1169 result = result && resultItem 1170 } 1171 return result, nil 1172 case reflect.Slice, reflect.Array: 1173 result := true 1174 for i := 0; i < v.Len(); i++ { 1175 var resultItem bool 1176 var err error 1177 if v.Index(i).Kind() != reflect.Struct { 1178 resultItem, err = typeCheck(v.Index(i), t, o, options) 1179 if err != nil { 1180 return false, err 1181 } 1182 } else { 1183 resultItem, err = ValidateStruct(v.Index(i).Interface()) 1184 if err != nil { 1185 err = PrependPathToErrors(err, t.Name+"."+strconv.Itoa(i)) 1186 return false, err 1187 } 1188 } 1189 result = result && resultItem 1190 } 1191 return result, nil 1192 case reflect.Interface: 1193 // If the value is an interface then encode its element 1194 if v.IsNil() { 1195 return true, nil 1196 } 1197 return ValidateStruct(v.Interface()) 1198 case reflect.Ptr: 1199 // If the value is a pointer then check its element 1200 if v.IsNil() { 1201 return true, nil 1202 } 1203 return typeCheck(v.Elem(), t, o, options) 1204 case reflect.Struct: 1205 return ValidateStruct(v.Interface()) 1206 default: 1207 return false, &UnsupportedTypeError{v.Type()} 1208 } 1209} 1210 1211func stripParams(validatorString string) string { 1212 return paramsRegexp.ReplaceAllString(validatorString, "") 1213} 1214 1215func isEmptyValue(v reflect.Value) bool { 1216 switch v.Kind() { 1217 case reflect.String, reflect.Array: 1218 return v.Len() == 0 1219 case reflect.Map, reflect.Slice: 1220 return v.Len() == 0 || v.IsNil() 1221 case reflect.Bool: 1222 return !v.Bool() 1223 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 1224 return v.Int() == 0 1225 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 1226 return v.Uint() == 0 1227 case reflect.Float32, reflect.Float64: 1228 return v.Float() == 0 1229 case reflect.Interface, reflect.Ptr: 1230 return v.IsNil() 1231 } 1232 1233 return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) 1234} 1235 1236// ErrorByField returns error for specified field of the struct 1237// validated by ValidateStruct or empty string if there are no errors 1238// or this field doesn't exists or doesn't have any errors. 1239func ErrorByField(e error, field string) string { 1240 if e == nil { 1241 return "" 1242 } 1243 return ErrorsByField(e)[field] 1244} 1245 1246// ErrorsByField returns map of errors of the struct validated 1247// by ValidateStruct or empty map if there are no errors. 1248func ErrorsByField(e error) map[string]string { 1249 m := make(map[string]string) 1250 if e == nil { 1251 return m 1252 } 1253 // prototype for ValidateStruct 1254 1255 switch e.(type) { 1256 case Error: 1257 m[e.(Error).Name] = e.(Error).Err.Error() 1258 case Errors: 1259 for _, item := range e.(Errors).Errors() { 1260 n := ErrorsByField(item) 1261 for k, v := range n { 1262 m[k] = v 1263 } 1264 } 1265 } 1266 1267 return m 1268} 1269 1270// Error returns string equivalent for reflect.Type 1271func (e *UnsupportedTypeError) Error() string { 1272 return "validator: unsupported type: " + e.Type.String() 1273} 1274 1275func (sv stringValues) Len() int { return len(sv) } 1276func (sv stringValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } 1277func (sv stringValues) Less(i, j int) bool { return sv.get(i) < sv.get(j) } 1278func (sv stringValues) get(i int) string { return sv[i].String() } 1279