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