123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- package gojsonschema
- import (
- "net"
- "net/mail"
- "net/url"
- "regexp"
- "strings"
- "sync"
- "time"
- )
- type (
- // FormatChecker is the interface all formatters added to FormatCheckerChain must implement
- FormatChecker interface {
- // IsFormat checks if input has the correct format and type
- IsFormat(input interface{}) bool
- }
- // FormatCheckerChain holds the formatters
- FormatCheckerChain struct {
- formatters map[string]FormatChecker
- }
- // EmailFormatChecker 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{}
- // DateFormatChecker verifies date formats
- //
- // Valid format:
- // Full Date: YYYY-MM-DD
- //
- // 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
- DateFormatChecker struct{}
- // TimeFormatChecker verifies time formats
- //
- // Valid formats:
- // Partial Time: HH:MM:SS
- // Full Time: HH:MM:SSZ-07:00
- //
- // Where
- // HH = 2DIGIT hour ; 00-23
- // MM = 2DIGIT ; 00-59
- // SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
- // T = Literal
- // Z = Literal
- TimeFormatChecker struct{}
- // URIFormatChecker validates a URI with a valid Scheme per RFC3986
- URIFormatChecker struct{}
- // URIReferenceFormatChecker validates a URI or relative-reference per RFC3986
- URIReferenceFormatChecker struct{}
- // URITemplateFormatChecker validates a URI template per RFC6570
- URITemplateFormatChecker struct{}
- // HostnameFormatChecker validates a hostname is in the correct format
- HostnameFormatChecker struct{}
- // UUIDFormatChecker validates a UUID is in the correct format
- UUIDFormatChecker struct{}
- // RegexFormatChecker validates a regex is in the correct format
- RegexFormatChecker struct{}
- // JSONPointerFormatChecker validates a JSON Pointer per RFC6901
- JSONPointerFormatChecker struct{}
- // RelativeJSONPointerFormatChecker validates a relative JSON Pointer is in the correct format
- RelativeJSONPointerFormatChecker struct{}
- )
- var (
- // FormatCheckers holds the valid formatters, and is a public variable
- // so library users can add custom formatters
- FormatCheckers = FormatCheckerChain{
- formatters: map[string]FormatChecker{
- "date": DateFormatChecker{},
- "time": TimeFormatChecker{},
- "date-time": DateTimeFormatChecker{},
- "hostname": HostnameFormatChecker{},
- "email": EmailFormatChecker{},
- "idn-email": EmailFormatChecker{},
- "ipv4": IPV4FormatChecker{},
- "ipv6": IPV6FormatChecker{},
- "uri": URIFormatChecker{},
- "uri-reference": URIReferenceFormatChecker{},
- "iri": URIFormatChecker{},
- "iri-reference": URIReferenceFormatChecker{},
- "uri-template": URITemplateFormatChecker{},
- "uuid": UUIDFormatChecker{},
- "regex": RegexFormatChecker{},
- "json-pointer": JSONPointerFormatChecker{},
- "relative-json-pointer": RelativeJSONPointerFormatChecker{},
- },
- }
- // 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\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`)
- // Use a regex to make sure curly brackets are balanced properly after validating it as a AURI
- rxURITemplate = regexp.MustCompile("^([^{]*({[^}]*})?)*$")
- rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
- rxJSONPointer = regexp.MustCompile("^(?:/(?:[^~/]|~0|~1)*)*$")
- rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$")
- lock = new(sync.RWMutex)
- )
- // 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 {
- lock.Lock()
- c.formatters[name] = f
- lock.Unlock()
- return c
- }
- // Remove deletes a FormatChecker from the FormatCheckerChain (if it exists)
- func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain {
- lock.Lock()
- delete(c.formatters, name)
- lock.Unlock()
- return c
- }
- // Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name
- func (c *FormatCheckerChain) Has(name string) bool {
- lock.RLock()
- _, ok := c.formatters[name]
- lock.RUnlock()
- 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 {
- lock.RLock()
- f, ok := c.formatters[name]
- lock.RUnlock()
- // If a format is unrecognized it should always pass validation
- if !ok {
- return true
- }
- return f.IsFormat(input)
- }
- // IsFormat checks if input is a correctly formatted e-mail address
- func (f EmailFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- _, err := mail.ParseAddress(asString)
- return err == nil
- }
- // IsFormat checks if input is a correctly formatted IPv4-address
- func (f IPV4FormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- // Credit: https://github.com/asaskevich/govalidator
- ip := net.ParseIP(asString)
- return ip != nil && strings.Contains(asString, ".")
- }
- // IsFormat checks if input is a correctly formatted IPv6=address
- func (f IPV6FormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- // Credit: https://github.com/asaskevich/govalidator
- ip := net.ParseIP(asString)
- return ip != nil && strings.Contains(asString, ":")
- }
- // IsFormat checks if input is a correctly formatted date/time per RFC3339 5.6
- func (f DateTimeFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- 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, asString); err == nil {
- return true
- }
- }
- return false
- }
- // IsFormat checks if input is a correctly formatted date (YYYY-MM-DD)
- func (f DateFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- _, err := time.Parse("2006-01-02", asString)
- return err == nil
- }
- // IsFormat checks if input correctly formatted time (HH:MM:SS or HH:MM:SSZ-07:00)
- func (f TimeFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- if _, err := time.Parse("15:04:05Z07:00", asString); err == nil {
- return true
- }
- _, err := time.Parse("15:04:05", asString)
- return err == nil
- }
- // IsFormat checks if input is correctly formatted URI with a valid Scheme per RFC3986
- func (f URIFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- u, err := url.Parse(asString)
- if err != nil || u.Scheme == "" {
- return false
- }
- return !strings.Contains(asString, `\`)
- }
- // IsFormat checks if input is a correctly formatted URI or relative-reference per RFC3986
- func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- _, err := url.Parse(asString)
- return err == nil && !strings.Contains(asString, `\`)
- }
- // IsFormat checks if input is a correctly formatted URI template per RFC6570
- func (f URITemplateFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- u, err := url.Parse(asString)
- if err != nil || strings.Contains(asString, `\`) {
- return false
- }
- return rxURITemplate.MatchString(u.Path)
- }
- // IsFormat checks if input is a correctly formatted hostname
- func (f HostnameFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- return rxHostname.MatchString(asString) && len(asString) < 256
- }
- // IsFormat checks if input is a correctly formatted UUID
- func (f UUIDFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- return rxUUID.MatchString(asString)
- }
- // IsFormat checks if input is a correctly formatted regular expression
- func (f RegexFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- if asString == "" {
- return true
- }
- _, err := regexp.Compile(asString)
- return err == nil
- }
- // IsFormat checks if input is a correctly formatted JSON Pointer per RFC6901
- func (f JSONPointerFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- return rxJSONPointer.MatchString(asString)
- }
- // IsFormat checks if input is a correctly formatted relative JSON Pointer
- func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) bool {
- asString, ok := input.(string)
- if !ok {
- return false
- }
- return rxRelJSONPointer.MatchString(asString)
- }
|