diff --git a/errors.go b/errors.go index 0629fa5..e4e9814 100644 --- a/errors.go +++ b/errors.go @@ -16,6 +16,12 @@ type errorTemplate struct { } type ( + + // FalseError. ErrorDetails: - + FalseError struct { + ResultErrorFields + } + // RequiredError indicates that a required field is missing // ErrorDetails: property string RequiredError struct { @@ -208,6 +214,9 @@ func newError(err ResultError, context *JsonContext, value interface{}, locale l var t string var d string switch err.(type) { + case *FalseError: + t = "false" + d = locale.False() case *RequiredError: t = "required" d = locale.Required() diff --git a/locales.go b/locales.go index 064d3f3..a416225 100644 --- a/locales.go +++ b/locales.go @@ -28,6 +28,10 @@ package gojsonschema type ( // locale is an interface for defining custom error strings 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 @@ -188,6 +192,11 @@ type ( 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 { return `{{.property}} is required` diff --git a/schema.go b/schema.go index 42ba677..9e93cd7 100644 --- a/schema.go +++ b/schema.go @@ -86,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":{}}" if *currentSchema.draft >= Draft6 && isKind(documentNode, reflect.Bool) { b := documentNode.(bool) - if b { - documentNode = map[string]interface{}{} - } else { - documentNode = map[string]interface{}{"not": true} - } + currentSchema.pass = &b + return nil } if !isKind(documentNode, reflect.Map) { @@ -813,7 +810,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) // validation : all if existsMapKey(m, KEY_CONST) && *currentSchema.draft >= Draft6 { - is,err := marshalWithoutNumber(m[KEY_CONST]) + is, err := marshalWithoutNumber(m[KEY_CONST]) if err != nil { return err } @@ -823,7 +820,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) if existsMapKey(m, KEY_ENUM) { if isKind(m[KEY_ENUM], reflect.Slice) { for _, v := range m[KEY_ENUM].([]interface{}) { - is,err := marshalWithoutNumber(v) + is, err := marshalWithoutNumber(v) if err != nil { return err } @@ -919,7 +916,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) if existsMapKey(m, KEY_IF) { if isKind(m[KEY_IF], reflect.Map, reflect.Bool) { newSchema := &subSchema{property: KEY_IF, parent: currentSchema, ref: currentSchema.ref} -currentSchema._if = newSchema + currentSchema._if = newSchema err := d.parseSchema(m[KEY_IF], newSchema) if err != nil { return err diff --git a/schema_test.go b/schema_test.go index 9a7bab4..102e887 100644 --- a/schema_test.go +++ b/schema_test.go @@ -272,6 +272,41 @@ func TestFragmentLoader(t *testing.T) { } } +func TestAdditionalPropertiesErrorMessage(t *testing.T) { + schema := `{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "Device": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } +}` + text := `{ + "Device":{ + "Color" : true + } + }` + loader := NewBytesLoader([]byte(schema)) + result, err := Validate(loader, NewBytesLoader([]byte(text))) + if err != nil { + t.Fatal(err) + } + + if len(result.Errors()) != 1 { + t.Fatal("Expected 1 error but got", len(result.Errors())) + } + + expected := "Device.Color: Invalid type. Expected: string, given: boolean" + actual := result.Errors()[0].String() + if actual != expected { + t.Fatalf("Expected '%s' but got '%s'", expected, actual) + } +} + // Inspired by http://json-schema.org/latest/json-schema-core.html#rfc.section.8.2.3 const locationIndependentSchema = `{ "definitions": { @@ -312,7 +347,7 @@ func TestLocationIndependentIdentifier(t *testing.T) { t.Errorf("Got error: %s", err.Error()) } - if len(result.Errors()) != 2 || result.Errors()[0].Type() != "number_not" || result.Errors()[1].Type() != "number_all_of" { + if len(result.Errors()) != 2 || result.Errors()[0].Type() != "false" || result.Errors()[1].Type() != "number_all_of" { t.Errorf("Got invalid validation result.") } } diff --git a/subSchema.go b/subSchema.go index cbcd3a6..ec77981 100644 --- a/subSchema.go +++ b/subSchema.go @@ -27,9 +27,9 @@ package gojsonschema import ( + "github.com/xeipuuv/gojsonreference" "math/big" "regexp" - "github.com/xeipuuv/gojsonreference" ) // Constants @@ -86,6 +86,9 @@ type subSchema struct { property string + // Quick pass/fail for boolean schemas + pass *bool + // Types associated with the subSchema types jsonSchemaType @@ -143,4 +146,4 @@ type subSchema struct { _if *subSchema // if/else are golang keywords _then *subSchema _else *subSchema -} \ No newline at end of file +} diff --git a/validation.go b/validation.go index 7b6fc30..74091bc 100644 --- a/validation.go +++ b/validation.go @@ -75,6 +75,19 @@ func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode i 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 if currentSubSchema.refSchema != nil { v.validateRecursive(currentSubSchema.refSchema, currentNode, result, context)