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()
 }