123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- package gojsonschema
- import (
- "net"
- "net/url"
- "reflect"
- "regexp"
- "time"
- )
- type (
- // FormatChecker is the interface all formatters added to FormatCheckerChain must implement
- FormatChecker interface {
- IsFormat(input string) bool
- }
- // FormatCheckerChain holds the formatters
- FormatCheckerChain struct {
- formatters map[string]FormatChecker
- }
- // EmailFormatter verifies email address formats
- EmailFormatChecker struct{}
- // IPV4FormatChecker verifies IP addresses in the ipv4 format
- IPV4FormatChecker struct{}
- // IPV6FormatChecker verifies IP addresses in the ipv6 format
- IPV6FormatChecker struct{}
- // DateTimeFormatChecker verifies date/time formats per RFC3339 5.6
- //
- // Valid formats:
- // Partial Time: HH:MM:SS
- // Full Date: YYYY-MM-DD
- // Full Time: HH:MM:SSZ-07:00
- // Date Time: YYYY-MM-DDTHH:MM:SSZ-0700
- //
- // Where
- // YYYY = 4DIGIT year
- // MM = 2DIGIT month ; 01-12
- // DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year
- // HH = 2DIGIT hour ; 00-23
- // MM = 2DIGIT ; 00-59
- // SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
- // T = Literal
- // Z = Literal
- //
- // Note: Nanoseconds are also suported in all formats
- //
- // http://tools.ietf.org/html/rfc3339#section-5.6
- DateTimeFormatChecker struct{}
- // URIFormatCheckers validates a URI with a valid Scheme per RFC3986
- URIFormatChecker struct{}
- // HostnameFormatChecker validates a hostname is in the correct format
- HostnameFormatChecker struct{}
- // UUIDFormatChecker validates a UUID is in the correct format
- UUIDFormatChecker struct{}
- )
- var (
- // Formatters holds the valid formatters, and is a public variable
- // so library users can add custom formatters
- FormatCheckers = FormatCheckerChain{
- formatters: map[string]FormatChecker{
- "date-time": DateTimeFormatChecker{},
- "hostname": HostnameFormatChecker{},
- "email": EmailFormatChecker{},
- "ipv4": IPV4FormatChecker{},
- "ipv6": IPV6FormatChecker{},
- "uri": URIFormatChecker{},
- "uuid": UUIDFormatChecker{},
- },
- }
- // Regex credit: https://github.com/asaskevich/govalidator
- 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}])))\\.?$")
- // Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname
- 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])$`)
- rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
- )
- // Add adds a FormatChecker to the FormatCheckerChain
- // The name used will be the value used for the format key in your json schema
- func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain {
- c.formatters[name] = f
- return c
- }
- // Remove deletes a FormatChecker from the FormatCheckerChain (if it exists)
- func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain {
- delete(c.formatters, name)
- return c
- }
- // Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name
- func (c *FormatCheckerChain) Has(name string) bool {
- _, ok := c.formatters[name]
- return ok
- }
- // IsFormat will check an input against a FormatChecker with the given name
- // to see if it is the correct format
- func (c *FormatCheckerChain) IsFormat(name string, input interface{}) bool {
- f, ok := c.formatters[name]
- if !ok {
- return false
- }
- if !isKind(input, reflect.String) {
- return false
- }
- inputString := input.(string)
- return f.IsFormat(inputString)
- }
- func (f EmailFormatChecker) IsFormat(input string) bool {
- return rxEmail.MatchString(input)
- }
- // Credit: https://github.com/asaskevich/govalidator
- func (f IPV4FormatChecker) IsFormat(input string) bool {
- ip := net.ParseIP(input)
- return ip != nil && ip.To4() != nil
- }
- // Credit: https://github.com/asaskevich/govalidator
- func (f IPV6FormatChecker) IsFormat(input string) bool {
- ip := net.ParseIP(input)
- return ip != nil && ip.To4() == nil
- }
- func (f DateTimeFormatChecker) IsFormat(input string) bool {
- formats := []string{
- "15:04:05",
- "15:04:05Z07:00",
- "2006-01-02",
- time.RFC3339,
- time.RFC3339Nano,
- }
- for _, format := range formats {
- if _, err := time.Parse(format, input); err == nil {
- return true
- }
- }
- return false
- }
- func (f URIFormatChecker) IsFormat(input string) bool {
- u, err := url.Parse(input)
- if err != nil || u.Scheme == "" {
- return false
- }
- return true
- }
- func (f HostnameFormatChecker) IsFormat(input string) bool {
- return rxHostname.MatchString(input)
- }
- func (f UUIDFormatChecker) IsFormat(input string) bool {
- return rxUUID.MatchString(input)
- }
|