diff --git a/format_checkers.go b/format_checkers.go index 1e77046..3c3d383 100644 --- a/format_checkers.go +++ b/format_checkers.go @@ -1,6 +1,7 @@ package gojsonschema import ( + "errors" "net" "net/mail" "net/url" @@ -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 @@ -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{ @@ -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)) } diff --git a/format_checkers_test.go b/format_checkers_test.go index 6742178..ddf74f6 100644 --- a/format_checkers_test.go +++ b/format_checkers_test.go @@ -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 = `{ @@ -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) { diff --git a/validation.go b/validation.go index 9081bd9..e99a162 100644 --- a/validation.go +++ b/validation.go @@ -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,