...

Source file src/github.com/xeipuuv/gojsonschema/format_checkers.go

Documentation: github.com/xeipuuv/gojsonschema

     1  package gojsonschema
     2  
     3  import (
     4  	"net"
     5  	"net/mail"
     6  	"net/url"
     7  	"regexp"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  type (
    14  	// FormatChecker is the interface all formatters added to FormatCheckerChain must implement
    15  	FormatChecker interface {
    16  		// IsFormat checks if input has the correct format and type
    17  		IsFormat(input interface{}) bool
    18  	}
    19  
    20  	// FormatCheckerChain holds the formatters
    21  	FormatCheckerChain struct {
    22  		formatters map[string]FormatChecker
    23  	}
    24  
    25  	// EmailFormatChecker verifies email address formats
    26  	EmailFormatChecker struct{}
    27  
    28  	// IPV4FormatChecker verifies IP addresses in the IPv4 format
    29  	IPV4FormatChecker struct{}
    30  
    31  	// IPV6FormatChecker verifies IP addresses in the IPv6 format
    32  	IPV6FormatChecker struct{}
    33  
    34  	// DateTimeFormatChecker verifies date/time formats per RFC3339 5.6
    35  	//
    36  	// Valid formats:
    37  	// 		Partial Time: HH:MM:SS
    38  	//		Full Date: YYYY-MM-DD
    39  	// 		Full Time: HH:MM:SSZ-07:00
    40  	//		Date Time: YYYY-MM-DDTHH:MM:SSZ-0700
    41  	//
    42  	// 	Where
    43  	//		YYYY = 4DIGIT year
    44  	//		MM = 2DIGIT month ; 01-12
    45  	//		DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year
    46  	//		HH = 2DIGIT hour ; 00-23
    47  	//		MM = 2DIGIT ; 00-59
    48  	//		SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
    49  	//		T = Literal
    50  	//		Z = Literal
    51  	//
    52  	//	Note: Nanoseconds are also suported in all formats
    53  	//
    54  	// http://tools.ietf.org/html/rfc3339#section-5.6
    55  	DateTimeFormatChecker struct{}
    56  
    57  	// DateFormatChecker verifies date formats
    58  	//
    59  	// Valid format:
    60  	//		Full Date: YYYY-MM-DD
    61  	//
    62  	// 	Where
    63  	//		YYYY = 4DIGIT year
    64  	//		MM = 2DIGIT month ; 01-12
    65  	//		DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year
    66  	DateFormatChecker struct{}
    67  
    68  	// TimeFormatChecker verifies time formats
    69  	//
    70  	// Valid formats:
    71  	// 		Partial Time: HH:MM:SS
    72  	// 		Full Time: HH:MM:SSZ-07:00
    73  	//
    74  	// 	Where
    75  	//		HH = 2DIGIT hour ; 00-23
    76  	//		MM = 2DIGIT ; 00-59
    77  	//		SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
    78  	//		T = Literal
    79  	//		Z = Literal
    80  	TimeFormatChecker struct{}
    81  
    82  	// URIFormatChecker validates a URI with a valid Scheme per RFC3986
    83  	URIFormatChecker struct{}
    84  
    85  	// URIReferenceFormatChecker validates a URI or relative-reference per RFC3986
    86  	URIReferenceFormatChecker struct{}
    87  
    88  	// URITemplateFormatChecker validates a URI template per RFC6570
    89  	URITemplateFormatChecker struct{}
    90  
    91  	// HostnameFormatChecker validates a hostname is in the correct format
    92  	HostnameFormatChecker struct{}
    93  
    94  	// UUIDFormatChecker validates a UUID is in the correct format
    95  	UUIDFormatChecker struct{}
    96  
    97  	// RegexFormatChecker validates a regex is in the correct format
    98  	RegexFormatChecker struct{}
    99  
   100  	// JSONPointerFormatChecker validates a JSON Pointer per RFC6901
   101  	JSONPointerFormatChecker struct{}
   102  
   103  	// RelativeJSONPointerFormatChecker validates a relative JSON Pointer is in the correct format
   104  	RelativeJSONPointerFormatChecker struct{}
   105  )
   106  
   107  var (
   108  	// FormatCheckers holds the valid formatters, and is a public variable
   109  	// so library users can add custom formatters
   110  	FormatCheckers = FormatCheckerChain{
   111  		formatters: map[string]FormatChecker{
   112  			"date":                  DateFormatChecker{},
   113  			"time":                  TimeFormatChecker{},
   114  			"date-time":             DateTimeFormatChecker{},
   115  			"hostname":              HostnameFormatChecker{},
   116  			"email":                 EmailFormatChecker{},
   117  			"idn-email":             EmailFormatChecker{},
   118  			"ipv4":                  IPV4FormatChecker{},
   119  			"ipv6":                  IPV6FormatChecker{},
   120  			"uri":                   URIFormatChecker{},
   121  			"uri-reference":         URIReferenceFormatChecker{},
   122  			"iri":                   URIFormatChecker{},
   123  			"iri-reference":         URIReferenceFormatChecker{},
   124  			"uri-template":          URITemplateFormatChecker{},
   125  			"uuid":                  UUIDFormatChecker{},
   126  			"regex":                 RegexFormatChecker{},
   127  			"json-pointer":          JSONPointerFormatChecker{},
   128  			"relative-json-pointer": RelativeJSONPointerFormatChecker{},
   129  		},
   130  	}
   131  
   132  	// Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname
   133  	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]))*$`)
   134  
   135  	// Use a regex to make sure curly brackets are balanced properly after validating it as a AURI
   136  	rxURITemplate = regexp.MustCompile("^([^{]*({[^}]*})?)*$")
   137  
   138  	rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
   139  
   140  	rxJSONPointer = regexp.MustCompile("^(?:/(?:[^~/]|~0|~1)*)*$")
   141  
   142  	rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$")
   143  
   144  	lock = new(sync.RWMutex)
   145  )
   146  
   147  // Add adds a FormatChecker to the FormatCheckerChain
   148  // The name used will be the value used for the format key in your json schema
   149  func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain {
   150  	lock.Lock()
   151  	c.formatters[name] = f
   152  	lock.Unlock()
   153  
   154  	return c
   155  }
   156  
   157  // Remove deletes a FormatChecker from the FormatCheckerChain (if it exists)
   158  func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain {
   159  	lock.Lock()
   160  	delete(c.formatters, name)
   161  	lock.Unlock()
   162  
   163  	return c
   164  }
   165  
   166  // Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name
   167  func (c *FormatCheckerChain) Has(name string) bool {
   168  	lock.RLock()
   169  	_, ok := c.formatters[name]
   170  	lock.RUnlock()
   171  
   172  	return ok
   173  }
   174  
   175  // IsFormat will check an input against a FormatChecker with the given name
   176  // to see if it is the correct format
   177  func (c *FormatCheckerChain) IsFormat(name string, input interface{}) bool {
   178  	lock.RLock()
   179  	f, ok := c.formatters[name]
   180  	lock.RUnlock()
   181  
   182  	// If a format is unrecognized it should always pass validation
   183  	if !ok {
   184  		return true
   185  	}
   186  
   187  	return f.IsFormat(input)
   188  }
   189  
   190  // IsFormat checks if input is a correctly formatted e-mail address
   191  func (f EmailFormatChecker) IsFormat(input interface{}) bool {
   192  	asString, ok := input.(string)
   193  	if !ok {
   194  		return false
   195  	}
   196  
   197  	_, err := mail.ParseAddress(asString)
   198  	return err == nil
   199  }
   200  
   201  // IsFormat checks if input is a correctly formatted IPv4-address
   202  func (f IPV4FormatChecker) IsFormat(input interface{}) bool {
   203  	asString, ok := input.(string)
   204  	if !ok {
   205  		return false
   206  	}
   207  
   208  	// Credit: https://github.com/asaskevich/govalidator
   209  	ip := net.ParseIP(asString)
   210  	return ip != nil && strings.Contains(asString, ".")
   211  }
   212  
   213  // IsFormat checks if input is a correctly formatted IPv6=address
   214  func (f IPV6FormatChecker) IsFormat(input interface{}) bool {
   215  	asString, ok := input.(string)
   216  	if !ok {
   217  		return false
   218  	}
   219  
   220  	// Credit: https://github.com/asaskevich/govalidator
   221  	ip := net.ParseIP(asString)
   222  	return ip != nil && strings.Contains(asString, ":")
   223  }
   224  
   225  // IsFormat checks if input is a correctly formatted  date/time per RFC3339 5.6
   226  func (f DateTimeFormatChecker) IsFormat(input interface{}) bool {
   227  	asString, ok := input.(string)
   228  	if !ok {
   229  		return false
   230  	}
   231  
   232  	formats := []string{
   233  		"15:04:05",
   234  		"15:04:05Z07:00",
   235  		"2006-01-02",
   236  		time.RFC3339,
   237  		time.RFC3339Nano,
   238  	}
   239  
   240  	for _, format := range formats {
   241  		if _, err := time.Parse(format, asString); err == nil {
   242  			return true
   243  		}
   244  	}
   245  
   246  	return false
   247  }
   248  
   249  // IsFormat checks if input is a correctly formatted  date (YYYY-MM-DD)
   250  func (f DateFormatChecker) IsFormat(input interface{}) bool {
   251  	asString, ok := input.(string)
   252  	if !ok {
   253  		return false
   254  	}
   255  	_, err := time.Parse("2006-01-02", asString)
   256  	return err == nil
   257  }
   258  
   259  // IsFormat checks if input correctly formatted time (HH:MM:SS or HH:MM:SSZ-07:00)
   260  func (f TimeFormatChecker) IsFormat(input interface{}) bool {
   261  	asString, ok := input.(string)
   262  	if !ok {
   263  		return false
   264  	}
   265  
   266  	if _, err := time.Parse("15:04:05Z07:00", asString); err == nil {
   267  		return true
   268  	}
   269  
   270  	_, err := time.Parse("15:04:05", asString)
   271  	return err == nil
   272  }
   273  
   274  // IsFormat checks if input is correctly formatted  URI with a valid Scheme per RFC3986
   275  func (f URIFormatChecker) IsFormat(input interface{}) bool {
   276  	asString, ok := input.(string)
   277  	if !ok {
   278  		return false
   279  	}
   280  
   281  	u, err := url.Parse(asString)
   282  
   283  	if err != nil || u.Scheme == "" {
   284  		return false
   285  	}
   286  
   287  	return !strings.Contains(asString, `\`)
   288  }
   289  
   290  // IsFormat checks if input is a correctly formatted URI or relative-reference per RFC3986
   291  func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool {
   292  	asString, ok := input.(string)
   293  	if !ok {
   294  		return false
   295  	}
   296  
   297  	_, err := url.Parse(asString)
   298  	return err == nil && !strings.Contains(asString, `\`)
   299  }
   300  
   301  // IsFormat checks if input is a correctly formatted URI template per RFC6570
   302  func (f URITemplateFormatChecker) IsFormat(input interface{}) bool {
   303  	asString, ok := input.(string)
   304  	if !ok {
   305  		return false
   306  	}
   307  
   308  	u, err := url.Parse(asString)
   309  	if err != nil || strings.Contains(asString, `\`) {
   310  		return false
   311  	}
   312  
   313  	return rxURITemplate.MatchString(u.Path)
   314  }
   315  
   316  // IsFormat checks if input is a correctly formatted hostname
   317  func (f HostnameFormatChecker) IsFormat(input interface{}) bool {
   318  	asString, ok := input.(string)
   319  	if !ok {
   320  		return false
   321  	}
   322  
   323  	return rxHostname.MatchString(asString) && len(asString) < 256
   324  }
   325  
   326  // IsFormat checks if input is a correctly formatted UUID
   327  func (f UUIDFormatChecker) IsFormat(input interface{}) bool {
   328  	asString, ok := input.(string)
   329  	if !ok {
   330  		return false
   331  	}
   332  
   333  	return rxUUID.MatchString(asString)
   334  }
   335  
   336  // IsFormat checks if input is a correctly formatted regular expression
   337  func (f RegexFormatChecker) IsFormat(input interface{}) bool {
   338  	asString, ok := input.(string)
   339  	if !ok {
   340  		return false
   341  	}
   342  
   343  	if asString == "" {
   344  		return true
   345  	}
   346  	_, err := regexp.Compile(asString)
   347  	return err == nil
   348  }
   349  
   350  // IsFormat checks if input is a correctly formatted JSON Pointer per RFC6901
   351  func (f JSONPointerFormatChecker) IsFormat(input interface{}) bool {
   352  	asString, ok := input.(string)
   353  	if !ok {
   354  		return false
   355  	}
   356  
   357  	return rxJSONPointer.MatchString(asString)
   358  }
   359  
   360  // IsFormat checks if input is a correctly formatted relative JSON Pointer
   361  func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) bool {
   362  	asString, ok := input.(string)
   363  	if !ok {
   364  		return false
   365  	}
   366  
   367  	return rxRelJSONPointer.MatchString(asString)
   368  }
   369  

View as plain text