Merge pull request #57 from cristiangraz/feature/format
Add support for email format as well as the ability to define custom formats
diff --git a/README.md b/README.md
index 7c99649..e5f0797 100644
--- a/README.md
+++ b/README.md
@@ -201,6 +201,34 @@
```
%field% must be greater than or equal to %min%
```
+
+## 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
+{"type": "string", "format": "email"}
+````
+Available formats: date-time, hostname, email, ipv4, ipv4, uri
+
+For repetitive or more complex formats, you can create custom format checkers and add them to gojsonschema like this:
+
+```go
+// Define the format checker
+type RoleFormatChecker struct {}
+
+// Ensure it meets the gojsonschema.FormatChecker interface
+func (f RoleFormatChecker) IsFormat(input string) bool {
+ return strings.HasPrefix("ROLE_", input)
+}
+
+// Add it to the library
+gojsonschema.FormatCheckers.Add("role", RoleFormatChecker{})
+````
+
+Now to use in your json schema:
+````json
+{"type": "string", "format": "role"}
+````
+
## 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/format_checkers.go b/format_checkers.go
new file mode 100644
index 0000000..a6dff32
--- /dev/null
+++ b/format_checkers.go
@@ -0,0 +1,167 @@
+package gojsonschema
+
+import (
+ "net"
+ "net/url"
+ "reflect"
+ "regexp"
+ "time"
+)
+
+type (
+ // FormatChecker is the interface all formatters added to FormatCheckerChain must implement
+ FormatChecker interface {
+ IsFormat(input string) bool
+ }
+
+ // FormatCheckerChain holds the formatters
+ FormatCheckerChain struct {
+ formatters map[string]FormatChecker
+ }
+
+ // EmailFormatter verifies email address formats
+ EmailFormatChecker struct{}
+
+ // IPV4FormatChecker verifies IP addresses in the ipv4 format
+ IPV4FormatChecker struct{}
+
+ // IPV6FormatChecker verifies IP addresses in the ipv6 format
+ IPV6FormatChecker struct{}
+
+ // DateTimeFormatChecker verifies date/time formats per RFC3339 5.6
+ //
+ // Valid formats:
+ // Partial Time: HH:MM:SS
+ // Full Date: YYYY-MM-DD
+ // Full Time: HH:MM:SSZ-07:00
+ // Date Time: YYYY-MM-DDTHH:MM:SSZ-0700
+ //
+ // Where
+ // YYYY = 4DIGIT year
+ // MM = 2DIGIT month ; 01-12
+ // DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year
+ // HH = 2DIGIT hour ; 00-23
+ // MM = 2DIGIT ; 00-59
+ // SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
+ // T = Literal
+ // Z = Literal
+ //
+ // Note: Nanoseconds are also suported in all formats
+ //
+ // http://tools.ietf.org/html/rfc3339#section-5.6
+ DateTimeFormatChecker struct{}
+
+ // URIFormatCheckers validates a URI with a valid Scheme per RFC3986
+ URIFormatChecker struct{}
+
+ // HostnameFormatChecker validates a hostname is in the correct format
+ HostnameFormatChecker struct{}
+)
+
+var (
+ // Formatters holds the valid formatters, and is a public variable
+ // so library users can add custom formatters
+ FormatCheckers = FormatCheckerChain{
+ formatters: map[string]FormatChecker{
+ "date-time": DateTimeFormatChecker{},
+ "hostname": HostnameFormatChecker{},
+ "email": EmailFormatChecker{},
+ "ipv4": IPV4FormatChecker{},
+ "ipv6": IPV6FormatChecker{},
+ "uri": URIFormatChecker{},
+ },
+ }
+
+ // 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}])))\\.?$")
+
+ // Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname
+ rxHostname = regexp.MustCompile(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`)
+)
+
+// Add adds a FormatChecker to the FormatCheckerChain
+// The name used will be the value used for the format key in your json schema
+func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain {
+ c.formatters[name] = f
+
+ return c
+}
+
+// Remove deletes a FormatChecker from the FormatCheckerChain (if it exists)
+func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain {
+ delete(c.formatters, name)
+
+ return c
+}
+
+// Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name
+func (c *FormatCheckerChain) 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 *FormatCheckerChain) 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 EmailFormatChecker) IsFormat(input string) bool {
+ return rxEmail.MatchString(input)
+}
+
+// Credit: https://github.com/asaskevich/govalidator
+func (f IPV4FormatChecker) IsFormat(input string) bool {
+ ip := net.ParseIP(input)
+ return ip != nil && ip.To4() != nil
+}
+
+// Credit: https://github.com/asaskevich/govalidator
+func (f IPV6FormatChecker) IsFormat(input string) bool {
+ ip := net.ParseIP(input)
+ return ip != nil && ip.To4() == nil
+}
+
+func (f DateTimeFormatChecker) IsFormat(input string) bool {
+ formats := []string{
+ "15:04:05",
+ "15:04:05Z07:00",
+ "2006-01-02",
+ time.RFC3339,
+ time.RFC3339Nano,
+ }
+
+ for _, format := range formats {
+ if _, err := time.Parse(format, input); err == nil {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (f URIFormatChecker) IsFormat(input string) bool {
+ u, err := url.Parse(input)
+ if err != nil || u.Scheme == "" {
+ return false
+ }
+
+ return true
+}
+
+func (f HostnameFormatChecker) IsFormat(input string) bool {
+ return rxHostname.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/data_05.json b/json_schema_test_suite/format/data_05.json
new file mode 100644
index 0000000..e87a8ac
--- /dev/null
+++ b/json_schema_test_suite/format/data_05.json
@@ -0,0 +1 @@
+"05:15:37"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_06.json b/json_schema_test_suite/format/data_06.json
new file mode 100644
index 0000000..17f91eb
--- /dev/null
+++ b/json_schema_test_suite/format/data_06.json
@@ -0,0 +1 @@
+"2015-05-13"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_07.json b/json_schema_test_suite/format/data_07.json
new file mode 100644
index 0000000..f0218e9
--- /dev/null
+++ b/json_schema_test_suite/format/data_07.json
@@ -0,0 +1 @@
+"2015-6-31"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_08.json b/json_schema_test_suite/format/data_08.json
new file mode 100644
index 0000000..bf9e258
--- /dev/null
+++ b/json_schema_test_suite/format/data_08.json
@@ -0,0 +1 @@
+"2015-01-30 19:08:06"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_09.json b/json_schema_test_suite/format/data_09.json
new file mode 100644
index 0000000..1a57beb
--- /dev/null
+++ b/json_schema_test_suite/format/data_09.json
@@ -0,0 +1 @@
+"18:31:24-05:00"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_10.json b/json_schema_test_suite/format/data_10.json
new file mode 100644
index 0000000..00ed4ee
--- /dev/null
+++ b/json_schema_test_suite/format/data_10.json
@@ -0,0 +1 @@
+"2002-10-02T10:00:00-05:00"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_11.json b/json_schema_test_suite/format/data_11.json
new file mode 100644
index 0000000..a8f92d5
--- /dev/null
+++ b/json_schema_test_suite/format/data_11.json
@@ -0,0 +1 @@
+"2002-10-02T15:00:00Z"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_12.json b/json_schema_test_suite/format/data_12.json
new file mode 100644
index 0000000..0975607
--- /dev/null
+++ b/json_schema_test_suite/format/data_12.json
@@ -0,0 +1 @@
+"2002-10-02T15:00:00.05Z"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_13.json b/json_schema_test_suite/format/data_13.json
new file mode 100644
index 0000000..9f9dace
--- /dev/null
+++ b/json_schema_test_suite/format/data_13.json
@@ -0,0 +1 @@
+"example.com"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_14.json b/json_schema_test_suite/format/data_14.json
new file mode 100644
index 0000000..15a9a87
--- /dev/null
+++ b/json_schema_test_suite/format/data_14.json
@@ -0,0 +1 @@
+"sub.example.com"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_15.json b/json_schema_test_suite/format/data_15.json
new file mode 100644
index 0000000..5c8e9b7
--- /dev/null
+++ b/json_schema_test_suite/format/data_15.json
@@ -0,0 +1 @@
+"hello.co.uk"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_16.json b/json_schema_test_suite/format/data_16.json
new file mode 100644
index 0000000..f5a11ec
--- /dev/null
+++ b/json_schema_test_suite/format/data_16.json
@@ -0,0 +1 @@
+"http://example.com"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_17.json b/json_schema_test_suite/format/data_17.json
new file mode 100644
index 0000000..062bf8c
--- /dev/null
+++ b/json_schema_test_suite/format/data_17.json
@@ -0,0 +1 @@
+"example_com"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_18.json b/json_schema_test_suite/format/data_18.json
new file mode 100644
index 0000000..fd44410
--- /dev/null
+++ b/json_schema_test_suite/format/data_18.json
@@ -0,0 +1 @@
+"4.2.2.4"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_19.json b/json_schema_test_suite/format/data_19.json
new file mode 100644
index 0000000..1e490c6
--- /dev/null
+++ b/json_schema_test_suite/format/data_19.json
@@ -0,0 +1 @@
+"4.1.1111.45"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_20.json b/json_schema_test_suite/format/data_20.json
new file mode 100644
index 0000000..2891810
--- /dev/null
+++ b/json_schema_test_suite/format/data_20.json
@@ -0,0 +1 @@
+"FE80:0000:0000:0000:0202:B3FF:FE1E:8329"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_21.json b/json_schema_test_suite/format/data_21.json
new file mode 100644
index 0000000..ad20ef3
--- /dev/null
+++ b/json_schema_test_suite/format/data_21.json
@@ -0,0 +1 @@
+"FE80::0202:B3FF:FE1E:8329"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_22.json b/json_schema_test_suite/format/data_22.json
new file mode 100644
index 0000000..97dae8f
--- /dev/null
+++ b/json_schema_test_suite/format/data_22.json
@@ -0,0 +1 @@
+"1200::AB00:1234::2552:7777:1313"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_23.json b/json_schema_test_suite/format/data_23.json
new file mode 100644
index 0000000..ef38c10
--- /dev/null
+++ b/json_schema_test_suite/format/data_23.json
@@ -0,0 +1 @@
+"1200:0000:AB00:1234:O000:2552:7777:1313"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_24.json b/json_schema_test_suite/format/data_24.json
new file mode 100644
index 0000000..4187b90
--- /dev/null
+++ b/json_schema_test_suite/format/data_24.json
@@ -0,0 +1 @@
+"ftp://ftp.is.co.za/rfc/rfc1808.txt"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_25.json b/json_schema_test_suite/format/data_25.json
new file mode 100644
index 0000000..7816838
--- /dev/null
+++ b/json_schema_test_suite/format/data_25.json
@@ -0,0 +1 @@
+"mailto:john.doe@example.com"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_26.json b/json_schema_test_suite/format/data_26.json
new file mode 100644
index 0000000..df1cfae
--- /dev/null
+++ b/json_schema_test_suite/format/data_26.json
@@ -0,0 +1 @@
+"tel:+1-816-555-1212"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_27.json b/json_schema_test_suite/format/data_27.json
new file mode 100644
index 0000000..28995dd
--- /dev/null
+++ b/json_schema_test_suite/format/data_27.json
@@ -0,0 +1 @@
+"http://www.ietf.org/rfc/rfc2396.txt"
\ No newline at end of file
diff --git a/json_schema_test_suite/format/data_28.json b/json_schema_test_suite/format/data_28.json
new file mode 100644
index 0000000..6b620ed
--- /dev/null
+++ b/json_schema_test_suite/format/data_28.json
@@ -0,0 +1 @@
+"example.com/path/to/file"
\ 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/json_schema_test_suite/format/schema_2.json b/json_schema_test_suite/format/schema_2.json
new file mode 100644
index 0000000..0dc4b8f
--- /dev/null
+++ b/json_schema_test_suite/format/schema_2.json
@@ -0,0 +1 @@
+{"type": "string", "format": "date-time"}
\ No newline at end of file
diff --git a/json_schema_test_suite/format/schema_3.json b/json_schema_test_suite/format/schema_3.json
new file mode 100644
index 0000000..d1cf1a0
--- /dev/null
+++ b/json_schema_test_suite/format/schema_3.json
@@ -0,0 +1 @@
+{"type": "string", "format": "hostname"}
\ No newline at end of file
diff --git a/json_schema_test_suite/format/schema_4.json b/json_schema_test_suite/format/schema_4.json
new file mode 100644
index 0000000..8468509
--- /dev/null
+++ b/json_schema_test_suite/format/schema_4.json
@@ -0,0 +1 @@
+{"type": "string", "format": "ipv4"}
\ No newline at end of file
diff --git a/json_schema_test_suite/format/schema_5.json b/json_schema_test_suite/format/schema_5.json
new file mode 100644
index 0000000..ddee847
--- /dev/null
+++ b/json_schema_test_suite/format/schema_5.json
@@ -0,0 +1 @@
+{"type": "string", "format": "ipv6"}
\ No newline at end of file
diff --git a/json_schema_test_suite/format/schema_6.json b/json_schema_test_suite/format/schema_6.json
new file mode 100644
index 0000000..493546d
--- /dev/null
+++ b/json_schema_test_suite/format/schema_6.json
@@ -0,0 +1 @@
+{"type": "string", "format": "uri"}
\ 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..577c786 100644
--- a/schema.go
+++ b/schema.go
@@ -534,6 +534,18 @@
}
}
+ if existsMapKey(m, KEY_FORMAT) {
+ formatString, ok := m[KEY_FORMAT].(string)
+ if ok && FormatCheckers.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..db8d00d 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 alwaysTrueFormatChecker struct{}
+type invoiceFormatChecker struct{}
+
+func (a alwaysTrueFormatChecker) IsFormat(input string) bool {
+ return true
+}
+
+func (a invoiceFormatChecker) IsFormat(input string) bool {
+ return rxInvoice.MatchString(input)
+}
+
func TestJsonSchemaTestSuite(t *testing.T) {
JsonSchemaTestSuiteMap := []map[string]string{
@@ -286,7 +301,38 @@
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"},
+ map[string]string{"phase": "format validation", "test": "date-time format is valid", "schema": "format/schema_2.json", "data": "format/data_05.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "date-time format is valid", "schema": "format/schema_2.json", "data": "format/data_06.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "date-time format is invalid", "schema": "format/schema_2.json", "data": "format/data_07.json", "valid": "false"},
+ map[string]string{"phase": "format validation", "test": "date-time format is valid", "schema": "format/schema_2.json", "data": "format/data_08.json", "valid": "false"},
+ map[string]string{"phase": "format validation", "test": "date-time format is valid", "schema": "format/schema_2.json", "data": "format/data_09.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "date-time format is valid", "schema": "format/schema_2.json", "data": "format/data_10.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "date-time format is valid", "schema": "format/schema_2.json", "data": "format/data_11.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "date-time format is valid", "schema": "format/schema_2.json", "data": "format/data_12.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "hostname format is valid", "schema": "format/schema_3.json", "data": "format/data_13.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "hostname format is valid", "schema": "format/schema_3.json", "data": "format/data_14.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "hostname format is valid", "schema": "format/schema_3.json", "data": "format/data_15.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "hostname format is invalid", "schema": "format/schema_3.json", "data": "format/data_16.json", "valid": "false"},
+ map[string]string{"phase": "format validation", "test": "hostname format is invalid", "schema": "format/schema_3.json", "data": "format/data_17.json", "valid": "false"},
+ map[string]string{"phase": "format validation", "test": "ipv4 format is valid", "schema": "format/schema_4.json", "data": "format/data_18.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "ipv4 format is invalid", "schema": "format/schema_4.json", "data": "format/data_19.json", "valid": "false"},
+ map[string]string{"phase": "format validation", "test": "ipv6 format is valid", "schema": "format/schema_5.json", "data": "format/data_20.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "ipv6 format is valid", "schema": "format/schema_5.json", "data": "format/data_21.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "ipv6 format is invalid", "schema": "format/schema_5.json", "data": "format/data_22.json", "valid": "false"},
+ map[string]string{"phase": "format validation", "test": "ipv6 format is invalid", "schema": "format/schema_5.json", "data": "format/data_23.json", "valid": "false"},
+ map[string]string{"phase": "format validation", "test": "uri format is valid", "schema": "format/schema_6.json", "data": "format/data_24.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "uri format is valid", "schema": "format/schema_6.json", "data": "format/data_25.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "uri format is valid", "schema": "format/schema_6.json", "data": "format/data_26.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "uri format is valid", "schema": "format/schema_6.json", "data": "format/data_27.json", "valid": "true"},
+ map[string]string{"phase": "format validation", "test": "uri format is invalid", "schema": "format/schema_6.json", "data": "format/data_28.json", "valid": "false"},
+ map[string]string{"phase": "format validation", "test": "uri format is invalid", "schema": "format/schema_6.json", "data": "format/data_13.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 +354,12 @@
}
}()
+ // Used for remote schema in ref/schema_5.json that defines "regex" type
+ FormatCheckers.Add("regex", alwaysTrueFormatChecker{})
+
+ // Custom Formatter
+ FormatCheckers.Add("invoice", invoiceFormatChecker{})
+
// 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..0531dc7 100644
--- a/validation.go
+++ b/validation.go
@@ -698,6 +698,18 @@
}
}
+ // format
+ if currentSubSchema.format != "" {
+ if !FormatCheckers.IsFormat(currentSubSchema.format, stringValue) {
+ result.addError(
+ new(DoesNotMatchFormatError),
+ context,
+ value,
+ ErrorDetails{"format": currentSubSchema.format},
+ )
+ }
+ }
+
result.incrementScore()
}