format_checkers.go 5.7 KB

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