vendor: xeipuuv/gojsonschema v1.2.0

full diff: https://github.com/xeipuuv/gojsonschema/compare/v1.1.0...v1.2.0

- Fix a race condition when registering new formats.
- Improve the performance of uniqueItems.
- Fix an issue where integers would be shown as a fraction
- Require format to be of type string and formats can now be registered after a schema is parsed.
- Improve the handling of true and false schema.
- Fix an issue where an invalid schema would cause a panic.
- Properly handle file URIs that contain spaces.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2020-06-30 16:20:03 +02:00
parent 1c6dd42dab
commit af62a0529b
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
17 changed files with 526 additions and 427 deletions

View File

@ -70,7 +70,7 @@ github.com/tonistiigi/fsutil c2c7d7b0e1441705cd802e5699c0
github.com/tonistiigi/units 6950e57a87eaf136bbe44ef2ec8e75b9e3569de2 github.com/tonistiigi/units 6950e57a87eaf136bbe44ef2ec8e75b9e3569de2
github.com/xeipuuv/gojsonpointer 02993c407bfbf5f6dae44c4f4b1cf6a39b5fc5bb github.com/xeipuuv/gojsonpointer 02993c407bfbf5f6dae44c4f4b1cf6a39b5fc5bb
github.com/xeipuuv/gojsonreference bd5ef7bd5415a7ac448318e64f11a24cd21e594b github.com/xeipuuv/gojsonreference bd5ef7bd5415a7ac448318e64f11a24cd21e594b
github.com/xeipuuv/gojsonschema f971f3cd73b2899de6923801c147f075263e0c50 # v1.1.0 github.com/xeipuuv/gojsonschema 82fcdeb203eb6ab2a67d0a623d9c19e5e5a64927 # v1.2.0
golang.org/x/crypto 2aa609cf4a9d7d1126360de73b55b6002f9e052a golang.org/x/crypto 2aa609cf4a9d7d1126360de73b55b6002f9e052a
golang.org/x/net 0de0cce0169b09b364e001f108dc0399ea8630b3 golang.org/x/net 0de0cce0169b09b364e001f108dc0399ea8630b3
golang.org/x/oauth2 bf48bf16ab8d622ce64ec6ce98d2c98f916b6303 golang.org/x/oauth2 bf48bf16ab8d622ce64ec6ce98d2c98f916b6303

View File

