format_checkers.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. package gojsonschema
  2. import (
  3. "net"
  4. "net/url"
  5. "reflect"
  6. "regexp"
  7. "strings"
  8. "time"
  9. )
  10. type (
  11. // FormatChecker is the interface all formatters added to FormatCheckerChain must implement
  12. FormatChecker interface {
  13. IsFormat(input string) bool
  14. }
  15. // FormatCheckerChain holds the formatters
  16. FormatCheckerChain struct {
  17. formatters map[string]FormatChecker
  18. }
  19. // EmailFormatter verifies email address formats
  20. EmailFormatChecker struct{}
  21. // IPV4FormatChecker verifies IP addresses in the ipv4 format
  22. IPV4FormatChecker struct{}
  23. // IPV6FormatChecker verifies IP addresses in the ipv6 format
  24. IPV6FormatChecker struct{}
  25. // DateTimeFormatChecker verifies date/time formats per RFC3339 5.6
  26. //
  27. // Valid formats:
  28. // Partial Time: HH:MM:SS
  29. // Full Date: YYYY-MM-DD
  30. // Full Time: HH:MM:SSZ-07:00
  31. // Date Time: YYYY-MM-DDTHH:MM:SSZ-0700
  32. //
  33. // Where
  34. // YYYY = 4DIGIT year
  35. // MM = 2DIGIT month ; 01-12
  36. // DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year
  37. // HH = 2DIGIT hour ; 00-23
  38. // MM = 2DIGIT ; 00-59
  39. // SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
  40. // T = Literal
  41. // Z = Literal
  42. //
  43. // Note: Nanoseconds are also suported in all formats
  44. //
  45. // http://tools.ietf.org/html/rfc3339#section-5.6
  46. DateTimeFormatChecker struct{}
  47. // URIFormatChecker validates a URI with a valid Scheme per RFC3986
  48. URIFormatChecker struct{}
  49. // URIReferenceFormatChecker validates a URI or relative-reference per RFC3986
  50. URIReferenceFormatChecker struct{}
  51. // HostnameFormatChecker validates a hostname is in the correct format
  52. HostnameFormatChecker struct{}
  53. // UUIDFormatChecker validates a UUID is in the correct format
  54. UUIDFormatChecker struct{}
  55. // RegexFormatChecker validates a regex is in the correct format
  56. RegexFormatChecker struct{}
  57. )
  58. var (
  59. // Formatters holds the valid formatters, and is a public variable
  60. // so library users can add custom formatters
  61. FormatCheckers = FormatCheckerChain{
  62. formatters: map[string]FormatChecker{
  63. "date-time": DateTimeFormatChecker{},
  64. "hostname": HostnameFormatChecker{},
  65. "email": EmailFormatChecker{},
  66. "ipv4": IPV4FormatChecker{},
  67. "ipv6": IPV6FormatChecker{},
  68. "uri": URIFormatChecker{},
  69. "uri-reference": URIReferenceFormatChecker{},
  70. "uuid": UUIDFormatChecker{},
  71. "regex": RegexFormatChecker{},
  72. },
  73. }
  74. // Regex credit: https://github.com/asaskevich/govalidator
  75. rxEmail = regexp.MustCompile("^(((([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}])))\\.?$")
  76. // Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname
  77. rxHostname = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`)
  78. rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
  79. )
  80. // Add adds a FormatChecker to the FormatCheckerChain
  81. // The name used will be the value used for the format key in your json schema
  82. func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain {
  83. c.formatters[name] = f
  84. return c
  85. }
  86. // Remove deletes a FormatChecker from the FormatCheckerChain (if it exists)
  87. func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain {
  88. delete(c.formatters, name)
  89. return c
  90. }
  91. // Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name
  92. func (c *FormatCheckerChain) Has(name string) bool {
  93. _, ok := c.formatters[name]
  94. return ok
  95. }
  96. // IsFormat will check an input against a FormatChecker with the given name
  97. // to see if it is the correct format
  98. func (c *FormatCheckerChain) IsFormat(name string, input interface{}) bool {
  99. f, ok := c.formatters[name]
  100. if !ok {
  101. return false
  102. }
  103. if !isKind(input, reflect.String) {
  104. return false
  105. }
  106. inputString := input.(string)
  107. return f.IsFormat(inputString)
  108. }
  109. func (f EmailFormatChecker) IsFormat(input string) bool {
  110. return rxEmail.MatchString(input)
  111. }
  112. // Credit: https://github.com/asaskevich/govalidator
  113. func (f IPV4FormatChecker) IsFormat(input string) bool {
  114. ip := net.ParseIP(input)
  115. return ip != nil && strings.Contains(input, ".")
  116. }
  117. // Credit: https://github.com/asaskevich/govalidator
  118. func (f IPV6FormatChecker) IsFormat(input string) bool {
  119. ip := net.ParseIP(input)
  120. return ip != nil && strings.Contains(input, ":")
  121. }
  122. func (f DateTimeFormatChecker) IsFormat(input string) bool {
  123. formats := []string{
  124. "15:04:05",
  125. "15:04:05Z07:00",
  126. "2006-01-02",
  127. time.RFC3339,
  128. time.RFC3339Nano,
  129. }
  130. for _, format := range formats {
  131. if _, err := time.Parse(format, input); err == nil {
  132. return true
  133. }
  134. }
  135. return false
  136. }
  137. func (f URIFormatChecker) IsFormat(input string) bool {
  138. u, err := url.Parse(input)
  139. if err != nil || u.Scheme == "" {
  140. return false
  141. }
  142. return true
  143. }
  144. func (f URIReferenceFormatChecker) IsFormat(input string) bool {
  145. _, err := url.Parse(input)
  146. return err == nil
  147. }
  148. func (f HostnameFormatChecker) IsFormat(input string) bool {
  149. return rxHostname.MatchString(input) && len(input) < 256
  150. }
  151. func (f UUIDFormatChecker) IsFormat(input string) bool {
  152. return rxUUID.MatchString(input)
  153. }
  154. // IsFormat implements FormatChecker interface.
  155. func (f RegexFormatChecker) IsFormat(input string) bool {
  156. if input == "" {
  157. return true
  158. }
  159. _, err := regexp.Compile(input)
  160. if err != nil {
  161. return false
  162. }
  163. return true
  164. }