Merge pull request #163 from chrisdostert/master
FormatChecker.IsFormat parameter as interface{}
diff --git a/README.md b/README.md
index 83ad31c..e02976b 100644
--- a/README.md
+++ b/README.md
@@ -224,7 +224,7 @@
Learn more about what types of template functions you can use in `ErrorTemplateFuncs` by referring to Go's [text/template FuncMap](https://golang.org/pkg/text/template/#FuncMap) type.
## Formats
-JSON Schema allows for optional "format" property to validate strings against well-known formats. gojsonschema ships with all of the formats defined in the spec that you can use like this:
+JSON Schema allows for optional "format" property to validate instances against well-known formats. gojsonschema ships with all of the formats defined in the spec that you can use like this:
````json
{"type": "string", "format": "email"}
````
@@ -237,8 +237,14 @@
type RoleFormatChecker struct {}
// Ensure it meets the gojsonschema.FormatChecker interface
-func (f RoleFormatChecker) IsFormat(input string) bool {
- return strings.HasPrefix("ROLE_", input)
+func (f RoleFormatChecker) IsFormat(input interface{}) bool {
+
+ asString, ok := input.(string)
+ if ok == false {
+ return false
+ }
+
+ return strings.HasPrefix("ROLE_", asString)
}
// Add it to the library
@@ -250,6 +256,37 @@
{"type": "string", "format": "role"}
````
+Another example would be to check if the provided integer matches an id on database:
+
+JSON schema:
+```json
+{"type": "integer", "format": "ValidUserId"}
+```
+
+```go
+// Define the format checker
+type ValidUserIdFormatChecker struct {}
+
+// Ensure it meets the gojsonschema.FormatChecker interface
+func (f ValidUserIdFormatChecker) IsFormat(input interface{}) bool {
+
+ asFloat64, ok := input.(float64) // Numbers are always float64 here
+ if ok == false {
+ return false
+ }
+
+ // XXX
+ // do the magic on the database looking for the int(asFloat64)
+
+ return true
+}
+
+// Add it to the library
+gojsonschema.FormatCheckers.Add("ValidUserId", ValidUserIdFormatChecker{})
+````
+
+
+
## Uses
gojsonschema uses the following test suite :
diff --git a/format_checkers.go b/format_checkers.go
index 94bd095..c6a0792 100644
--- a/format_checkers.go
+++ b/format_checkers.go
@@ -3,7 +3,6 @@
import (
"net"
"net/url"
- "reflect"
"regexp"
"strings"
"time"
@@ -12,7 +11,7 @@
type (
// FormatChecker is the interface all formatters added to FormatCheckerChain must implement
FormatChecker interface {
- IsFormat(input string) bool
+ IsFormat(input interface{}) bool
}
// FormatCheckerChain holds the formatters
@@ -125,32 +124,50 @@
return false
}
- if !isKind(input, reflect.String) {
+ return f.IsFormat(input)
+}
+
+func (f EmailFormatChecker) IsFormat(input interface{}) bool {
+
+ asString, ok := input.(string)
+ if ok == false {
return false
}
- inputString := input.(string)
-
- return f.IsFormat(inputString)
-}
-
-func (f EmailFormatChecker) IsFormat(input string) bool {
- return rxEmail.MatchString(input)
+ return rxEmail.MatchString(asString)
}
// Credit: https://github.com/asaskevich/govalidator
-func (f IPV4FormatChecker) IsFormat(input string) bool {
- ip := net.ParseIP(input)
- return ip != nil && strings.Contains(input, ".")
+func (f IPV4FormatChecker) IsFormat(input interface{}) bool {
+
+ asString, ok := input.(string)
+ if ok == false {
+ return false
+ }
+
+ ip := net.ParseIP(asString)
+ return ip != nil && strings.Contains(asString, ".")
}
// Credit: https://github.com/asaskevich/govalidator
-func (f IPV6FormatChecker) IsFormat(input string) bool {
- ip := net.ParseIP(input)
- return ip != nil && strings.Contains(input, ":")
+func (f IPV6FormatChecker) IsFormat(input interface{}) bool {
+
+ asString, ok := input.(string)
+ if ok == false {
+ return false
+ }
+
+ ip := net.ParseIP(asString)
+ return ip != nil && strings.Contains(asString, ":")
}
-func (f DateTimeFormatChecker) IsFormat(input string) bool {
+func (f DateTimeFormatChecker) IsFormat(input interface{}) bool {
+
+ asString, ok := input.(string)
+ if ok == false {
+ return false
+ }
+
formats := []string{
"15:04:05",
"15:04:05Z07:00",
@@ -160,7 +177,7 @@
}
for _, format := range formats {
- if _, err := time.Parse(format, input); err == nil {
+ if _, err := time.Parse(format, asString); err == nil {
return true
}
}
@@ -168,8 +185,14 @@
return false
}
-func (f URIFormatChecker) IsFormat(input string) bool {
- u, err := url.Parse(input)
+func (f URIFormatChecker) IsFormat(input interface{}) bool {
+
+ asString, ok := input.(string)
+ if ok == false {
+ return false
+ }
+
+ u, err := url.Parse(asString)
if err != nil || u.Scheme == "" {
return false
}
@@ -177,25 +200,49 @@
return true
}
-func (f URIReferenceFormatChecker) IsFormat(input string) bool {
- _, err := url.Parse(input)
+func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool {
+
+ asString, ok := input.(string)
+ if ok == false {
+ return false
+ }
+
+ _, err := url.Parse(asString)
return err == nil
}
-func (f HostnameFormatChecker) IsFormat(input string) bool {
- return rxHostname.MatchString(input) && len(input) < 256
+func (f HostnameFormatChecker) IsFormat(input interface{}) bool {
+
+ asString, ok := input.(string)
+ if ok == false {
+ return false
+ }
+
+ return rxHostname.MatchString(asString) && len(asString) < 256
}
-func (f UUIDFormatChecker) IsFormat(input string) bool {
- return rxUUID.MatchString(input)
+func (f UUIDFormatChecker) IsFormat(input interface{}) bool {
+
+ asString, ok := input.(string)
+ if ok == false {
+ return false
+ }
+
+ return rxUUID.MatchString(asString)
}
// IsFormat implements FormatChecker interface.
-func (f RegexFormatChecker) IsFormat(input string) bool {
- if input == "" {
+func (f RegexFormatChecker) IsFormat(input interface{}) bool {
+
+ asString, ok := input.(string)
+ if ok == false {
+ return false
+ }
+
+ if asString == "" {
return true
}
- _, err := regexp.Compile(input)
+ _, err := regexp.Compile(asString)
if err != nil {
return false
}
diff --git a/json_schema_test_suite/format/data_29.json b/json_schema_test_suite/format/data_29.json
new file mode 100644
index 0000000..f599e28
--- /dev/null
+++ b/json_schema_test_suite/format/data_29.json
@@ -0,0 +1 @@
+10
diff --git a/json_schema_test_suite/format/data_30.json b/json_schema_test_suite/format/data_30.json
new file mode 100644
index 0000000..b4de394
--- /dev/null
+++ b/json_schema_test_suite/format/data_30.json
@@ -0,0 +1 @@
+11
diff --git a/json_schema_test_suite/format/schema_7.json b/json_schema_test_suite/format/schema_7.json
new file mode 100644
index 0000000..4743e21
--- /dev/null
+++ b/json_schema_test_suite/format/schema_7.json
@@ -0,0 +1 @@
+{"type": "number", "format": "evenNumber"}
diff --git a/schema_test.go b/schema_test.go
index b80c30a..453dfb8 100644
--- a/schema_test.go
+++ b/schema_test.go
@@ -50,13 +50,30 @@
// Used for remote schema in ref/schema_5.json that defines "uri" and "regex" types
type alwaysTrueFormatChecker struct{}
type invoiceFormatChecker struct{}
+type evenNumberFormatChecker struct{}
-func (a alwaysTrueFormatChecker) IsFormat(input string) bool {
+func (a alwaysTrueFormatChecker) IsFormat(input interface{}) bool {
return true
}
-func (a invoiceFormatChecker) IsFormat(input string) bool {
- return rxInvoice.MatchString(input)
+func (a evenNumberFormatChecker) IsFormat(input interface{}) bool {
+
+ asFloat64, ok := input.(float64)
+ if ok == false {
+ return false
+ }
+
+ return int(asFloat64)%2 == 0
+}
+
+func (a invoiceFormatChecker) IsFormat(input interface{}) bool {
+
+ asString, ok := input.(string)
+ if ok == false {
+ return false
+ }
+
+ return rxInvoice.MatchString(asString)
}
func TestJsonSchemaTestSuite(t *testing.T) {
@@ -341,6 +358,8 @@
{"phase": "format validation", "test": "uri format is valid", "schema": "format/schema_6.json", "data": "format/data_27.json", "valid": "true"},
{"phase": "format validation", "test": "uri format is invalid", "schema": "format/schema_6.json", "data": "format/data_28.json", "valid": "false", "errors": "format"},
{"phase": "format validation", "test": "uri format is invalid", "schema": "format/schema_6.json", "data": "format/data_13.json", "valid": "false", "errors": "format"},
+ {"phase": "format validation", "test": "number format is valid", "schema": "format/schema_7.json", "data": "format/data_29.json", "valid": "true"},
+ {"phase": "format validation", "test": "number format is valid", "schema": "format/schema_7.json", "data": "format/data_30.json", "valid": "false", "errors": "format"},
}
//TODO Pass failed tests : id(s) as scope for references is not implemented yet
@@ -369,6 +388,9 @@
// Custom Formatter
FormatCheckers.Add("invoice", invoiceFormatChecker{})
+ // Number Formatter
+ FormatCheckers.Add("evenNumber", evenNumberFormatChecker{})
+
// Launch tests
for testJsonIndex, testJson := range JsonSchemaTestSuiteMap {
diff --git a/validation.go b/validation.go
index 6140bd8..9afea25 100644
--- a/validation.go
+++ b/validation.go
@@ -828,5 +828,17 @@
}
}
+ // format
+ if currentSubSchema.format != "" {
+ if !FormatCheckers.IsFormat(currentSubSchema.format, float64Value) {
+ result.addError(
+ new(DoesNotMatchFormatError),
+ context,
+ value,
+ ErrorDetails{"format": currentSubSchema.format},
+ )
+ }
+ }
+
result.incrementScore()
}