Add support for email format as well as the ability to define custom formatters
diff --git a/README.md b/README.md
index 7c99649..6c9bca1 100644
--- a/README.md
+++ b/README.md
@@ -201,6 +201,36 @@
 ```
 %field% must be greater than or equal to %min%
 ```
+
+## Formatters
+JSON Schema allows for optional "format" property to validate strings against well-known formats. gojsonschema ships with an email format that you can use like this:
+````json
+   {"type": "string", "format": "email"}
+````
+For repetitive or more complex formats, you can create custom formatters and add them to gojsonschema like this:
+
+```go
+    // Define the format checker
+    type URLFormatter struct {}
+
+    // Ensure it meets the gojsonschema.FormatChecker interface
+    func (f URLFormatter) IsFormat(input string) bool {
+        if _, err := URL.Parse(input); err != nil {
+            return false;
+        }
+
+        return true
+    }
+
+    // Add the formatter
+    gojsonschema.Formatters.Add("url", URLFormatter{})
+````
+
+Now to use in your json schema:
+````json
+   {"type": "string", "format": "url"}
+````
+
 ## Uses
 
 gojsonschema uses the following test suite :
diff --git a/errors.go b/errors.go
index 61c9639..6c82c26 100644
--- a/errors.go
+++ b/errors.go
@@ -106,6 +106,11 @@
 		ResultErrorFields
 	}
 
+	// DoesNotMatchFormatError. ErrorDetails: format
+	DoesNotMatchFormatError struct {
+		ResultErrorFields
+	}
+
 	// MultipleOfError. ErrorDetails: multiple
 	MultipleOfError struct {
 		ResultErrorFields
@@ -197,6 +202,9 @@
 	case *DoesNotMatchPatternError:
 		t = "pattern"
 		d = locale.DoesNotMatchPattern()
+	case *DoesNotMatchFormatError:
+		t = "format"
+		d = locale.DoesNotMatchFormat()
 	case *MultipleOfError:
 		t = "multiple_of"
 		d = locale.MultipleOf()
diff --git a/formatters.go b/formatters.go
new file mode 100644
index 0000000..b3396d0
--- /dev/null
+++ b/formatters.go
@@ -0,0 +1,78 @@
+package gojsonschema
+
+import (
+	"reflect"
+	"regexp"
+)
+
+type (
+	// FormatChecker is the interface all formatters added to FormatterChain must implement
+	FormatChecker interface {
+		IsFormat(input string) bool
+	}
+
+	// FormatterChain holds the formatters
+	FormatterChain struct {
+		formatters map[string]FormatChecker
+	}
+
+	// EmailFormatter verifies emails
+	EmailFormatter struct{}
+)
+
+var (
+	// Formatters holds the valid formatters, and is a public variable
+	// so library users can add custom formatters
+	Formatters = FormatterChain{
+		formatters: map[string]FormatChecker{
+			"email": EmailFormatter{},
+		},
+	}
+
+	// Regex credit: https://github.com/asaskevich/govalidator
+	rxEmail = regexp.MustCompile("^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$")
+)
+
+// Add adds a FormatChecker to the FormatterChain
+// The name used will be the value used for the format key in your json schema
+func (c *FormatterChain) Add(name string, f FormatChecker) *FormatterChain {
+	c.formatters[name] = f
+
+	return c
+}
+
+// Remove deletes a FormatChecker from the FormatterChain (if it exists)
+func (c *FormatterChain) Remove(name string) *FormatterChain {
+	delete(c.formatters, name)
+
+	return c
+}
+
+// Has checks to see if the FormatterChain holds a FormatChecker with the given name
+func (c *FormatterChain) Has(name string) bool {
+	_, ok := c.formatters[name]
+
+	return ok
+}
+
+// IsFormat will check an input against a FormatChecker with the given name
+// to see if it is the correct format
+func (c *FormatterChain) IsFormat(name string, input interface{}) bool {
+	f, ok := c.formatters[name]
+
+	if !ok {
+		return false
+	}
+
+	if !isKind(input, reflect.String) {
+		return false
+	}
+
+	inputString := input.(string)
+
+	return f.IsFormat(inputString)
+}
+
+func (f EmailFormatter) IsFormat(input string) bool {
+	return rxEmail.MatchString(input)
+}
diff --git a/json_schema_test_suite/format/data_00.json b/json_schema_test_suite/format/data_00.json
new file mode 100644
index 0000000..60bc259
--- /dev/null
+++ b/json_schema_test_suite/format/data_00.json
@@ -0,0 +1 @@
+"test"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_01.json b/json_schema_test_suite/format/data_01.json
new file mode 100644
index 0000000..6d9ec40
--- /dev/null
+++ b/json_schema_test_suite/format/data_01.json
@@ -0,0 +1 @@
+"test@"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_02.json b/json_schema_test_suite/format/data_02.json
new file mode 100644
index 0000000..6b8674d
--- /dev/null
+++ b/json_schema_test_suite/format/data_02.json
@@ -0,0 +1 @@
+"test@test.com"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_03.json b/json_schema_test_suite/format/data_03.json
new file mode 100644
index 0000000..970043c
--- /dev/null
+++ b/json_schema_test_suite/format/data_03.json
@@ -0,0 +1 @@
+"AB-10105"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_04.json b/json_schema_test_suite/format/data_04.json
new file mode 100644
index 0000000..140843f
--- /dev/null
+++ b/json_schema_test_suite/format/data_04.json
@@ -0,0 +1 @@
+"ABC10105"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/schema_0.json b/json_schema_test_suite/format/schema_0.json
new file mode 100644
index 0000000..b54a202
--- /dev/null
+++ b/json_schema_test_suite/format/schema_0.json
@@ -0,0 +1 @@
+{"type": "string", "format": "email"}
\ No newline at end of file
diff --git a/json_schema_test_suite/format/schema_1.json b/json_schema_test_suite/format/schema_1.json
new file mode 100644
index 0000000..7df6711
--- /dev/null
+++ b/json_schema_test_suite/format/schema_1.json
@@ -0,0 +1 @@
+{"type": "string", "format": "invoice"}
\ No newline at end of file
diff --git a/locales.go b/locales.go
index 2312a23..de05d60 100644
--- a/locales.go
+++ b/locales.go
@@ -48,6 +48,7 @@
 		StringGTE() string
 		StringLTE() string
 		DoesNotMatchPattern() string
+		DoesNotMatchFormat() string
 		MultipleOf() string
 		NumberGTE() string
 		NumberGT() string
@@ -63,6 +64,7 @@
 		CannotBeGT() string
 		MustBeOfType() string
 		MustBeValidRegex() string
+		MustBeValidFormat() string
 		MustBeGTEZero() string
 		KeyCannotBeGreaterThan() string
 		KeyItemsMustBeOfType() string
@@ -160,6 +162,10 @@
 	return `Does not match pattern '%pattern%'`
 }
 
+func (l DefaultLocale) DoesNotMatchFormat() string {
+	return `Does not match format '%format%'`
+}
+
 func (l DefaultLocale) MultipleOf() string {
 	return `Must be a multiple of %multiple%`
 }
@@ -213,6 +219,10 @@
 	return `%key% must be a valid regex`
 }
 
+func (l DefaultLocale) MustBeValidFormat() string {
+	return `%key% must be a valid format %given%`
+}
+
 func (l DefaultLocale) MustBeGTEZero() string {
 	return `%key% must be greater than or equal to 0`
 }
diff --git a/schema.go b/schema.go
index f00674e..3f8da16 100644
--- a/schema.go
+++ b/schema.go
@@ -534,6 +534,18 @@
 		}
 	}
 
+	if existsMapKey(m, KEY_FORMAT) {
+		formatString, ok := m[KEY_FORMAT].(string)
+		if ok && Formatters.Has(formatString) {
+			currentSchema.format = formatString
+		} else {
+			return errors.New(formatErrorDescription(
+				Locale.MustBeValidFormat(),
+				ErrorDetails{"key": KEY_FORMAT, "given": m[KEY_FORMAT]},
+			))
+		}
+	}
+
 	// validation : object
 
 	if existsMapKey(m, KEY_MIN_PROPERTIES) {
diff --git a/schema_test.go b/schema_test.go
index 01ff133..f892f0b 100644
--- a/schema_test.go
+++ b/schema_test.go
@@ -29,12 +29,27 @@
 	"fmt"
 	"net/http"
 	"os"
+	"regexp"
 	"strconv"
 	"testing"
 )
 
 const displayErrorMessages = false
 
+var rxInvoice = regexp.MustCompile("^[A-Z]{2}-[0-9]{5}")
+
+// Used for remote schema in ref/schema_5.json that defines "uri" and "regex" types
+type alwaysTrueFormatter struct{}
+type invoiceFormatter struct{}
+
+func (a alwaysTrueFormatter) IsFormat(input string) bool {
+	return true
+}
+
+func (a invoiceFormatter) IsFormat(input string) bool {
+	return rxInvoice.MatchString(input)
+}
+
 func TestJsonSchemaTestSuite(t *testing.T) {
 
 	JsonSchemaTestSuiteMap := []map[string]string{
@@ -286,7 +301,12 @@
 		map[string]string{"phase": "fragment within remote ref", "test": "remote fragment valid", "schema": "refRemote/schema_1.json", "data": "refRemote/data_10.json", "valid": "true"},
 		map[string]string{"phase": "fragment within remote ref", "test": "remote fragment invalid", "schema": "refRemote/schema_1.json", "data": "refRemote/data_11.json", "valid": "false"},
 		map[string]string{"phase": "ref within remote ref", "test": "ref within ref valid", "schema": "refRemote/schema_2.json", "data": "refRemote/data_20.json", "valid": "true"},
-		map[string]string{"phase": "ref within remote ref", "test": "ref within ref invalid", "schema": "refRemote/schema_2.json", "data": "refRemote/data_21.json", "valid": "false"}}
+		map[string]string{"phase": "ref within remote ref", "test": "ref within ref invalid", "schema": "refRemote/schema_2.json", "data": "refRemote/data_21.json", "valid": "false"},
+		map[string]string{"phase": "format validation", "test": "email format is invalid", "schema": "format/schema_0.json", "data": "format/data_00.json", "valid": "false"},
+		map[string]string{"phase": "format validation", "test": "email format is invalid", "schema": "format/schema_0.json", "data": "format/data_01.json", "valid": "false"},
+		map[string]string{"phase": "format validation", "test": "email format valid", "schema": "format/schema_0.json", "data": "format/data_02.json", "valid": "true"},
+		map[string]string{"phase": "format validation", "test": "invoice format valid", "schema": "format/schema_1.json", "data": "format/data_03.json", "valid": "true"},
+		map[string]string{"phase": "format validation", "test": "invoice format is invalid", "schema": "format/schema_1.json", "data": "format/data_04.json", "valid": "false"}}
 
 	//TODO Pass failed tests : id(s) as scope for references is not implemented yet
 	//map[string]string{"phase": "change resolution scope", "test": "changed scope ref valid", "schema": "refRemote/schema_3.json", "data": "refRemote/data_30.json", "valid": "true"},
@@ -308,6 +328,12 @@
 		}
 	}()
 
+	// Used for remote schema in ref/schema_5.json that defines "uri" and "regex" types
+	Formatters.Add("uri", alwaysTrueFormatter{}).Add("regex", alwaysTrueFormatter{})
+
+	// Custom Formatter
+	Formatters.Add("invoice", invoiceFormatter{})
+
 	// Launch tests
 
 	for testJsonIndex, testJson := range JsonSchemaTestSuiteMap {
diff --git a/subSchema.go b/subSchema.go
index fa8124d..b249b7e 100644
--- a/subSchema.go
+++ b/subSchema.go
@@ -55,6 +55,7 @@
 	KEY_MIN_LENGTH            = "minLength"
 	KEY_MAX_LENGTH            = "maxLength"
 	KEY_PATTERN               = "pattern"
+	KEY_FORMAT                = "format"
 	KEY_MIN_PROPERTIES        = "minProperties"
 	KEY_MAX_PROPERTIES        = "maxProperties"
 	KEY_DEPENDENCIES          = "dependencies"
@@ -107,6 +108,7 @@
 	minLength *int
 	maxLength *int
 	pattern   *regexp.Regexp
+	format    string
 
 	// validation : object
 	minProperties *int
diff --git a/validation.go b/validation.go
index c0d7d05..d79147a 100644
--- a/validation.go
+++ b/validation.go
@@ -698,6 +698,18 @@
 		}
 	}
 
+	// format
+	if currentSubSchema.format != "" {
+		if !Formatters.IsFormat(currentSubSchema.format, stringValue) {
+			result.addError(
+				new(DoesNotMatchFormatError),
+				context,
+				value,
+				ErrorDetails{"format": currentSubSchema.format},
+			)
+		}
+	}
+
 	result.incrementScore()
 }