@ -1,5 +1,6 @@
[![GoDoc](https://godoc.org/github.com/xeipuuv/gojsonschema?status.svg)](https://godoc.org/github.com/xeipuuv/gojsonschema) [![GoDoc](https://godoc.org/github.com/xeipuuv/gojsonschema?status.svg)](https://godoc.org/github.com/xeipuuv/gojsonschema)
[![Build Status](https://travis-ci.org/xeipuuv/gojsonschema.svg)](https://travis-ci.org/xeipuuv/gojsonschema) [![Build Status](https://travis-ci.org/xeipuuv/gojsonschema.svg)](https://travis-ci.org/xeipuuv/gojsonschema)
[![Go Report Card](https://goreportcard.com/badge/github.com/xeipuuv/gojsonschema)](https://goreportcard.com/report/github.com/xeipuuv/gojsonschema)
# gojsonschema # gojsonschema
@ -343,7 +344,7 @@ Not all formats defined in draft-07 are available. Implemented formats are:
`email`, `uri` and `uri-reference` use the same validation code as their unicode counterparts `idn-email`, `iri` and `iri-reference`. If you rely on unicode support you should use the specific `email`, `uri` and `uri-reference` use the same validation code as their unicode counterparts `idn-email`, `iri` and `iri-reference`. If you rely on unicode support you should use the specific
unicode enabled formats for the sake of interoperability as other implementations might not support unicode in the regular formats. unicode enabled formats for the sake of interoperability as other implementations might not support unicode in the regular formats.
The validation code for `uri`, `idn-email` and their relatives use mostly standard library code. Go 1.5 and 1.6 contain some minor bugs with handling URIs and unicode. You are encouraged to use Go 1.7+ if you rely on these formats. The validation code for `uri`, `idn-email` and their relatives use mostly standard library code.
For repetitive or more complex formats, you can create custom format checkers and add them to gojsonschema like this: For repetitive or more complex formats, you can create custom format checkers and add them to gojsonschema like this:

View File

@ -22,8 +22,10 @@ import (
"github.com/xeipuuv/gojsonreference" "github.com/xeipuuv/gojsonreference"
) )
// Draft is a JSON-schema draft version
type Draft int type Draft int
// Supported Draft versions
const ( const (
Draft4 Draft = 4 Draft4 Draft = 4
Draft6 Draft = 6 Draft6 Draft = 6
@ -42,17 +44,17 @@ var drafts draftConfigs
func init() { func init() {
drafts = []draftConfig{ drafts = []draftConfig{
draftConfig{ {
Version: Draft4, Version: Draft4,
MetaSchemaURL: "http://json-schema.org/draft-04/schema", MetaSchemaURL: "http://json-schema.org/draft-04/schema",
MetaSchema: `{"id":"http://json-schema.org/draft-04/schema#","$schema":"http://json-schema.org/draft-04/schema#","description":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"positiveInteger":{"type":"integer","minimum":0},"positiveIntegerDefault0":{"allOf":[{"$ref":"#/definitions/positiveInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"minItems":1,"uniqueItems":true}},"type":"object","properties":{"id":{"type":"string"},"$schema":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"multipleOf":{"type":"number","minimum":0,"exclusiveMinimum":true},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"boolean","default":false},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"boolean","default":false},"maxLength":{"$ref":"#/definitions/positiveInteger"},"minLength":{"$ref":"#/definitions/positiveIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/positiveInteger"},"minItems":{"$ref":"#/definitions/positiveIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxProperties":{"$ref":"#/definitions/positiveInteger"},"minProperties":{"$ref":"#/definitions/positiveIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"dependencies":{"exclusiveMaximum":["maximum"],"exclusiveMinimum":["minimum"]},"default":{}}`, MetaSchema: `{"id":"http://json-schema.org/draft-04/schema#","$schema":"http://json-schema.org/draft-04/schema#","description":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"positiveInteger":{"type":"integer","minimum":0},"positiveIntegerDefault0":{"allOf":[{"$ref":"#/definitions/positiveInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"minItems":1,"uniqueItems":true}},"type":"object","properties":{"id":{"type":"string"},"$schema":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"multipleOf":{"type":"number","minimum":0,"exclusiveMinimum":true},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"boolean","default":false},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"boolean","default":false},"maxLength":{"$ref":"#/definitions/positiveInteger"},"minLength":{"$ref":"#/definitions/positiveIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/positiveInteger"},"minItems":{"$ref":"#/definitions/positiveIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxProperties":{"$ref":"#/definitions/positiveInteger"},"minProperties":{"$ref":"#/definitions/positiveIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"dependencies":{"exclusiveMaximum":["maximum"],"exclusiveMinimum":["minimum"]},"default":{}}`,
}, },
draftConfig{ {
Version: Draft6, Version: Draft6,
MetaSchemaURL: "http://json-schema.org/draft-06/schema", MetaSchemaURL: "http://json-schema.org/draft-06/schema",
MetaSchema: `{"$schema":"http://json-schema.org/draft-06/schema#","$id":"http://json-schema.org/draft-06/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"examples":{"type":"array","items":{}},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":{},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":{}}`, MetaSchema: `{"$schema":"http://json-schema.org/draft-06/schema#","$id":"http://json-schema.org/draft-06/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"examples":{"type":"array","items":{}},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":{},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":{}}`,
}, },
draftConfig{ {
Version: Draft7, Version: Draft7,
MetaSchemaURL: "http://json-schema.org/draft-07/schema", MetaSchemaURL: "http://json-schema.org/draft-07/schema",
MetaSchema: `{"$schema":"http://json-schema.org/draft-07/schema#","$id":"http://json-schema.org/draft-07/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"$comment":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":true,"readOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":true},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":true,"enum":{"type":"array","items":true,"minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"if":{"$ref":"#"},"then":{"$ref":"#"},"else":{"$ref":"#"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":true}`, MetaSchema: `{"$schema":"http://json-schema.org/draft-07/schema#","$id":"http://json-schema.org/draft-07/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"$comment":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":true,"readOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":true},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":true,"enum":{"type":"array","items":true,"minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"if":{"$ref":"#"},"then":{"$ref":"#"},"else":{"$ref":"#"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":true}`,
@ -90,6 +92,11 @@ func parseSchemaURL(documentNode interface{}) (string, *Draft, error) {
if isKind(documentNode, reflect.Bool) { if isKind(documentNode, reflect.Bool) {
return "", nil, nil return "", nil, nil
} }
if !isKind(documentNode, reflect.Map) {
return "", nil, errors.New("schema is invalid")
}
m := documentNode.(map[string]interface{}) m := documentNode.(map[string]interface{})
if existsMapKey(m, KEY_SCHEMA) { if existsMapKey(m, KEY_SCHEMA) {

View File

@ -6,7 +6,7 @@ import (
"text/template" "text/template"
) )
var errorTemplates errorTemplate = errorTemplate{template.New("errors-new"), sync.RWMutex{}} var errorTemplates = errorTemplate{template.New("errors-new"), sync.RWMutex{}}
// template.Template is not thread-safe for writing, so some locking is done // template.Template is not thread-safe for writing, so some locking is done
// sync.RWMutex is used for efficiently locking when new templates are created // sync.RWMutex is used for efficiently locking when new templates are created
@ -16,157 +16,194 @@ type errorTemplate struct {
} }
type ( type (
// RequiredError. ErrorDetails: property string
// FalseError. ErrorDetails: -
FalseError struct {
ResultErrorFields
}
// RequiredError indicates that a required field is missing
// ErrorDetails: property string
RequiredError struct { RequiredError struct {
ResultErrorFields ResultErrorFields
} }
// InvalidTypeError. ErrorDetails: expected, given // InvalidTypeError indicates that a field has the incorrect type
// ErrorDetails: expected, given
InvalidTypeError struct { InvalidTypeError struct {
ResultErrorFields ResultErrorFields
} }
// NumberAnyOfError. ErrorDetails: - // NumberAnyOfError is produced in case of a failing "anyOf" validation
// ErrorDetails: -
NumberAnyOfError struct { NumberAnyOfError struct {
ResultErrorFields ResultErrorFields
} }
// NumberOneOfError. ErrorDetails: - // NumberOneOfError is produced in case of a failing "oneOf" validation
// ErrorDetails: -
NumberOneOfError struct { NumberOneOfError struct {
ResultErrorFields ResultErrorFields
} }
// NumberAllOfError. ErrorDetails: - // NumberAllOfError is produced in case of a failing "allOf" validation
// ErrorDetails: -
NumberAllOfError struct { NumberAllOfError struct {
ResultErrorFields ResultErrorFields
} }
// NumberNotError. ErrorDetails: - // NumberNotError is produced if a "not" validation failed
// ErrorDetails: -
NumberNotError struct { NumberNotError struct {
ResultErrorFields ResultErrorFields
} }
// MissingDependencyError. ErrorDetails: dependency // MissingDependencyError is produced in case of a "missing dependency" problem
// ErrorDetails: dependency
MissingDependencyError struct { MissingDependencyError struct {
ResultErrorFields ResultErrorFields
} }
// InternalError. ErrorDetails: error // InternalError indicates an internal error
// ErrorDetails: error
InternalError struct { InternalError struct {
ResultErrorFields ResultErrorFields
} }
// ConstError. ErrorDetails: allowed // ConstError indicates a const error
// ErrorDetails: allowed
ConstError struct { ConstError struct {
ResultErrorFields ResultErrorFields
} }
// EnumError. ErrorDetails: allowed // EnumError indicates an enum error
// ErrorDetails: allowed
EnumError struct { EnumError struct {
ResultErrorFields ResultErrorFields
} }
// ArrayNoAdditionalItemsError. ErrorDetails: - // ArrayNoAdditionalItemsError is produced if additional items were found, but not allowed
// ErrorDetails: -
ArrayNoAdditionalItemsError struct { ArrayNoAdditionalItemsError struct {
ResultErrorFields ResultErrorFields
} }
// ArrayMinItemsError. ErrorDetails: min // ArrayMinItemsError is produced if an array contains less items than the allowed minimum
// ErrorDetails: min
ArrayMinItemsError struct { ArrayMinItemsError struct {
ResultErrorFields ResultErrorFields
} }
// ArrayMaxItemsError. ErrorDetails: max // ArrayMaxItemsError is produced if an array contains more items than the allowed maximum
// ErrorDetails: max
ArrayMaxItemsError struct { ArrayMaxItemsError struct {
ResultErrorFields ResultErrorFields
} }
// ItemsMustBeUniqueError. ErrorDetails: type, i, j // ItemsMustBeUniqueError is produced if an array requires unique items, but contains non-unique items
// ErrorDetails: type, i, j
ItemsMustBeUniqueError struct { ItemsMustBeUniqueError struct {
ResultErrorFields ResultErrorFields
} }
// ArrayContainsError. ErrorDetails: // ArrayContainsError is produced if an array contains invalid items
// ErrorDetails:
ArrayContainsError struct { ArrayContainsError struct {
ResultErrorFields ResultErrorFields
} }
// ArrayMinPropertiesError. ErrorDetails: min // ArrayMinPropertiesError is produced if an object contains less properties than the allowed minimum
// ErrorDetails: min
ArrayMinPropertiesError struct { ArrayMinPropertiesError struct {
ResultErrorFields ResultErrorFields
} }
// ArrayMaxPropertiesError. ErrorDetails: max // ArrayMaxPropertiesError is produced if an object contains more properties than the allowed maximum
// ErrorDetails: max
ArrayMaxPropertiesError struct { ArrayMaxPropertiesError struct {
ResultErrorFields ResultErrorFields
} }
// AdditionalPropertyNotAllowedError. ErrorDetails: property // AdditionalPropertyNotAllowedError is produced if an object has additional properties, but not allowed
// ErrorDetails: property
AdditionalPropertyNotAllowedError struct { AdditionalPropertyNotAllowedError struct {
ResultErrorFields ResultErrorFields
} }
// InvalidPropertyPatternError. ErrorDetails: property, pattern // InvalidPropertyPatternError is produced if an pattern was found
// ErrorDetails: property, pattern
InvalidPropertyPatternError struct { InvalidPropertyPatternError struct {
ResultErrorFields ResultErrorFields
} }
// InvalidPopertyNameError. ErrorDetails: property // InvalidPropertyNameError is produced if an invalid-named property was found
// ErrorDetails: property
InvalidPropertyNameError struct { InvalidPropertyNameError struct {
ResultErrorFields ResultErrorFields
} }
// StringLengthGTEError. ErrorDetails: min // StringLengthGTEError is produced if a string is shorter than the minimum required length
// ErrorDetails: min
StringLengthGTEError struct { StringLengthGTEError struct {
ResultErrorFields ResultErrorFields
} }
// StringLengthLTEError. ErrorDetails: max // StringLengthLTEError is produced if a string is longer than the maximum allowed length
// ErrorDetails: max
StringLengthLTEError struct { StringLengthLTEError struct {
ResultErrorFields ResultErrorFields
} }
// DoesNotMatchPatternError. ErrorDetails: pattern // DoesNotMatchPatternError is produced if a string does not match the defined pattern
// ErrorDetails: pattern
DoesNotMatchPatternError struct { DoesNotMatchPatternError struct {
ResultErrorFields ResultErrorFields
} }
// DoesNotMatchFormatError. ErrorDetails: format // DoesNotMatchFormatError is produced if a string does not match the defined format
// ErrorDetails: format
DoesNotMatchFormatError struct { DoesNotMatchFormatError struct {
ResultErrorFields ResultErrorFields
} }
// MultipleOfError. ErrorDetails: multiple // MultipleOfError is produced if a number is not a multiple of the defined multipleOf
// ErrorDetails: multiple
MultipleOfError struct { MultipleOfError struct {
ResultErrorFields ResultErrorFields
} }
// NumberGTEError. ErrorDetails: min // NumberGTEError is produced if a number is lower than the allowed minimum
// ErrorDetails: min
NumberGTEError struct { NumberGTEError struct {
ResultErrorFields ResultErrorFields
} }
// NumberGTError. ErrorDetails: min // NumberGTError is produced if a number is lower than, or equal to the specified minimum, and exclusiveMinimum is set
// ErrorDetails: min
NumberGTError struct { NumberGTError struct {
ResultErrorFields ResultErrorFields
} }
// NumberLTEError. ErrorDetails: max // NumberLTEError is produced if a number is higher than the allowed maximum
// ErrorDetails: max
NumberLTEError struct { NumberLTEError struct {
ResultErrorFields ResultErrorFields
} }
// NumberLTError. ErrorDetails: max // NumberLTError is produced if a number is higher than, or equal to the specified maximum, and exclusiveMaximum is set
// ErrorDetails: max
NumberLTError struct { NumberLTError struct {
ResultErrorFields ResultErrorFields
} }
// ConditionThenError. ErrorDetails: - // ConditionThenError is produced if a condition's "then" validation is invalid
// ErrorDetails: -
ConditionThenError struct { ConditionThenError struct {
ResultErrorFields ResultErrorFields
} }
// ConditionElseError. ErrorDetails: - // ConditionElseError is produced if a condition's "else" condition is invalid
// ErrorDetails: -
ConditionElseError struct { ConditionElseError struct {
ResultErrorFields ResultErrorFields
} }
@ -177,6 +214,9 @@ func newError(err ResultError, context *JsonContext, value interface{}, locale l
var t string var t string
var d string var d string
switch err.(type) { switch err.(type) {
case *FalseError:
t = "false"
d = locale.False()
case *RequiredError: case *RequiredError:
t = "required" t = "required"
d = locale.Required() d = locale.Required()

View File

@ -13,6 +13,7 @@ import (
type ( type (
// FormatChecker is the interface all formatters added to FormatCheckerChain must implement // FormatChecker is the interface all formatters added to FormatCheckerChain must implement
FormatChecker interface { FormatChecker interface {
// IsFormat checks if input has the correct format and type
IsFormat(input interface{}) bool IsFormat(input interface{}) bool
} }
@ -21,13 +22,13 @@ type (
formatters map[string]FormatChecker formatters map[string]FormatChecker
} }
// EmailFormatter verifies email address formats // EmailFormatChecker verifies email address formats
EmailFormatChecker struct{} EmailFormatChecker struct{}
// IPV4FormatChecker verifies IP addresses in the ipv4 format // IPV4FormatChecker verifies IP addresses in the IPv4 format
IPV4FormatChecker struct{} IPV4FormatChecker struct{}
// IPV6FormatChecker verifies IP addresses in the ipv6 format // IPV6FormatChecker verifies IP addresses in the IPv6 format
IPV6FormatChecker struct{} IPV6FormatChecker struct{}
// DateTimeFormatChecker verifies date/time formats per RFC3339 5.6 // DateTimeFormatChecker verifies date/time formats per RFC3339 5.6
@ -53,8 +54,29 @@ type (
// http://tools.ietf.org/html/rfc3339#section-5.6 // http://tools.ietf.org/html/rfc3339#section-5.6
DateTimeFormatChecker struct{} DateTimeFormatChecker struct{}
// DateFormatChecker verifies date formats
//
// Valid format:
// Full Date: YYYY-MM-DD
//
// 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
DateFormatChecker struct{} DateFormatChecker struct{}
// TimeFormatChecker verifies time formats
//
// Valid formats:
// Partial Time: HH:MM:SS
// Full Time: HH:MM:SSZ-07:00
//
// Where
// HH = 2DIGIT hour ; 00-23
// MM = 2DIGIT ; 00-59
// SS = 2DIGIT ; 00-58, 00-60 based on leap second rules
// T = Literal
// Z = Literal
TimeFormatChecker struct{} TimeFormatChecker struct{}
// URIFormatChecker validates a URI with a valid Scheme per RFC3986 // URIFormatChecker validates a URI with a valid Scheme per RFC3986
@ -83,7 +105,7 @@ type (
) )
var ( var (
// Formatters holds the valid formatters, and is a public variable // FormatCheckers holds the valid formatters, and is a public variable
// so library users can add custom formatters // so library users can add custom formatters
FormatCheckers = FormatCheckerChain{ FormatCheckers = FormatCheckerChain{
formatters: map[string]FormatChecker{ formatters: map[string]FormatChecker{
@ -119,7 +141,7 @@ var (
rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$") rxRelJSONPointer = regexp.MustCompile("^(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~/]|~0|~1)*)*)$")
lock = new(sync.Mutex) lock = new(sync.RWMutex)
) )
// Add adds a FormatChecker to the FormatCheckerChain // Add adds a FormatChecker to the FormatCheckerChain
@ -143,9 +165,9 @@ func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain {
// Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name // Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name
func (c *FormatCheckerChain) Has(name string) bool { func (c *FormatCheckerChain) Has(name string) bool {
lock.Lock() lock.RLock()
_, ok := c.formatters[name] _, ok := c.formatters[name]
lock.Unlock() lock.RUnlock()
return ok return ok
} }
@ -153,55 +175,57 @@ func (c *FormatCheckerChain) Has(name string) bool {
// IsFormat will check an input against a FormatChecker with the given name // IsFormat will check an input against a FormatChecker with the given name
// to see if it is the correct format // 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{}) bool {
lock.RLock()
f, ok := c.formatters[name] f, ok := c.formatters[name]
lock.RUnlock()
// If a format is unrecognized it should always pass validation
if !ok { if !ok {
return false return true
} }
return f.IsFormat(input) 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{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
_, err := mail.ParseAddress(asString) _, err := mail.ParseAddress(asString)
return err == nil return err == nil
} }
// Credit: https://github.com/asaskevich/govalidator // IsFormat checks if input is a correctly formatted IPv4-address
func (f IPV4FormatChecker) IsFormat(input interface{}) bool { func (f IPV4FormatChecker) IsFormat(input interface{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
// Credit: https://github.com/asaskevich/govalidator
ip := net.ParseIP(asString) ip := net.ParseIP(asString)
return ip != nil && strings.Contains(asString, ".") return ip != nil && strings.Contains(asString, ".")
} }
// Credit: https://github.com/asaskevich/govalidator // IsFormat checks if input is a correctly formatted IPv6=address
func (f IPV6FormatChecker) IsFormat(input interface{}) bool { func (f IPV6FormatChecker) IsFormat(input interface{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
// Credit: https://github.com/asaskevich/govalidator
ip := net.ParseIP(asString) ip := net.ParseIP(asString)
return ip != nil && strings.Contains(asString, ":") return 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{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
@ -222,18 +246,20 @@ func (f DateTimeFormatChecker) IsFormat(input interface{}) bool {
return false return false
} }
// 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{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
_, err := time.Parse("2006-01-02", asString) _, err := time.Parse("2006-01-02", asString)
return err == nil return 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{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
@ -245,10 +271,10 @@ func (f TimeFormatChecker) IsFormat(input interface{}) bool {
return err == nil return err == nil
} }
// IsFormat checks if input is correctly formatted URI with a valid Scheme per RFC3986
func (f URIFormatChecker) IsFormat(input interface{}) bool { func (f URIFormatChecker) IsFormat(input interface{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
@ -261,10 +287,10 @@ func (f URIFormatChecker) IsFormat(input interface{}) bool {
return !strings.Contains(asString, `\`) return !strings.Contains(asString, `\`)
} }
// 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{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
@ -272,9 +298,10 @@ func (f URIReferenceFormatChecker) IsFormat(input interface{}) bool {
return err == nil && !strings.Contains(asString, `\`) return 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{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
@ -286,31 +313,30 @@ func (f URITemplateFormatChecker) IsFormat(input interface{}) bool {
return rxURITemplate.MatchString(u.Path) return 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{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
return rxHostname.MatchString(asString) && len(asString) < 256 return 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{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
return rxUUID.MatchString(asString) return rxUUID.MatchString(asString)
} }
// IsFormat implements FormatChecker interface. // IsFormat checks if input is a correctly formatted regular expression
func (f RegexFormatChecker) IsFormat(input interface{}) bool { func (f RegexFormatChecker) IsFormat(input interface{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
@ -318,24 +344,23 @@ func (f RegexFormatChecker) IsFormat(input interface{}) bool {
return true return true
} }
_, err := regexp.Compile(asString) _, err := regexp.Compile(asString)
if err != nil { return err == nil
return false
}
return true
} }
// 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{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }
return rxJSONPointer.MatchString(asString) return 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{}) bool {
asString, ok := input.(string) asString, ok := input.(string)
if ok == false { if !ok {
return false return false
} }

7
vendor/github.com/xeipuuv/gojsonschema/go.mod generated vendored Normal file
View File

@ -0,0 +1,7 @@
module github.com/xeipuuv/gojsonschema
require (
github.com/stretchr/testify v1.3.0
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415
)

View File

@ -32,6 +32,7 @@ type JsonContext struct {
tail *JsonContext tail *JsonContext
} }
// NewJsonContext creates a new JsonContext
func NewJsonContext(head string, tail *JsonContext) *JsonContext { func NewJsonContext(head string, tail *JsonContext) *JsonContext {
return &JsonContext{head, tail} return &JsonContext{head, tail}
} }

View File

@ -33,6 +33,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -43,8 +44,7 @@ import (
var osFS = osFileSystem(os.Open) var osFS = osFileSystem(os.Open)
// JSON loader interface // JSONLoader defines the JSON loader interface
type JSONLoader interface { type JSONLoader interface {
JsonSource() interface{} JsonSource() interface{}
LoadJSON() (interface{}, error) LoadJSON() (interface{}, error)
@ -52,17 +52,22 @@ type JSONLoader interface {
LoaderFactory() JSONLoaderFactory LoaderFactory() JSONLoaderFactory
} }
// JSONLoaderFactory defines the JSON loader factory interface
type JSONLoaderFactory interface { type JSONLoaderFactory interface {
// New creates a new JSON loader for the given source
New(source string) JSONLoader New(source string) JSONLoader
} }
// DefaultJSONLoaderFactory is the default JSON loader factory
type DefaultJSONLoaderFactory struct { type DefaultJSONLoaderFactory struct {
} }
// FileSystemJSONLoaderFactory is a JSON loader factory that uses http.FileSystem
type FileSystemJSONLoaderFactory struct { type FileSystemJSONLoaderFactory struct {
fs http.FileSystem fs http.FileSystem
} }
// New creates a new JSON loader for the given source
func (d DefaultJSONLoaderFactory) New(source string) JSONLoader { func (d DefaultJSONLoaderFactory) New(source string) JSONLoader {
return &jsonReferenceLoader{ return &jsonReferenceLoader{
fs: osFS, fs: osFS,
@ -70,6 +75,7 @@ func (d DefaultJSONLoaderFactory) New(source string) JSONLoader {
} }
} }
// New creates a new JSON loader for the given source
func (f FileSystemJSONLoaderFactory) New(source string) JSONLoader { func (f FileSystemJSONLoaderFactory) New(source string) JSONLoader {
return &jsonReferenceLoader{ return &jsonReferenceLoader{
fs: f.fs, fs: f.fs,
@ -80,6 +86,7 @@ func (f FileSystemJSONLoaderFactory) New(source string) JSONLoader {
// osFileSystem is a functional wrapper for os.Open that implements http.FileSystem. // osFileSystem is a functional wrapper for os.Open that implements http.FileSystem.
type osFileSystem func(string) (*os.File, error) type osFileSystem func(string) (*os.File, error)
// Opens a file with the given name
func (o osFileSystem) Open(name string) (http.File, error) { func (o osFileSystem) Open(name string) (http.File, error) {
return o(name) return o(name)
} }
@ -131,14 +138,20 @@ func (l *jsonReferenceLoader) LoadJSON() (interface{}, error) {
return nil, err return nil, err
} }
refToUrl := reference refToURL := reference
refToUrl.GetUrl().Fragment = "" refToURL.GetUrl().Fragment = ""
var document interface{} var document interface{}
if reference.HasFileScheme { if reference.HasFileScheme {
filename := strings.TrimPrefix(refToUrl.String(), "file://") filename := strings.TrimPrefix(refToURL.String(), "file://")
filename, err = url.QueryUnescape(filename)
if err != nil {
return nil, err
}
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// on Windows, a file URL may have an extra leading slash, use slashes // on Windows, a file URL may have an extra leading slash, use slashes
// instead of backslashes, and have spaces escaped // instead of backslashes, and have spaces escaped
@ -153,7 +166,7 @@ func (l *jsonReferenceLoader) LoadJSON() (interface{}, error) {
} else { } else {
document, err = l.loadFromHTTP(refToUrl.String()) document, err = l.loadFromHTTP(refToURL.String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -169,7 +182,7 @@ func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error)
// returned cached versions for metaschemas for drafts 4, 6 and 7 // returned cached versions for metaschemas for drafts 4, 6 and 7
// for performance and allow for easier offline use // for performance and allow for easier offline use
if metaSchema := drafts.GetMetaSchema(address); metaSchema != "" { if metaSchema := drafts.GetMetaSchema(address); metaSchema != "" {
return decodeJsonUsingNumber(strings.NewReader(metaSchema)) return decodeJSONUsingNumber(strings.NewReader(metaSchema))
} }
resp, err := http.Get(address) resp, err := http.Get(address)
@ -187,7 +200,7 @@ func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error)
return nil, err return nil, err
} }
return decodeJsonUsingNumber(bytes.NewReader(bodyBuff)) return decodeJSONUsingNumber(bytes.NewReader(bodyBuff))
} }
func (l *jsonReferenceLoader) loadFromFile(path string) (interface{}, error) { func (l *jsonReferenceLoader) loadFromFile(path string) (interface{}, error) {
@ -202,7 +215,7 @@ func (l *jsonReferenceLoader) loadFromFile(path string) (interface{}, error) {
return nil, err return nil, err
} }
return decodeJsonUsingNumber(bytes.NewReader(bodyBuff)) return decodeJSONUsingNumber(bytes.NewReader(bodyBuff))
} }
@ -224,13 +237,14 @@ func (l *jsonStringLoader) LoaderFactory() JSONLoaderFactory {
return &DefaultJSONLoaderFactory{} return &DefaultJSONLoaderFactory{}
} }
// NewStringLoader creates a new JSONLoader, taking a string as source
func NewStringLoader(source string) JSONLoader { func NewStringLoader(source string) JSONLoader {
return &jsonStringLoader{source: source} return &jsonStringLoader{source: source}
} }
func (l *jsonStringLoader) LoadJSON() (interface{}, error) { func (l *jsonStringLoader) LoadJSON() (interface{}, error) {
return decodeJsonUsingNumber(strings.NewReader(l.JsonSource().(string))) return decodeJSONUsingNumber(strings.NewReader(l.JsonSource().(string)))
} }
@ -252,12 +266,13 @@ func (l *jsonBytesLoader) LoaderFactory() JSONLoaderFactory {
return &DefaultJSONLoaderFactory{} return &DefaultJSONLoaderFactory{}
} }
// NewBytesLoader creates a new JSONLoader, taking a `[]byte` as source
func NewBytesLoader(source []byte) JSONLoader { func NewBytesLoader(source []byte) JSONLoader {
return &jsonBytesLoader{source: source} return &jsonBytesLoader{source: source}
} }
func (l *jsonBytesLoader) LoadJSON() (interface{}, error) { func (l *jsonBytesLoader) LoadJSON() (interface{}, error) {
return decodeJsonUsingNumber(bytes.NewReader(l.JsonSource().([]byte))) return decodeJSONUsingNumber(bytes.NewReader(l.JsonSource().([]byte)))
} }
// JSON Go (types) loader // JSON Go (types) loader
@ -279,6 +294,7 @@ func (l *jsonGoLoader) LoaderFactory() JSONLoaderFactory {
return &DefaultJSONLoaderFactory{} return &DefaultJSONLoaderFactory{}
} }
// NewGoLoader creates a new JSONLoader from a given Go struct
func NewGoLoader(source interface{}) JSONLoader { func NewGoLoader(source interface{}) JSONLoader {
return &jsonGoLoader{source: source} return &jsonGoLoader{source: source}
} }
@ -292,7 +308,7 @@ func (l *jsonGoLoader) LoadJSON() (interface{}, error) {
return nil, err return nil, err
} }
return decodeJsonUsingNumber(bytes.NewReader(jsonBytes)) return decodeJSONUsingNumber(bytes.NewReader(jsonBytes))
} }
@ -300,11 +316,13 @@ type jsonIOLoader struct {
buf *bytes.Buffer buf *bytes.Buffer
} }
// NewReaderLoader creates a new JSON loader using the provided io.Reader
func NewReaderLoader(source io.Reader) (JSONLoader, io.Reader) { func NewReaderLoader(source io.Reader) (JSONLoader, io.Reader) {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
return &jsonIOLoader{buf: buf}, io.TeeReader(source, buf) return &jsonIOLoader{buf: buf}, io.TeeReader(source, buf)
} }
// NewWriterLoader creates a new JSON loader using the provided io.Writer
func NewWriterLoader(source io.Writer) (JSONLoader, io.Writer) { func NewWriterLoader(source io.Writer) (JSONLoader, io.Writer) {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
return &jsonIOLoader{buf: buf}, io.MultiWriter(source, buf) return &jsonIOLoader{buf: buf}, io.MultiWriter(source, buf)
@ -315,7 +333,7 @@ func (l *jsonIOLoader) JsonSource() interface{} {
} }
func (l *jsonIOLoader) LoadJSON() (interface{}, error) { func (l *jsonIOLoader) LoadJSON() (interface{}, error) {
return decodeJsonUsingNumber(l.buf) return decodeJSONUsingNumber(l.buf)
} }
func (l *jsonIOLoader) JsonReference() (gojsonreference.JsonReference, error) { func (l *jsonIOLoader) JsonReference() (gojsonreference.JsonReference, error) {
@ -334,7 +352,8 @@ type jsonRawLoader struct {
source interface{} source interface{}
} }
func NewRawLoader(source interface{}) *jsonRawLoader { // NewRawLoader creates a new JSON raw loader for the given source
func NewRawLoader(source interface{}) JSONLoader {
return &jsonRawLoader{source: source} return &jsonRawLoader{source: source}
} }
func (l *jsonRawLoader) JsonSource() interface{} { func (l *jsonRawLoader) JsonSource() interface{} {
@ -350,7 +369,7 @@ func (l *jsonRawLoader) LoaderFactory() JSONLoaderFactory {
return &DefaultJSONLoaderFactory{} return &DefaultJSONLoaderFactory{}
} }
func decodeJsonUsingNumber(r io.Reader) (interface{}, error) { func decodeJSONUsingNumber(r io.Reader) (interface{}, error) {
var document interface{} var document interface{}

View File

@ -28,61 +28,163 @@ package gojsonschema
type ( type (
// locale is an interface for defining custom error strings // locale is an interface for defining custom error strings
locale interface { locale interface {
// False returns a format-string for "false" schema validation errors
False() string
// Required returns a format-string for "required" schema validation errors
Required() string Required() string
// InvalidType returns a format-string for "invalid type" schema validation errors
InvalidType() string InvalidType() string
// NumberAnyOf returns a format-string for "anyOf" schema validation errors
NumberAnyOf() string NumberAnyOf() string
// NumberOneOf returns a format-string for "oneOf" schema validation errors
NumberOneOf() string NumberOneOf() string
// NumberAllOf returns a format-string for "allOf" schema validation errors
NumberAllOf() string NumberAllOf() string
// NumberNot returns a format-string to format a NumberNotError
NumberNot() string NumberNot() string
// MissingDependency returns a format-string for "missing dependency" schema validation errors
MissingDependency() string MissingDependency() string
// Internal returns a format-string for internal errors
Internal() string Internal() string
// Const returns a format-string to format a ConstError
Const() string Const() string
// Enum returns a format-string to format an EnumError
Enum() string Enum() string
// ArrayNotEnoughItems returns a format-string to format an error for arrays having not enough items to match positional list of schema
ArrayNotEnoughItems() string ArrayNotEnoughItems() string
// ArrayNoAdditionalItems returns a format-string to format an ArrayNoAdditionalItemsError
ArrayNoAdditionalItems() string ArrayNoAdditionalItems() string
// ArrayMinItems returns a format-string to format an ArrayMinItemsError
ArrayMinItems() string ArrayMinItems() string
// ArrayMaxItems returns a format-string to format an ArrayMaxItemsError
ArrayMaxItems() string ArrayMaxItems() string
// Unique returns a format-string to format an ItemsMustBeUniqueError
Unique() string Unique() string
// ArrayContains returns a format-string to format an ArrayContainsError
ArrayContains() string ArrayContains() string
// ArrayMinProperties returns a format-string to format an ArrayMinPropertiesError
ArrayMinProperties() string ArrayMinProperties() string
// ArrayMaxProperties returns a format-string to format an ArrayMaxPropertiesError
ArrayMaxProperties() string ArrayMaxProperties() string
// AdditionalPropertyNotAllowed returns a format-string to format an AdditionalPropertyNotAllowedError
AdditionalPropertyNotAllowed() string AdditionalPropertyNotAllowed() string
// InvalidPropertyPattern returns a format-string to format an InvalidPropertyPatternError
InvalidPropertyPattern() string InvalidPropertyPattern() string
// InvalidPropertyName returns a format-string to format an InvalidPropertyNameError
InvalidPropertyName() string InvalidPropertyName() string
// StringGTE returns a format-string to format an StringLengthGTEError
StringGTE() string StringGTE() string
// StringLTE returns a format-string to format an StringLengthLTEError
StringLTE() string StringLTE() string
// DoesNotMatchPattern returns a format-string to format an DoesNotMatchPatternError
DoesNotMatchPattern() string DoesNotMatchPattern() string
// DoesNotMatchFormat returns a format-string to format an DoesNotMatchFormatError
DoesNotMatchFormat() string DoesNotMatchFormat() string
// MultipleOf returns a format-string to format an MultipleOfError
MultipleOf() string MultipleOf() string
// NumberGTE returns a format-string to format an NumberGTEError
NumberGTE() string NumberGTE() string
// NumberGT returns a format-string to format an NumberGTError
NumberGT() string NumberGT() string
// NumberLTE returns a format-string to format an NumberLTEError
NumberLTE() string NumberLTE() string
// NumberLT returns a format-string to format an NumberLTError
NumberLT() string NumberLT() string
// Schema validations // Schema validations
// RegexPattern returns a format-string to format a regex-pattern error
RegexPattern() string RegexPattern() string
// GreaterThanZero returns a format-string to format an error where a number must be greater than zero
GreaterThanZero() string GreaterThanZero() string
// MustBeOfA returns a format-string to format an error where a value is of the wrong type
MustBeOfA() string MustBeOfA() string
// MustBeOfAn returns a format-string to format an error where a value is of the wrong type
MustBeOfAn() string MustBeOfAn() string
// CannotBeUsedWithout returns a format-string to format a "cannot be used without" error
CannotBeUsedWithout() string CannotBeUsedWithout() string
// CannotBeGT returns a format-string to format an error where a value are greater than allowed
CannotBeGT() string CannotBeGT() string
// MustBeOfType returns a format-string to format an error where a value does not match the required type
MustBeOfType() string MustBeOfType() string
// MustBeValidRegex returns a format-string to format an error where a regex is invalid
MustBeValidRegex() string MustBeValidRegex() string
// MustBeValidFormat returns a format-string to format an error where a value does not match the expected format
MustBeValidFormat() string MustBeValidFormat() string
// MustBeGTEZero returns a format-string to format an error where a value must be greater or equal than 0
MustBeGTEZero() string MustBeGTEZero() string
// KeyCannotBeGreaterThan returns a format-string to format an error where a key is greater than the maximum allowed
KeyCannotBeGreaterThan() string KeyCannotBeGreaterThan() string
// KeyItemsMustBeOfType returns a format-string to format an error where a key is of the wrong type
KeyItemsMustBeOfType() string KeyItemsMustBeOfType() string
// KeyItemsMustBeUnique returns a format-string to format an error where keys are not unique
KeyItemsMustBeUnique() string KeyItemsMustBeUnique() string
// ReferenceMustBeCanonical returns a format-string to format a "reference must be canonical" error
ReferenceMustBeCanonical() string ReferenceMustBeCanonical() string
// NotAValidType returns a format-string to format an invalid type error
NotAValidType() string NotAValidType() string
// Duplicated returns a format-string to format an error where types are duplicated
Duplicated() string Duplicated() string
// HttpBadStatus returns a format-string for errors when loading a schema using HTTP
HttpBadStatus() string HttpBadStatus() string
// ParseError returns a format-string for JSON parsing errors
ParseError() string ParseError() string
// ConditionThen returns a format-string for ConditionThenError errors
ConditionThen() string ConditionThen() string
// ConditionElse returns a format-string for ConditionElseError errors
ConditionElse() string ConditionElse() string
// ErrorFormat // ErrorFormat returns a format string for errors
ErrorFormat() string ErrorFormat() string
} }
@ -90,214 +192,271 @@ type (
DefaultLocale struct{} DefaultLocale struct{}
) )
// False returns a format-string for "false" schema validation errors
func (l DefaultLocale) False() string {
return "False always fails validation"
}
// Required returns a format-string for "required" schema validation errors
func (l DefaultLocale) Required() string { func (l DefaultLocale) Required() string {
return `{{.property}} is required` return `{{.property}} is required`
} }
// InvalidType returns a format-string for "invalid type" schema validation errors
func (l DefaultLocale) InvalidType() string { func (l DefaultLocale) InvalidType() string {
return `Invalid type. Expected: {{.expected}}, given: {{.given}}` return `Invalid type. Expected: {{.expected}}, given: {{.given}}`
} }
// NumberAnyOf returns a format-string for "anyOf" schema validation errors
func (l DefaultLocale) NumberAnyOf() string { func (l DefaultLocale) NumberAnyOf() string {
return `Must validate at least one schema (anyOf)` return `Must validate at least one schema (anyOf)`
} }
// NumberOneOf returns a format-string for "oneOf" schema validation errors
func (l DefaultLocale) NumberOneOf() string { func (l DefaultLocale) NumberOneOf() string {
return `Must validate one and only one schema (oneOf)` return `Must validate one and only one schema (oneOf)`
} }
// NumberAllOf returns a format-string for "allOf" schema validation errors
func (l DefaultLocale) NumberAllOf() string { func (l DefaultLocale) NumberAllOf() string {
return `Must validate all the schemas (allOf)` return `Must validate all the schemas (allOf)`
} }
// NumberNot returns a format-string to format a NumberNotError
func (l DefaultLocale) NumberNot() string { func (l DefaultLocale) NumberNot() string {
return `Must not validate the schema (not)` return `Must not validate the schema (not)`
} }
// MissingDependency returns a format-string for "missing dependency" schema validation errors
func (l DefaultLocale) MissingDependency() string { func (l DefaultLocale) MissingDependency() string {
return `Has a dependency on {{.dependency}}` return `Has a dependency on {{.dependency}}`
} }
// Internal returns a format-string for internal errors
func (l DefaultLocale) Internal() string { func (l DefaultLocale) Internal() string {
return `Internal Error {{.error}}` return `Internal Error {{.error}}`
} }
// Const returns a format-string to format a ConstError
func (l DefaultLocale) Const() string { func (l DefaultLocale) Const() string {
return `{{.field}} does not match: {{.allowed}}` return `{{.field}} does not match: {{.allowed}}`
} }
// Enum returns a format-string to format an EnumError
func (l DefaultLocale) Enum() string { func (l DefaultLocale) Enum() string {
return `{{.field}} must be one of the following: {{.allowed}}` return `{{.field}} must be one of the following: {{.allowed}}`
} }
// ArrayNoAdditionalItems returns a format-string to format an ArrayNoAdditionalItemsError
func (l DefaultLocale) ArrayNoAdditionalItems() string { func (l DefaultLocale) ArrayNoAdditionalItems() string {
return `No additional items allowed on array` return `No additional items allowed on array`
} }
// ArrayNotEnoughItems returns a format-string to format an error for arrays having not enough items to match positional list of schema
func (l DefaultLocale) ArrayNotEnoughItems() string { func (l DefaultLocale) ArrayNotEnoughItems() string {
return `Not enough items on array to match positional list of schema` return `Not enough items on array to match positional list of schema`
} }
// ArrayMinItems returns a format-string to format an ArrayMinItemsError
func (l DefaultLocale) ArrayMinItems() string { func (l DefaultLocale) ArrayMinItems() string {
return `Array must have at least {{.min}} items` return `Array must have at least {{.min}} items`
} }
// ArrayMaxItems returns a format-string to format an ArrayMaxItemsError
func (l DefaultLocale) ArrayMaxItems() string { func (l DefaultLocale) ArrayMaxItems() string {
return `Array must have at most {{.max}} items` return `Array must have at most {{.max}} items`
} }
// Unique returns a format-string to format an ItemsMustBeUniqueError
func (l DefaultLocale) Unique() string { func (l DefaultLocale) Unique() string {
return `{{.type}} items[{{.i}},{{.j}}] must be unique` return `{{.type}} items[{{.i}},{{.j}}] must be unique`
} }
// ArrayContains returns a format-string to format an ArrayContainsError
func (l DefaultLocale) ArrayContains() string { func (l DefaultLocale) ArrayContains() string {
return `At least one of the items must match` return `At least one of the items must match`
} }
// ArrayMinProperties returns a format-string to format an ArrayMinPropertiesError
func (l DefaultLocale) ArrayMinProperties() string { func (l DefaultLocale) ArrayMinProperties() string {
return `Must have at least {{.min}} properties` return `Must have at least {{.min}} properties`
} }
// ArrayMaxProperties returns a format-string to format an ArrayMaxPropertiesError
func (l DefaultLocale) ArrayMaxProperties() string { func (l DefaultLocale) ArrayMaxProperties() string {
return `Must have at most {{.max}} properties` return `Must have at most {{.max}} properties`
} }
// AdditionalPropertyNotAllowed returns a format-string to format an AdditionalPropertyNotAllowedError
func (l DefaultLocale) AdditionalPropertyNotAllowed() string { func (l DefaultLocale) AdditionalPropertyNotAllowed() string {
return `Additional property {{.property}} is not allowed` return `Additional property {{.property}} is not allowed`
} }
// InvalidPropertyPattern returns a format-string to format an InvalidPropertyPatternError
func (l DefaultLocale) InvalidPropertyPattern() string { func (l DefaultLocale) InvalidPropertyPattern() string {
return `Property "{{.property}}" does not match pattern {{.pattern}}` return `Property "{{.property}}" does not match pattern {{.pattern}}`
} }
// InvalidPropertyName returns a format-string to format an InvalidPropertyNameError
func (l DefaultLocale) InvalidPropertyName() string { func (l DefaultLocale) InvalidPropertyName() string {
return `Property name of "{{.property}}" does not match` return `Property name of "{{.property}}" does not match`
} }
// StringGTE returns a format-string to format an StringLengthGTEError
func (l DefaultLocale) StringGTE() string { func (l DefaultLocale) StringGTE() string {
return `String length must be greater than or equal to {{.min}}` return `String length must be greater than or equal to {{.min}}`
} }
// StringLTE returns a format-string to format an StringLengthLTEError
func (l DefaultLocale) StringLTE() string { func (l DefaultLocale) StringLTE() string {
return `String length must be less than or equal to {{.max}}` return `String length must be less than or equal to {{.max}}`
} }
// DoesNotMatchPattern returns a format-string to format an DoesNotMatchPatternError
func (l DefaultLocale) DoesNotMatchPattern() string { func (l DefaultLocale) DoesNotMatchPattern() string {
return `Does not match pattern '{{.pattern}}'` return `Does not match pattern '{{.pattern}}'`
} }
// DoesNotMatchFormat returns a format-string to format an DoesNotMatchFormatError
func (l DefaultLocale) DoesNotMatchFormat() string { func (l DefaultLocale) DoesNotMatchFormat() string {
return `Does not match format '{{.format}}'` return `Does not match format '{{.format}}'`
} }
// MultipleOf returns a format-string to format an MultipleOfError
func (l DefaultLocale) MultipleOf() string { func (l DefaultLocale) MultipleOf() string {
return `Must be a multiple of {{.multiple}}` return `Must be a multiple of {{.multiple}}`
} }
// NumberGTE returns the format string to format a NumberGTEError
func (l DefaultLocale) NumberGTE() string { func (l DefaultLocale) NumberGTE() string {
return `Must be greater than or equal to {{.min}}` return `Must be greater than or equal to {{.min}}`
} }
// NumberGT returns the format string to format a NumberGTError
func (l DefaultLocale) NumberGT() string { func (l DefaultLocale) NumberGT() string {
return `Must be greater than {{.min}}` return `Must be greater than {{.min}}`
} }
// NumberLTE returns the format string to format a NumberLTEError
func (l DefaultLocale) NumberLTE() string { func (l DefaultLocale) NumberLTE() string {
return `Must be less than or equal to {{.max}}` return `Must be less than or equal to {{.max}}`
} }
// NumberLT returns the format string to format a NumberLTError
func (l DefaultLocale) NumberLT() string { func (l DefaultLocale) NumberLT() string {
return `Must be less than {{.max}}` return `Must be less than {{.max}}`
} }
// Schema validators // Schema validators
// RegexPattern returns a format-string to format a regex-pattern error
func (l DefaultLocale) RegexPattern() string { func (l DefaultLocale) RegexPattern() string {
return `Invalid regex pattern '{{.pattern}}'` return `Invalid regex pattern '{{.pattern}}'`
} }
// GreaterThanZero returns a format-string to format an error where a number must be greater than zero
func (l DefaultLocale) GreaterThanZero() string { func (l DefaultLocale) GreaterThanZero() string {
return `{{.number}} must be strictly greater than 0` return `{{.number}} must be strictly greater than 0`
} }
// MustBeOfA returns a format-string to format an error where a value is of the wrong type
func (l DefaultLocale) MustBeOfA() string { func (l DefaultLocale) MustBeOfA() string {
return `{{.x}} must be of a {{.y}}` return `{{.x}} must be of a {{.y}}`
} }
// MustBeOfAn returns a format-string to format an error where a value is of the wrong type
func (l DefaultLocale) MustBeOfAn() string { func (l DefaultLocale) MustBeOfAn() string {
return `{{.x}} must be of an {{.y}}` return `{{.x}} must be of an {{.y}}`
} }
// CannotBeUsedWithout returns a format-string to format a "cannot be used without" error
func (l DefaultLocale) CannotBeUsedWithout() string { func (l DefaultLocale) CannotBeUsedWithout() string {
return `{{.x}} cannot be used without {{.y}}` return `{{.x}} cannot be used without {{.y}}`
} }
// CannotBeGT returns a format-string to format an error where a value are greater than allowed
func (l DefaultLocale) CannotBeGT() string { func (l DefaultLocale) CannotBeGT() string {
return `{{.x}} cannot be greater than {{.y}}` return `{{.x}} cannot be greater than {{.y}}`
} }
// MustBeOfType returns a format-string to format an error where a value does not match the required type
func (l DefaultLocale) MustBeOfType() string { func (l DefaultLocale) MustBeOfType() string {
return `{{.key}} must be of type {{.type}}` return `{{.key}} must be of type {{.type}}`
} }
// MustBeValidRegex returns a format-string to format an error where a regex is invalid
func (l DefaultLocale) MustBeValidRegex() string { func (l DefaultLocale) MustBeValidRegex() string {
return `{{.key}} must be a valid regex` return `{{.key}} must be a valid regex`
} }
// MustBeValidFormat returns a format-string to format an error where a value does not match the expected format
func (l DefaultLocale) MustBeValidFormat() string { func (l DefaultLocale) MustBeValidFormat() string {
return `{{.key}} must be a valid format {{.given}}` return `{{.key}} must be a valid format {{.given}}`
} }
// MustBeGTEZero returns a format-string to format an error where a value must be greater or equal than 0
func (l DefaultLocale) MustBeGTEZero() string { func (l DefaultLocale) MustBeGTEZero() string {
return `{{.key}} must be greater than or equal to 0` return `{{.key}} must be greater than or equal to 0`
} }
// KeyCannotBeGreaterThan returns a format-string to format an error where a value is greater than the maximum allowed
func (l DefaultLocale) KeyCannotBeGreaterThan() string { func (l DefaultLocale) KeyCannotBeGreaterThan() string {
return `{{.key}} cannot be greater than {{.y}}` return `{{.key}} cannot be greater than {{.y}}`
} }
// KeyItemsMustBeOfType returns a format-string to format an error where a key is of the wrong type
func (l DefaultLocale) KeyItemsMustBeOfType() string { func (l DefaultLocale) KeyItemsMustBeOfType() string {
return `{{.key}} items must be {{.type}}` return `{{.key}} items must be {{.type}}`
} }
// KeyItemsMustBeUnique returns a format-string to format an error where keys are not unique
func (l DefaultLocale) KeyItemsMustBeUnique() string { func (l DefaultLocale) KeyItemsMustBeUnique() string {
return `{{.key}} items must be unique` return `{{.key}} items must be unique`
} }
// ReferenceMustBeCanonical returns a format-string to format a "reference must be canonical" error
func (l DefaultLocale) ReferenceMustBeCanonical() string { func (l DefaultLocale) ReferenceMustBeCanonical() string {
return `Reference {{.reference}} must be canonical` return `Reference {{.reference}} must be canonical`
} }
// NotAValidType returns a format-string to format an invalid type error
func (l DefaultLocale) NotAValidType() string { func (l DefaultLocale) NotAValidType() string {
return `has a primitive type that is NOT VALID -- given: {{.given}} Expected valid values are:{{.expected}}` return `has a primitive type that is NOT VALID -- given: {{.given}} Expected valid values are:{{.expected}}`
} }
// Duplicated returns a format-string to format an error where types are duplicated
func (l DefaultLocale) Duplicated() string { func (l DefaultLocale) Duplicated() string {
return `{{.type}} type is duplicated` return `{{.type}} type is duplicated`
} }
// HttpBadStatus returns a format-string for errors when loading a schema using HTTP
func (l DefaultLocale) HttpBadStatus() string { func (l DefaultLocale) HttpBadStatus() string {
return `Could not read schema from HTTP, response status is {{.status}}` return `Could not read schema from HTTP, response status is {{.status}}`
} }
// ErrorFormat returns a format string for errors
// Replacement options: field, description, context, value // Replacement options: field, description, context, value
func (l DefaultLocale) ErrorFormat() string { func (l DefaultLocale) ErrorFormat() string {
return `{{.field}}: {{.description}}` return `{{.field}}: {{.description}}`
} }
//Parse error // ParseError returns a format-string for JSON parsing errors
func (l DefaultLocale) ParseError() string { func (l DefaultLocale) ParseError() string {
return `Expected: {{.expected}}, given: Invalid JSON` return `Expected: {{.expected}}, given: Invalid JSON`
} }
// ConditionThen returns a format-string for ConditionThenError errors
// If/Else // If/Else
func (l DefaultLocale) ConditionThen() string { func (l DefaultLocale) ConditionThen() string {
return `Must validate "then" as "if" was valid` return `Must validate "then" as "if" was valid`
} }
// ConditionElse returns a format-string for ConditionElseError errors
func (l DefaultLocale) ConditionElse() string { func (l DefaultLocale) ConditionElse() string {
return `Must validate "else" as "if" was not valid` return `Must validate "else" as "if" was not valid`
} }
// constants
const ( const (
STRING_NUMBER = "number" STRING_NUMBER = "number"
STRING_ARRAY_OF_STRINGS = "array of strings" STRING_ARRAY_OF_STRINGS = "array of strings"

View File

@ -37,19 +37,34 @@ type (
// ResultError is the interface that library errors must implement // ResultError is the interface that library errors must implement
ResultError interface { ResultError interface {
// Field returns the field name without the root context
// i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName
Field() string Field() string
// SetType sets the error-type
SetType(string) SetType(string)
// Type returns the error-type
Type() string Type() string
// SetContext sets the JSON-context for the error
SetContext(*JsonContext) SetContext(*JsonContext)
// Context returns the JSON-context of the error
Context() *JsonContext Context() *JsonContext
// SetDescription sets a description for the error
SetDescription(string) SetDescription(string)
// Description returns the description of the error
Description() string Description() string
// SetDescriptionFormat sets the format for the description in the default text/template format
SetDescriptionFormat(string) SetDescriptionFormat(string)
// DescriptionFormat returns the format for the description in the default text/template format
DescriptionFormat() string DescriptionFormat() string
// SetValue sets the value related to the error
SetValue(interface{}) SetValue(interface{})
// Value returns the value related to the error
Value() interface{} Value() interface{}
// SetDetails sets the details specific to the error
SetDetails(ErrorDetails) SetDetails(ErrorDetails)
// Details returns details about the error
Details() ErrorDetails Details() ErrorDetails
// String returns a string representation of the error
String() string String() string
} }
@ -65,6 +80,7 @@ type (
details ErrorDetails details ErrorDetails
} }
// Result holds the result of a validation
Result struct { Result struct {
errors []ResultError errors []ResultError
// Scores how well the validation matched. Useful in generating // Scores how well the validation matched. Useful in generating
@ -73,60 +89,73 @@ type (
} }
) )
// Field outputs the field name without the root context // Field returns the field name without the root context
// i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName // i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName
func (v *ResultErrorFields) Field() string { func (v *ResultErrorFields) Field() string {
return strings.TrimPrefix(v.context.String(), STRING_ROOT_SCHEMA_PROPERTY+".") return strings.TrimPrefix(v.context.String(), STRING_ROOT_SCHEMA_PROPERTY+".")
} }
// SetType sets the error-type
func (v *ResultErrorFields) SetType(errorType string) { func (v *ResultErrorFields) SetType(errorType string) {
v.errorType = errorType v.errorType = errorType
} }
// Type returns the error-type
func (v *ResultErrorFields) Type() string { func (v *ResultErrorFields) Type() string {
return v.errorType return v.errorType
} }
// SetContext sets the JSON-context for the error
func (v *ResultErrorFields) SetContext(context *JsonContext) { func (v *ResultErrorFields) SetContext(context *JsonContext) {
v.context = context v.context = context
} }
// Context returns the JSON-context of the error
func (v *ResultErrorFields) Context() *JsonContext { func (v *ResultErrorFields) Context() *JsonContext {
return v.context return v.context
} }
// SetDescription sets a description for the error
func (v *ResultErrorFields) SetDescription(description string) { func (v *ResultErrorFields) SetDescription(description string) {
v.description = description v.description = description
} }
// Description returns the description of the error
func (v *ResultErrorFields) Description() string { func (v *ResultErrorFields) Description() string {
return v.description return v.description
} }
// SetDescriptionFormat sets the format for the description in the default text/template format
func (v *ResultErrorFields) SetDescriptionFormat(descriptionFormat string) { func (v *ResultErrorFields) SetDescriptionFormat(descriptionFormat string) {
v.descriptionFormat = descriptionFormat v.descriptionFormat = descriptionFormat
} }
// DescriptionFormat returns the format for the description in the default text/template format
func (v *ResultErrorFields) DescriptionFormat() string { func (v *ResultErrorFields) DescriptionFormat() string {
return v.descriptionFormat return v.descriptionFormat
} }
// SetValue sets the value related to the error
func (v *ResultErrorFields) SetValue(value interface{}) { func (v *ResultErrorFields) SetValue(value interface{}) {
v.value = value v.value = value
} }
// Value returns the value related to the error
func (v *ResultErrorFields) Value() interface{} { func (v *ResultErrorFields) Value() interface{} {
return v.value return v.value
} }
// SetDetails sets the details specific to the error
func (v *ResultErrorFields) SetDetails(details ErrorDetails) { func (v *ResultErrorFields) SetDetails(details ErrorDetails) {
v.details = details v.details = details
} }
// Details returns details about the error
func (v *ResultErrorFields) Details() ErrorDetails { func (v *ResultErrorFields) Details() ErrorDetails {
return v.details return v.details
} }
// String returns a string representation of the error
func (v ResultErrorFields) String() string { func (v ResultErrorFields) String() string {
// as a fallback, the value is displayed go style // as a fallback, the value is displayed go style
valueString := fmt.Sprintf("%v", v.value) valueString := fmt.Sprintf("%v", v.value)
@ -135,7 +164,7 @@ func (v ResultErrorFields) String() string {
if v.value == nil { if v.value == nil {
valueString = TYPE_NULL valueString = TYPE_NULL
} else { } else {
if vs, err := marshalToJsonString(v.value); err == nil { if vs, err := marshalToJSONString(v.value); err == nil {
if vs == nil { if vs == nil {
valueString = TYPE_NULL valueString = TYPE_NULL
} else { } else {
@ -152,15 +181,17 @@ func (v ResultErrorFields) String() string {
}) })
} }
// Valid indicates if no errors were found
func (v *Result) Valid() bool { func (v *Result) Valid() bool {
return len(v.errors) == 0 return len(v.errors) == 0
} }
// Errors returns the errors that were found
func (v *Result) Errors() []ResultError { func (v *Result) Errors() []ResultError {
return v.errors return v.errors
} }
// Add a fully filled error to the error set // AddError appends a fully filled error to the error set
// SetDescription() will be called with the result of the parsed err.DescriptionFormat() // SetDescription() will be called with the result of the parsed err.DescriptionFormat()
func (v *Result) AddError(err ResultError, details ErrorDetails) { func (v *Result) AddError(err ResultError, details ErrorDetails) {
if _, exists := details["context"]; !exists && err.Context() != nil { if _, exists := details["context"]; !exists && err.Context() != nil {

View File

@ -45,10 +45,12 @@ var (
ErrorTemplateFuncs template.FuncMap ErrorTemplateFuncs template.FuncMap
) )
// NewSchema instances a schema using the given JSONLoader
func NewSchema(l JSONLoader) (*Schema, error) { func NewSchema(l JSONLoader) (*Schema, error) {
return NewSchemaLoader().Compile(l) return NewSchemaLoader().Compile(l)
} }
// Schema holds a schema
type Schema struct { type Schema struct {
documentReference gojsonreference.JsonReference documentReference gojsonreference.JsonReference
rootSchema *subSchema rootSchema *subSchema
@ -61,6 +63,7 @@ func (d *Schema) parse(document interface{}, draft Draft) error {
return d.parseSchema(document, d.rootSchema) return d.parseSchema(document, d.rootSchema)
} }
// SetRootSchemaName sets the root-schema name
func (d *Schema) SetRootSchemaName(name string) { func (d *Schema) SetRootSchemaName(name string) {
d.rootSchema.property = name d.rootSchema.property = name
} }
@ -83,11 +86,8 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
// As of draft 6 "true" is equivalent to an empty schema "{}" and false equals "{"not":{}}" // As of draft 6 "true" is equivalent to an empty schema "{}" and false equals "{"not":{}}"
if *currentSchema.draft >= Draft6 && isKind(documentNode, reflect.Bool) { if *currentSchema.draft >= Draft6 && isKind(documentNode, reflect.Bool) {
b := documentNode.(bool) b := documentNode.(bool)
if b { currentSchema.pass = &b
documentNode = map[string]interface{}{} return nil
} else {
documentNode = map[string]interface{}{"not": true}
}
} }
if !isKind(documentNode, reflect.Map) { if !isKind(documentNode, reflect.Map) {
@ -267,8 +267,9 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
"given": KEY_TYPE, "given": KEY_TYPE,
}, },
)) ))
} else { }
currentSchema.types.Add(typeInArray.(string)) if err := currentSchema.types.Add(typeInArray.(string)); err != nil {
return err
} }
} }
@ -382,7 +383,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
if isKind(itemElement, reflect.Map, reflect.Bool) { if isKind(itemElement, reflect.Map, reflect.Bool) {
newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS} newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS}
newSchema.ref = currentSchema.ref newSchema.ref = currentSchema.ref
currentSchema.AddItemsChild(newSchema) currentSchema.itemsChildren = append(currentSchema.itemsChildren, newSchema)
err := d.parseSchema(itemElement, newSchema) err := d.parseSchema(itemElement, newSchema)
if err != nil { if err != nil {
return err return err
@ -401,7 +402,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
} else if isKind(m[KEY_ITEMS], reflect.Map, reflect.Bool) { } else if isKind(m[KEY_ITEMS], reflect.Map, reflect.Bool) {
newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS} newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS}
newSchema.ref = currentSchema.ref newSchema.ref = currentSchema.ref
currentSchema.AddItemsChild(newSchema) currentSchema.itemsChildren = append(currentSchema.itemsChildren, newSchema)
err := d.parseSchema(m[KEY_ITEMS], newSchema) err := d.parseSchema(m[KEY_ITEMS], newSchema)
if err != nil { if err != nil {
return err return err
@ -507,7 +508,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
currentSchema.exclusiveMinimum = currentSchema.minimum currentSchema.exclusiveMinimum = currentSchema.minimum
currentSchema.minimum = nil currentSchema.minimum = nil
} }
} else if isJsonNumber(m[KEY_EXCLUSIVE_MINIMUM]) { } else if isJSONNumber(m[KEY_EXCLUSIVE_MINIMUM]) {
currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM]) currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM])
} else { } else {
return errors.New(formatErrorDescription( return errors.New(formatErrorDescription(
@ -519,7 +520,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
)) ))
} }
default: default:
if isJsonNumber(m[KEY_EXCLUSIVE_MINIMUM]) { if isJSONNumber(m[KEY_EXCLUSIVE_MINIMUM]) {
currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM]) currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM])
} else { } else {
return errors.New(formatErrorDescription( return errors.New(formatErrorDescription(
@ -578,7 +579,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
currentSchema.exclusiveMaximum = currentSchema.maximum currentSchema.exclusiveMaximum = currentSchema.maximum
currentSchema.maximum = nil currentSchema.maximum = nil
} }
} else if isJsonNumber(m[KEY_EXCLUSIVE_MAXIMUM]) { } else if isJSONNumber(m[KEY_EXCLUSIVE_MAXIMUM]) {
currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM]) currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM])
} else { } else {
return errors.New(formatErrorDescription( return errors.New(formatErrorDescription(
@ -590,7 +591,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
)) ))
} }
default: default:
if isJsonNumber(m[KEY_EXCLUSIVE_MAXIMUM]) { if isJSONNumber(m[KEY_EXCLUSIVE_MAXIMUM]) {
currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM]) currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM])
} else { } else {
return errors.New(formatErrorDescription( return errors.New(formatErrorDescription(
@ -669,9 +670,13 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
if existsMapKey(m, KEY_FORMAT) { if existsMapKey(m, KEY_FORMAT) {
formatString, ok := m[KEY_FORMAT].(string) formatString, ok := m[KEY_FORMAT].(string)
if ok && FormatCheckers.Has(formatString) { if !ok {
currentSchema.format = formatString return errors.New(formatErrorDescription(
Locale.MustBeOfType(),
ErrorDetails{"key": KEY_FORMAT, "type": TYPE_STRING},
))
} }
currentSchema.format = formatString
} }
// validation : object // validation : object
@ -724,10 +729,13 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
requiredValues := m[KEY_REQUIRED].([]interface{}) requiredValues := m[KEY_REQUIRED].([]interface{})
for _, requiredValue := range requiredValues { for _, requiredValue := range requiredValues {
if isKind(requiredValue, reflect.String) { if isKind(requiredValue, reflect.String) {
err := currentSchema.AddRequired(requiredValue.(string)) if isStringInSlice(currentSchema.required, requiredValue.(string)) {
if err != nil { return errors.New(formatErrorDescription(
return err Locale.KeyItemsMustBeUnique(),
ErrorDetails{"key": KEY_REQUIRED},
))
} }
currentSchema.required = append(currentSchema.required, requiredValue.(string))
} else { } else {
return errors.New(formatErrorDescription( return errors.New(formatErrorDescription(
Locale.KeyItemsMustBeOfType(), Locale.KeyItemsMustBeOfType(),
@ -802,19 +810,27 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
// validation : all // validation : all
if existsMapKey(m, KEY_CONST) && *currentSchema.draft >= Draft6 { if existsMapKey(m, KEY_CONST) && *currentSchema.draft >= Draft6 {
err := currentSchema.AddConst(m[KEY_CONST]) is, err := marshalWithoutNumber(m[KEY_CONST])
if err != nil { if err != nil {
return err return err
} }
currentSchema._const = is
} }
if existsMapKey(m, KEY_ENUM) { if existsMapKey(m, KEY_ENUM) {
if isKind(m[KEY_ENUM], reflect.Slice) { if isKind(m[KEY_ENUM], reflect.Slice) {
for _, v := range m[KEY_ENUM].([]interface{}) { for _, v := range m[KEY_ENUM].([]interface{}) {
err := currentSchema.AddEnum(v) is, err := marshalWithoutNumber(v)
if err != nil { if err != nil {
return err return err
} }
if isStringInSlice(currentSchema.enum, *is) {
return errors.New(formatErrorDescription(
Locale.KeyItemsMustBeUnique(),
ErrorDetails{"key": KEY_ENUM},
))
}
currentSchema.enum = append(currentSchema.enum, *is)
} }
} else { } else {
return errors.New(formatErrorDescription( return errors.New(formatErrorDescription(
@ -830,7 +846,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
if isKind(m[KEY_ONE_OF], reflect.Slice) { if isKind(m[KEY_ONE_OF], reflect.Slice) {
for _, v := range m[KEY_ONE_OF].([]interface{}) { for _, v := range m[KEY_ONE_OF].([]interface{}) {
newSchema := &subSchema{property: KEY_ONE_OF, parent: currentSchema, ref: currentSchema.ref} newSchema := &subSchema{property: KEY_ONE_OF, parent: currentSchema, ref: currentSchema.ref}
currentSchema.AddOneOf(newSchema) currentSchema.oneOf = append(currentSchema.oneOf, newSchema)
err := d.parseSchema(v, newSchema) err := d.parseSchema(v, newSchema)
if err != nil { if err != nil {
return err return err
@ -848,7 +864,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
if isKind(m[KEY_ANY_OF], reflect.Slice) { if isKind(m[KEY_ANY_OF], reflect.Slice) {
for _, v := range m[KEY_ANY_OF].([]interface{}) { for _, v := range m[KEY_ANY_OF].([]interface{}) {
newSchema := &subSchema{property: KEY_ANY_OF, parent: currentSchema, ref: currentSchema.ref} newSchema := &subSchema{property: KEY_ANY_OF, parent: currentSchema, ref: currentSchema.ref}
currentSchema.AddAnyOf(newSchema) currentSchema.anyOf = append(currentSchema.anyOf, newSchema)
err := d.parseSchema(v, newSchema) err := d.parseSchema(v, newSchema)
if err != nil { if err != nil {
return err return err
@ -866,7 +882,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
if isKind(m[KEY_ALL_OF], reflect.Slice) { if isKind(m[KEY_ALL_OF], reflect.Slice) {
for _, v := range m[KEY_ALL_OF].([]interface{}) { for _, v := range m[KEY_ALL_OF].([]interface{}) {
newSchema := &subSchema{property: KEY_ALL_OF, parent: currentSchema, ref: currentSchema.ref} newSchema := &subSchema{property: KEY_ALL_OF, parent: currentSchema, ref: currentSchema.ref}
currentSchema.AddAllOf(newSchema) currentSchema.allOf = append(currentSchema.allOf, newSchema)
err := d.parseSchema(v, newSchema) err := d.parseSchema(v, newSchema)
if err != nil { if err != nil {
return err return err
@ -883,7 +899,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
if existsMapKey(m, KEY_NOT) { if existsMapKey(m, KEY_NOT) {
if isKind(m[KEY_NOT], reflect.Map, reflect.Bool) { if isKind(m[KEY_NOT], reflect.Map, reflect.Bool) {
newSchema := &subSchema{property: KEY_NOT, parent: currentSchema, ref: currentSchema.ref} newSchema := &subSchema{property: KEY_NOT, parent: currentSchema, ref: currentSchema.ref}
currentSchema.SetNot(newSchema) currentSchema.not = newSchema
err := d.parseSchema(m[KEY_NOT], newSchema) err := d.parseSchema(m[KEY_NOT], newSchema)
if err != nil { if err != nil {
return err return err
@ -900,7 +916,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
if existsMapKey(m, KEY_IF) { if existsMapKey(m, KEY_IF) {
if isKind(m[KEY_IF], reflect.Map, reflect.Bool) { if isKind(m[KEY_IF], reflect.Map, reflect.Bool) {
newSchema := &subSchema{property: KEY_IF, parent: currentSchema, ref: currentSchema.ref} newSchema := &subSchema{property: KEY_IF, parent: currentSchema, ref: currentSchema.ref}
currentSchema.SetIf(newSchema) currentSchema._if = newSchema
err := d.parseSchema(m[KEY_IF], newSchema) err := d.parseSchema(m[KEY_IF], newSchema)
if err != nil { if err != nil {
return err return err
@ -916,7 +932,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
if existsMapKey(m, KEY_THEN) { if existsMapKey(m, KEY_THEN) {
if isKind(m[KEY_THEN], reflect.Map, reflect.Bool) { if isKind(m[KEY_THEN], reflect.Map, reflect.Bool) {
newSchema := &subSchema{property: KEY_THEN, parent: currentSchema, ref: currentSchema.ref} newSchema := &subSchema{property: KEY_THEN, parent: currentSchema, ref: currentSchema.ref}
currentSchema.SetThen(newSchema) currentSchema._then = newSchema
err := d.parseSchema(m[KEY_THEN], newSchema) err := d.parseSchema(m[KEY_THEN], newSchema)
if err != nil { if err != nil {
return err return err
@ -932,7 +948,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
if existsMapKey(m, KEY_ELSE) { if existsMapKey(m, KEY_ELSE) {
if isKind(m[KEY_ELSE], reflect.Map, reflect.Bool) { if isKind(m[KEY_ELSE], reflect.Map, reflect.Bool) {
newSchema := &subSchema{property: KEY_ELSE, parent: currentSchema, ref: currentSchema.ref} newSchema := &subSchema{property: KEY_ELSE, parent: currentSchema, ref: currentSchema.ref}
currentSchema.SetElse(newSchema) currentSchema._else = newSchema
err := d.parseSchema(m[KEY_ELSE], newSchema) err := d.parseSchema(m[KEY_ELSE], newSchema)
if err != nil { if err != nil {
return err return err
@ -1004,7 +1020,7 @@ func (d *Schema) parseProperties(documentNode interface{}, currentSchema *subSch
for k := range m { for k := range m {
schemaProperty := k schemaProperty := k
newSchema := &subSchema{property: schemaProperty, parent: currentSchema, ref: currentSchema.ref} newSchema := &subSchema{property: schemaProperty, parent: currentSchema, ref: currentSchema.ref}
currentSchema.AddPropertiesChild(newSchema) currentSchema.propertiesChildren = append(currentSchema.propertiesChildren, newSchema)
err := d.parseSchema(m[k], newSchema) err := d.parseSchema(m[k], newSchema)
if err != nil { if err != nil {
return err return err
@ -1042,9 +1058,8 @@ func (d *Schema) parseDependencies(documentNode interface{}, currentSchema *subS
"type": STRING_SCHEMA_OR_ARRAY_OF_STRINGS, "type": STRING_SCHEMA_OR_ARRAY_OF_STRINGS,
}, },
)) ))
} else {
valuesToRegister = append(valuesToRegister, value.(string))
} }
valuesToRegister = append(valuesToRegister, value.(string))
currentSchema.dependencies[k] = valuesToRegister currentSchema.dependencies[k] = valuesToRegister
} }

View File

@ -21,6 +21,7 @@ import (
"github.com/xeipuuv/gojsonreference" "github.com/xeipuuv/gojsonreference"
) )
// SchemaLoader is used to load schemas
type SchemaLoader struct { type SchemaLoader struct {
pool *schemaPool pool *schemaPool
AutoDetect bool AutoDetect bool
@ -28,6 +29,7 @@ type SchemaLoader struct {
Draft Draft Draft Draft
} }
// NewSchemaLoader creates a new NewSchemaLoader
func NewSchemaLoader() *SchemaLoader { func NewSchemaLoader() *SchemaLoader {
ps := &SchemaLoader{ ps := &SchemaLoader{
@ -141,6 +143,7 @@ func (sl *SchemaLoader) AddSchema(url string, loader JSONLoader) error {
return sl.pool.parseReferences(doc, ref, true) return sl.pool.parseReferences(doc, ref, true)
} }
// Compile loads and compiles a schema
func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) { func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) {
ref, err := rootSchema.JsonReference() ref, err := rootSchema.JsonReference()

View File

@ -150,12 +150,12 @@ func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*sche
} }
// Create a deep copy, so we can remove the fragment part later on without altering the original // Create a deep copy, so we can remove the fragment part later on without altering the original
refToUrl, _ := gojsonreference.NewJsonReference(reference.String()) refToURL, _ := gojsonreference.NewJsonReference(reference.String())
// First check if the given fragment is a location independent identifier // First check if the given fragment is a location independent identifier
// http://json-schema.org/latest/json-schema-core.html#rfc.section.8.2.3 // http://json-schema.org/latest/json-schema-core.html#rfc.section.8.2.3
if spd, ok = p.schemaPoolDocuments[refToUrl.String()]; ok { if spd, ok = p.schemaPoolDocuments[refToURL.String()]; ok {
if internalLogEnabled { if internalLogEnabled {
internalLog(" From pool") internalLog(" From pool")
} }
@ -165,9 +165,9 @@ func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*sche
// If the given reference is not a location independent identifier, // If the given reference is not a location independent identifier,
// strip the fragment and look for a document with it's base URI // strip the fragment and look for a document with it's base URI
refToUrl.GetUrl().Fragment = "" refToURL.GetUrl().Fragment = ""
if cachedSpd, ok := p.schemaPoolDocuments[refToUrl.String()]; ok { if cachedSpd, ok := p.schemaPoolDocuments[refToURL.String()]; ok {
document, _, err := reference.GetPointer().Get(cachedSpd.Document) document, _, err := reference.GetPointer().Get(cachedSpd.Document)
if err != nil { if err != nil {
@ -200,7 +200,7 @@ func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*sche
} }
// add the whole document to the pool for potential re-use // add the whole document to the pool for potential re-use
p.parseReferences(document, refToUrl, true) p.parseReferences(document, refToURL, true)
_, draft, _ = parseSchemaURL(document) _, draft, _ = parseSchemaURL(document)

View File

@ -27,14 +27,12 @@
package gojsonschema package gojsonschema
import ( import (
"errors" "github.com/xeipuuv/gojsonreference"
"math/big" "math/big"
"regexp" "regexp"
"strings"
"github.com/xeipuuv/gojsonreference"
) )
// Constants
const ( const (
KEY_SCHEMA = "$schema" KEY_SCHEMA = "$schema"
KEY_ID = "id" KEY_ID = "id"
@ -88,6 +86,9 @@ type subSchema struct {
property string property string
// Quick pass/fail for boolean schemas
pass *bool
// Types associated with the subSchema // Types associated with the subSchema
types jsonSchemaType types jsonSchemaType
@ -146,111 +147,3 @@ type subSchema struct {
_then *subSchema _then *subSchema
_else *subSchema _else *subSchema
} }
func (s *subSchema) AddConst(i interface{}) error {
is, err := marshalWithoutNumber(i)
if err != nil {
return err
}
s._const = is
return nil
}
func (s *subSchema) AddEnum(i interface{}) error {
is, err := marshalWithoutNumber(i)
if err != nil {
return err
}
if isStringInSlice(s.enum, *is) {
return errors.New(formatErrorDescription(
Locale.KeyItemsMustBeUnique(),
ErrorDetails{"key": KEY_ENUM},
))
}
s.enum = append(s.enum, *is)
return nil
}
func (s *subSchema) ContainsEnum(i interface{}) (bool, error) {
is, err := marshalWithoutNumber(i)
if err != nil {
return false, err
}
return isStringInSlice(s.enum, *is), nil
}
func (s *subSchema) AddOneOf(subSchema *subSchema) {
s.oneOf = append(s.oneOf, subSchema)
}
func (s *subSchema) AddAllOf(subSchema *subSchema) {
s.allOf = append(s.allOf, subSchema)
}
func (s *subSchema) AddAnyOf(subSchema *subSchema) {
s.anyOf = append(s.anyOf, subSchema)
}
func (s *subSchema) SetNot(subSchema *subSchema) {
s.not = subSchema
}
func (s *subSchema) SetIf(subSchema *subSchema) {
s._if = subSchema
}
func (s *subSchema) SetThen(subSchema *subSchema) {
s._then = subSchema
}
func (s *subSchema) SetElse(subSchema *subSchema) {
s._else = subSchema
}
func (s *subSchema) AddRequired(value string) error {
if isStringInSlice(s.required, value) {
return errors.New(formatErrorDescription(
Locale.KeyItemsMustBeUnique(),
ErrorDetails{"key": KEY_REQUIRED},
))
}
s.required = append(s.required, value)
return nil
}
func (s *subSchema) AddItemsChild(child *subSchema) {
s.itemsChildren = append(s.itemsChildren, child)
}
func (s *subSchema) AddPropertiesChild(child *subSchema) {
s.propertiesChildren = append(s.propertiesChildren, child)
}
func (s *subSchema) PatternPropertiesString() string {
if s.patternProperties == nil || len(s.patternProperties) == 0 {
return STRING_UNDEFINED // should never happen
}
patternPropertiesKeySlice := []string{}
for pk := range s.patternProperties {
patternPropertiesKeySlice = append(patternPropertiesKeySlice, `"`+pk+`"`)
}
if len(patternPropertiesKeySlice) == 1 {
return patternPropertiesKeySlice[0]
}
return "[" + strings.Join(patternPropertiesKeySlice, ",") + "]"
}

View File

@ -25,6 +25,7 @@
package gojsonschema package gojsonschema
// Type constants
const ( const (
TYPE_ARRAY = `array` TYPE_ARRAY = `array`
TYPE_BOOLEAN = `boolean` TYPE_BOOLEAN = `boolean`
@ -35,7 +36,10 @@ const (
TYPE_STRING = `string` TYPE_STRING = `string`
) )
// JSON_TYPES hosts the list of type that are supported in JSON
var JSON_TYPES []string var JSON_TYPES []string
// SCHEMA_TYPES hosts the list of type that are supported in schemas
var SCHEMA_TYPES []string var SCHEMA_TYPES []string
func init() { func init() {

View File

@ -27,15 +27,13 @@ package gojsonschema
import ( import (
"encoding/json" "encoding/json"
"fmt"
"math"
"math/big" "math/big"
"reflect" "reflect"
) )
func isKind(what interface{}, kinds ...reflect.Kind) bool { func isKind(what interface{}, kinds ...reflect.Kind) bool {
target := what target := what
if isJsonNumber(what) { if isJSONNumber(what) {
// JSON Numbers are strings! // JSON Numbers are strings!
target = *mustBeNumber(what) target = *mustBeNumber(what)
} }
@ -72,7 +70,7 @@ func indexStringInSlice(s []string, what string) int {
return -1 return -1
} }
func marshalToJsonString(value interface{}) (*string, error) { func marshalToJSONString(value interface{}) (*string, error) {
mBytes, err := json.Marshal(value) mBytes, err := json.Marshal(value)
if err != nil { if err != nil {
@ -90,7 +88,7 @@ func marshalWithoutNumber(value interface{}) (*string, error) {
// One way to eliminate these differences is to decode and encode the JSON one more time without Decoder.UseNumber // One way to eliminate these differences is to decode and encode the JSON one more time without Decoder.UseNumber
// so that these differences in representation are removed // so that these differences in representation are removed
jsonString, err := marshalToJsonString(value) jsonString, err := marshalToJSONString(value)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -102,10 +100,10 @@ func marshalWithoutNumber(value interface{}) (*string, error) {
return nil, err return nil, err
} }
return marshalToJsonString(document) return marshalToJSONString(document)
} }
func isJsonNumber(what interface{}) bool { func isJSONNumber(what interface{}) bool {
switch what.(type) { switch what.(type) {
@ -116,7 +114,7 @@ func isJsonNumber(what interface{}) bool {
return false return false
} }
func checkJsonInteger(what interface{}) (isInt bool) { func checkJSONInteger(what interface{}) (isInt bool) {
jsonNumber := what.(json.Number) jsonNumber := what.(json.Number)
@ -128,26 +126,17 @@ func checkJsonInteger(what interface{}) (isInt bool) {
// same as ECMA Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER // same as ECMA Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER
const ( const (
max_json_float = float64(1<<53 - 1) // 9007199254740991.0 2^53 - 1 maxJSONFloat = float64(1<<53 - 1) // 9007199254740991.0 2^53 - 1
min_json_float = -float64(1<<53 - 1) //-9007199254740991.0 -2^53 - 1 minJSONFloat = -float64(1<<53 - 1) //-9007199254740991.0 -2^53 - 1
) )
func isFloat64AnInteger(f float64) bool {
if math.IsNaN(f) || math.IsInf(f, 0) || f < min_json_float || f > max_json_float {
return false
}
return f == float64(int64(f)) || f == float64(uint64(f))
}
func mustBeInteger(what interface{}) *int { func mustBeInteger(what interface{}) *int {
if isJsonNumber(what) { if isJSONNumber(what) {
number := what.(json.Number) number := what.(json.Number)
isInt := checkJsonInteger(number) isInt := checkJSONInteger(number)
if isInt { if isInt {
@ -158,9 +147,6 @@ func mustBeInteger(what interface{}) *int {
int32Value := int(int64Value) int32Value := int(int64Value)
return &int32Value return &int32Value
} else {
return nil
} }
} }
@ -170,43 +156,18 @@ func mustBeInteger(what interface{}) *int {
func mustBeNumber(what interface{}) *big.Rat { func mustBeNumber(what interface{}) *big.Rat {
if isJsonNumber(what) { if isJSONNumber(what) {
number := what.(json.Number) number := what.(json.Number)
float64Value, success := new(big.Rat).SetString(string(number)) float64Value, success := new(big.Rat).SetString(string(number))
if success { if success {
return float64Value return float64Value
} else {
return nil
} }
} }
return nil return nil
} }
// formats a number so that it is displayed as the smallest string possible
func resultErrorFormatJsonNumber(n json.Number) string {
if int64Value, err := n.Int64(); err == nil {
return fmt.Sprintf("%d", int64Value)
}
float64Value, _ := n.Float64()
return fmt.Sprintf("%g", float64Value)
}
// formats a number so that it is displayed as the smallest string possible
func resultErrorFormatNumber(n float64) string {
if isFloat64AnInteger(n) {
return fmt.Sprintf("%d", int64(n))
}
return fmt.Sprintf("%g", n)
}
func convertDocumentNode(val interface{}) interface{} { func convertDocumentNode(val interface{}) interface{} {
if lval, ok := val.([]interface{}); ok { if lval, ok := val.([]interface{}); ok {

View File

@ -35,42 +35,29 @@ import (
"unicode/utf8" "unicode/utf8"
) )
// Validate loads and validates a JSON schema
func Validate(ls JSONLoader, ld JSONLoader) (*Result, error) { func Validate(ls JSONLoader, ld JSONLoader) (*Result, error) {
var err error
// load schema // load schema
schema, err := NewSchema(ls) schema, err := NewSchema(ls)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// begine validation
return schema.Validate(ld) return schema.Validate(ld)
} }
// Validate loads and validates a JSON document
func (v *Schema) Validate(l JSONLoader) (*Result, error) { func (v *Schema) Validate(l JSONLoader) (*Result, error) {
// load document
root, err := l.LoadJSON() root, err := l.LoadJSON()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return v.validateDocument(root), nil return v.validateDocument(root), nil
} }
func (v *Schema) validateDocument(root interface{}) *Result { func (v *Schema) validateDocument(root interface{}) *Result {
// begin validation
result := &Result{} result := &Result{}
context := NewJsonContext(STRING_CONTEXT_ROOT, nil) context := NewJsonContext(STRING_CONTEXT_ROOT, nil)
v.rootSchema.validateRecursive(v.rootSchema, root, result, context) v.rootSchema.validateRecursive(v.rootSchema, root, result, context)
return result return result
} }
@ -88,6 +75,19 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i
internalLog(" %v", currentNode) internalLog(" %v", currentNode)
} }
// Handle true/false schema as early as possible as all other fields will be nil
if currentSubSchema.pass != nil {
if !*currentSubSchema.pass {
result.addInternalError(
new(FalseError),
context,
currentNode,
ErrorDetails{},
)
}
return
}
// Handle referenced schemas, returns directly when a $ref is found // Handle referenced schemas, returns directly when a $ref is found
if currentSubSchema.refSchema != nil { if currentSubSchema.refSchema != nil {
v.validateRecursive(currentSubSchema.refSchema, currentNode, result, context) v.validateRecursive(currentSubSchema.refSchema, currentNode, result, context)
@ -114,11 +114,11 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i
} else { // Not a null value } else { // Not a null value
if isJsonNumber(currentNode) { if isJSONNumber(currentNode) {
value := currentNode.(json.Number) value := currentNode.(json.Number)
isInt := checkJsonInteger(value) isInt := checkJSONInteger(value)
validType := currentSubSchema.types.Contains(TYPE_NUMBER) || (isInt && currentSubSchema.types.Contains(TYPE_INTEGER)) validType := currentSubSchema.types.Contains(TYPE_NUMBER) || (isInt && currentSubSchema.types.Contains(TYPE_INTEGER))
@ -424,11 +424,11 @@ func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{
// enum: // enum:
if len(currentSubSchema.enum) > 0 { if len(currentSubSchema.enum) > 0 {
has, err := currentSubSchema.ContainsEnum(value) vString, err := marshalWithoutNumber(value)
if err != nil { if err != nil {
result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err}) result.addInternalError(new(InternalError), context, value, ErrorDetails{"error": err})
} }
if !has { if !isStringInSlice(currentSubSchema.enum, *vString) {
result.addInternalError( result.addInternalError(
new(EnumError), new(EnumError),
context, context,
@ -516,13 +516,13 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface
// uniqueItems: // uniqueItems:
if currentSubSchema.uniqueItems { if currentSubSchema.uniqueItems {
var stringifiedItems []string var stringifiedItems = make(map[string]int)
for j, v := range value { for j, v := range value {
vString, err := marshalWithoutNumber(v) vString, err := marshalWithoutNumber(v)
if err != nil { if err != nil {
result.addInternalError(new(InternalError), context, value, ErrorDetails{"err": err}) result.addInternalError(new(InternalError), context, value, ErrorDetails{"err": err})
} }
if i := indexStringInSlice(stringifiedItems, *vString); i > -1 { if i, ok := stringifiedItems[*vString]; ok {
result.addInternalError( result.addInternalError(
new(ItemsMustBeUniqueError), new(ItemsMustBeUniqueError),
context, context,
@ -530,7 +530,7 @@ func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface
ErrorDetails{"type": TYPE_ARRAY, "i": i, "j": j}, ErrorDetails{"type": TYPE_ARRAY, "i": i, "j": j},
) )
} }
stringifiedItems = append(stringifiedItems, *vString) stringifiedItems[*vString] = j
} }
} }
@ -614,101 +614,37 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string
} }
// additionalProperty & patternProperty: // additionalProperty & patternProperty:
if currentSubSchema.additionalProperties != nil { for pk := range value {
switch currentSubSchema.additionalProperties.(type) { // Check whether this property is described by "properties"
found := false
for _, spValue := range currentSubSchema.propertiesChildren {
if pk == spValue.property {
found = true
}
}
// Check whether this property is described by "patternProperties"
ppMatch := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context)
// If it is not described by neither "properties" nor "patternProperties" it must pass "additionalProperties"
if !found && !ppMatch {
switch ap := currentSubSchema.additionalProperties.(type) {
case bool: case bool:
// Handle the boolean case separately as it's cleaner to return a specific error than failing to pass the false schema
if !currentSubSchema.additionalProperties.(bool) { if !ap {
for pk := range value {
found := false
for _, spValue := range currentSubSchema.propertiesChildren {
if pk == spValue.property {
found = true
}
}
pp_has, pp_match := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context)
if found {
if pp_has && !pp_match {
result.addInternalError( result.addInternalError(
new(AdditionalPropertyNotAllowedError), new(AdditionalPropertyNotAllowedError),
context, context,
value[pk], value[pk],
ErrorDetails{"property": pk}, ErrorDetails{"property": pk},
) )
}
} else {
if !pp_has || !pp_match {
result.addInternalError(
new(AdditionalPropertyNotAllowedError),
context,
value[pk],
ErrorDetails{"property": pk},
)
}
} }
}
}
case *subSchema: case *subSchema:
validationResult := ap.subValidateWithContext(value[pk], NewJsonContext(pk, context))
additionalPropertiesSchema := currentSubSchema.additionalProperties.(*subSchema)
for pk := range value {
found := false
for _, spValue := range currentSubSchema.propertiesChildren {
if pk == spValue.property {
found = true
}
}
pp_has, pp_match := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context)
if found {
if pp_has && !pp_match {
validationResult := additionalPropertiesSchema.subValidateWithContext(value[pk], context)
result.mergeErrors(validationResult) result.mergeErrors(validationResult)
} }
} else {
if !pp_has || !pp_match {
validationResult := additionalPropertiesSchema.subValidateWithContext(value[pk], context)
result.mergeErrors(validationResult)
}
}
}
}
} else {
for pk := range value {
pp_has, pp_match := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context)
if pp_has && !pp_match {
result.addInternalError(
new(InvalidPropertyPatternError),
context,
value[pk],
ErrorDetails{
"property": pk,
"pattern": currentSubSchema.PatternPropertiesString(),
},
)
}
} }
} }
@ -730,40 +666,36 @@ func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string
result.incrementScore() result.incrementScore()
} }
func (v *subSchema) validatePatternProperty(currentSubSchema *subSchema, key string, value interface{}, result *Result, context *JsonContext) (has bool, matched bool) { func (v *subSchema) validatePatternProperty(currentSubSchema *subSchema, key string, value interface{}, result *Result, context *JsonContext) bool {
if internalLogEnabled { if internalLogEnabled {
internalLog("validatePatternProperty %s", context.String()) internalLog("validatePatternProperty %s", context.String())
internalLog(" %s %v", key, value) internalLog(" %s %v", key, value)
} }
has = false validated := false
validatedkey := false
for pk, pv := range currentSubSchema.patternProperties { for pk, pv := range currentSubSchema.patternProperties {
if matches, _ := regexp.MatchString(pk, key); matches { if matches, _ := regexp.MatchString(pk, key); matches {
has = true validated = true
subContext := NewJsonContext(key, context) subContext := NewJsonContext(key, context)
validationResult := pv.subValidateWithContext(value, subContext) validationResult := pv.subValidateWithContext(value, subContext)
result.mergeErrors(validationResult) result.mergeErrors(validationResult)
validatedkey = true
} }
} }
if !validatedkey { if !validated {
return has, false return false
} }
result.incrementScore() result.incrementScore()
return true
return has, true
} }
func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) { func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) {
// Ignore JSON numbers // Ignore JSON numbers
if isJsonNumber(value) { if isJSONNumber(value) {
return return
} }
@ -832,7 +764,7 @@ func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{
func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) { func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{}, result *Result, context *JsonContext) {
// Ignore non numbers // Ignore non numbers
if !isJsonNumber(value) { if !isJSONNumber(value) {
return return
} }
@ -850,8 +782,10 @@ func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{
result.addInternalError( result.addInternalError(
new(MultipleOfError), new(MultipleOfError),
context, context,
resultErrorFormatJsonNumber(number), number,
ErrorDetails{"multiple": new(big.Float).SetRat(currentSubSchema.multipleOf)}, ErrorDetails{
"multiple": new(big.Float).SetRat(currentSubSchema.multipleOf),
},
) )
} }
} }
@ -862,9 +796,9 @@ func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{
result.addInternalError( result.addInternalError(
new(NumberLTEError), new(NumberLTEError),
context, context,
resultErrorFormatJsonNumber(number), number,
ErrorDetails{ ErrorDetails{
"max": currentSubSchema.maximum, "max": new(big.Float).SetRat(currentSubSchema.maximum),
}, },
) )
} }
@ -874,9 +808,9 @@ func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{
result.addInternalError( result.addInternalError(
new(NumberLTError), new(NumberLTError),
context, context,
resultErrorFormatJsonNumber(number), number,
ErrorDetails{ ErrorDetails{
"max": currentSubSchema.exclusiveMaximum, "max": new(big.Float).SetRat(currentSubSchema.exclusiveMaximum),
}, },
) )
} }
@ -888,22 +822,21 @@ func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{
result.addInternalError( result.addInternalError(
new(NumberGTEError), new(NumberGTEError),
context, context,
resultErrorFormatJsonNumber(number), number,
ErrorDetails{ ErrorDetails{
"min": currentSubSchema.minimum, "min": new(big.Float).SetRat(currentSubSchema.minimum),
}, },
) )
} }
} }
if currentSubSchema.exclusiveMinimum != nil { if currentSubSchema.exclusiveMinimum != nil {
if float64Value.Cmp(currentSubSchema.exclusiveMinimum) <= 0 { if float64Value.Cmp(currentSubSchema.exclusiveMinimum) <= 0 {
// if float64Value <= *currentSubSchema.minimum {
result.addInternalError( result.addInternalError(
new(NumberGTError), new(NumberGTError),
context, context,
resultErrorFormatJsonNumber(number), number,
ErrorDetails{ ErrorDetails{
"min": currentSubSchema.exclusiveMinimum, "min": new(big.Float).SetRat(currentSubSchema.exclusiveMinimum),
}, },
) )
} }