Skip to content

Commit

Permalink
Change FormatChecker interface IsFormat() to return error not bool
Browse files Browse the repository at this point in the history
  • Loading branch information
timbunce committed May 31, 2020
1 parent 76ea6eb commit 13cce39
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 80 deletions.
122 changes: 67 additions & 55 deletions format_checkers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gojsonschema

import (
"errors"
"net"
"net/mail"
"net/url"
Expand All @@ -10,11 +11,20 @@ import (
"time"
)

var ErrBadFormat = errors.New("bad format") // TODO better string or other way to indicate a sentinel error

func badFormatUnless(ok bool) error {
if ok {
return nil
}
return ErrBadFormat
}

type (
// FormatChecker is the interface all formatters added to FormatCheckerChain must implement
FormatChecker interface {
// IsFormat checks if input has the correct format
IsFormat(input interface{}) bool
IsFormat(input interface{}) error
}

// FormatCheckerChain holds the formatters
Expand Down Expand Up @@ -174,59 +184,59 @@ func (c *FormatCheckerChain) Has(name string) bool {

// 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 {
func (c *FormatCheckerChain) IsFormat(name string, input interface{}) error {
lock.RLock()
f, ok := c.formatters[name]
lock.RUnlock()

// If a format is unrecognized it should always pass validation
if !ok {
return true
return nil
}

return f.IsFormat(input)
}

// IsFormat checks if input is a correctly formatted e-mail address
func (f EmailFormatChecker) IsFormat(input interface{}) bool {
func (f EmailFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

_, err := mail.ParseAddress(asString)
return err == nil
return badFormatUnless(err == nil)
}

// IsFormat checks if input is a correctly formatted IPv4-address
func (f IPV4FormatChecker) IsFormat(input interface{}) bool {
func (f IPV4FormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

// Credit: https://github.com/asaskevich/govalidator
ip := net.ParseIP(asString)
return ip != nil && strings.Contains(asString, ".")
return badFormatUnless(ip != nil && strings.Contains(asString, "."))
}

// IsFormat checks if input is a correctly formatted IPv6=address
func (f IPV6FormatChecker) IsFormat(input interface{}) bool {
func (f IPV6FormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

// Credit: https://github.com/asaskevich/govalidator
ip := net.ParseIP(asString)
return ip != nil && strings.Contains(asString, ":")
return badFormatUnless(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 {
func (f DateTimeFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

formats := []string{
Expand All @@ -239,130 +249,132 @@ func (f DateTimeFormatChecker) IsFormat(input interface{}) bool {

for _, format := range formats {
if _, err := time.Parse(format, asString); err == nil {
return true
return nil
}
}

return false
return ErrBadFormat
}

// IsFormat checks if input is a correctly formatted date (YYYY-MM-DD)
func (f DateFormatChecker) IsFormat(input interface{}) bool {
func (f DateFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}
_, err := time.Parse("2006-01-02", asString)
return err == nil
return badFormatUnless(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 {
func (f TimeFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

if _, err := time.Parse("15:04:05Z07:00", asString); err == nil {
return true
return nil
}

_, err := time.Parse("15:04:05", asString)
return err == nil
if _, err := time.Parse("15:04:05", asString); err == nil {
return nil
}
return ErrBadFormat
}

// IsFormat checks if input is correctly formatted URI with a valid Scheme per RFC3986
func (f URIFormatChecker) IsFormat(input interface{}) bool {
// IsFormat checks if input is correctly formatted URI with a valid Scheme per RFC3986
func (f URIFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

u, err := url.Parse(asString)

if err != nil || u.Scheme == "" {
return false
return ErrBadFormat
}

return !strings.Contains(asString, `\`)
if strings.Contains(asString, `\`) {
return ErrBadFormat
}
return nil
}

// IsFormat checks if input is a correctly formatted URI or relative-reference per RFC3986
func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool {
func (f URIReferenceFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

_, err := url.Parse(asString)
return err == nil && !strings.Contains(asString, `\`)
return badFormatUnless(err == nil && !strings.Contains(asString, `\`))
}

// IsFormat checks if input is a correctly formatted URI template per RFC6570
func (f URITemplateFormatChecker) IsFormat(input interface{}) bool {
func (f URITemplateFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

u, err := url.Parse(asString)
if err != nil || strings.Contains(asString, `\`) {
return false
return ErrBadFormat
}

return rxURITemplate.MatchString(u.Path)
return badFormatUnless(rxURITemplate.MatchString(u.Path))
}

// IsFormat checks if input is a correctly formatted hostname
func (f HostnameFormatChecker) IsFormat(input interface{}) bool {
func (f HostnameFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

return rxHostname.MatchString(asString) && len(asString) < 256
return badFormatUnless(rxHostname.MatchString(asString) && len(asString) < 256)
}

// IsFormat checks if input is a correctly formatted UUID
func (f UUIDFormatChecker) IsFormat(input interface{}) bool {
func (f UUIDFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

return rxUUID.MatchString(asString)
return badFormatUnless(rxUUID.MatchString(asString))
}

// IsFormat checks if input is a correctly formatted regular expression
func (f RegexFormatChecker) IsFormat(input interface{}) bool {
func (f RegexFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

if asString == "" {
return true
return nil
}
_, err := regexp.Compile(asString)
return err == nil
return badFormatUnless(err == nil)
}

// IsFormat checks if input is a correctly formatted JSON Pointer per RFC6901
func (f JSONPointerFormatChecker) IsFormat(input interface{}) bool {
func (f JSONPointerFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

return rxJSONPointer.MatchString(asString)
return badFormatUnless(rxJSONPointer.MatchString(asString))
}

// IsFormat checks if input is a correctly formatted relative JSON Pointer
func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) bool {
func (f RelativeJSONPointerFormatChecker) IsFormat(input interface{}) error {
asString, ok := input.(string)
if !ok {
return true
return nil
}

return rxRelJSONPointer.MatchString(asString)
return badFormatUnless(rxRelJSONPointer.MatchString(asString))
}
48 changes: 24 additions & 24 deletions format_checkers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ import (
func TestUUIDFormatCheckerIsFormat(t *testing.T) {
checker := UUIDFormatChecker{}

assert.True(t, checker.IsFormat("01234567-89ab-cdef-0123-456789abcdef"))
assert.True(t, checker.IsFormat("f1234567-89ab-cdef-0123-456789abcdef"))
assert.True(t, checker.IsFormat("01234567-89AB-CDEF-0123-456789ABCDEF"))
assert.True(t, checker.IsFormat("F1234567-89AB-CDEF-0123-456789ABCDEF"))
assert.Nil(t, checker.IsFormat("01234567-89ab-cdef-0123-456789abcdef"))
assert.Nil(t, checker.IsFormat("f1234567-89ab-cdef-0123-456789abcdef"))
assert.Nil(t, checker.IsFormat("01234567-89AB-CDEF-0123-456789ABCDEF"))
assert.Nil(t, checker.IsFormat("F1234567-89AB-CDEF-0123-456789ABCDEF"))

assert.False(t, checker.IsFormat("not-a-uuid"))
assert.False(t, checker.IsFormat("g1234567-89ab-cdef-0123-456789abcdef"))
assert.NotNil(t, checker.IsFormat("not-a-uuid"))
assert.NotNil(t, checker.IsFormat("g1234567-89ab-cdef-0123-456789abcdef"))
}

func TestURIReferenceFormatCheckerIsFormat(t *testing.T) {
checker := URIReferenceFormatChecker{}

assert.True(t, checker.IsFormat("relative"))
assert.True(t, checker.IsFormat("https://dummyhost.com/dummy-path?dummy-qp-name=dummy-qp-value"))
assert.Nil(t, checker.IsFormat("relative"))
assert.Nil(t, checker.IsFormat("https://dummyhost.com/dummy-path?dummy-qp-name=dummy-qp-value"))
}

const formatSchema = `{
Expand All @@ -41,58 +41,58 @@ const formatSchema = `{

type arrayChecker struct{}

func (c arrayChecker) IsFormat(input interface{}) bool {
func (c arrayChecker) IsFormat(input interface{}) error {
arr, ok := input.([]interface{})
if !ok {
return true
return nil
}
for _, v := range arr {
if v == "x" {
return true
return nil
}
}
return false
return ErrBadFormat
}

type boolChecker struct{}

func (c boolChecker) IsFormat(input interface{}) bool {
func (c boolChecker) IsFormat(input interface{}) error {
b, ok := input.(bool)
if !ok {
return true
return nil
}
return b
return badFormatUnless(b)
}

type integerChecker struct{}

func (c integerChecker) IsFormat(input interface{}) bool {
func (c integerChecker) IsFormat(input interface{}) error {
number, ok := input.(json.Number)
if !ok {
return true
return nil
}
f, _ := number.Float64()
return int(f)%2 == 0
return badFormatUnless(int(f)%2 == 0)
}

type objectChecker struct{}

func (c objectChecker) IsFormat(input interface{}) bool {
func (c objectChecker) IsFormat(input interface{}) error {
obj, ok := input.(map[string]interface{})
if !ok {
return true
return nil
}
return obj["name"] == "x"
return badFormatUnless(obj["name"] == "x")
}

type stringChecker struct{}

func (c stringChecker) IsFormat(input interface{}) bool {
func (c stringChecker) IsFormat(input interface{}) error {
str, ok := input.(string)
if !ok {
return true
return nil
}
return str == "o"
return badFormatUnless(str == "o")
}

func TestCustomFormat(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{

// format:
if currentSubSchema.format != "" {
if !FormatCheckers.IsFormat(currentSubSchema.format, value) {
if err := FormatCheckers.IsFormat(currentSubSchema.format, value); err != nil {
result.addInternalError(
new(DoesNotMatchFormatError),
context,
Expand Down

0 comments on commit 13cce39

Please sign in to comment.