Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discriminator proof-of-concept #297

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions schema.go
Expand Up @@ -962,6 +962,34 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
}
}

if existsMapKey(m, KEY_DISCRIMINATOR) {
discriminator := m[KEY_DISCRIMINATOR].(map[string]interface{})
mapping := discriminator[KEY_MAPPING].(map[string]interface{})

currentSchema.discriminatorProperty = discriminator[KEY_PROPERTY_NAME].(string)
currentSchema.discriminatorMapping = map[string]*subSchema{}

for key, value := range mapping {
localRef, err := gojsonreference.NewJsonReference(value.(string))
if err != nil {
return err
}
fullRef, err := currentSchema.id.Inherits(localRef)
if err != nil {
return err
}
tempObj := map[string]interface{}{
"$ref": fullRef.String(),
}
newSchema := &subSchema{property: KEY_ONE_OF, parent: currentSchema, ref: currentSchema.ref}
err = d.parseSchema(tempObj, newSchema)
if err != nil {
return err
}
currentSchema.discriminatorMapping[key] = newSchema
}
}

return nil
}

Expand Down
10 changes: 9 additions & 1 deletion subSchema.go
Expand Up @@ -27,9 +27,10 @@
package gojsonschema

import (
"github.com/xeipuuv/gojsonreference"
"math/big"
"regexp"

"github.com/xeipuuv/gojsonreference"
)

// Constants
Expand All @@ -46,6 +47,7 @@ const (
KEY_PROPERTIES = "properties"
KEY_PATTERN_PROPERTIES = "patternProperties"
KEY_ADDITIONAL_PROPERTIES = "additionalProperties"
KEY_PROPERTY_NAME = "propertyName"
KEY_PROPERTY_NAMES = "propertyNames"
KEY_DEFINITIONS = "definitions"
KEY_MULTIPLE_OF = "multipleOf"
Expand Down Expand Up @@ -74,6 +76,8 @@ const (
KEY_IF = "if"
KEY_THEN = "then"
KEY_ELSE = "else"
KEY_DISCRIMINATOR = "discriminator"
KEY_MAPPING = "mapping"
)

type subSchema struct {
Expand Down Expand Up @@ -146,4 +150,8 @@ type subSchema struct {
_if *subSchema // if/else are golang keywords
_then *subSchema
_else *subSchema

// validation : discriminator
discriminatorProperty string
discriminatorMapping map[string]*subSchema
}
86 changes: 86 additions & 0 deletions testdata/draft6/discriminator.json
@@ -0,0 +1,86 @@
[
{
"description": "discriminator",
"schema": {
"id": "https://cody.ebberson.com/",
"discriminator": {
"propertyName": "resourceType",
"mapping": {
"bar": "#/definitions/bar",
"foo": "#/definitions/foo"
}
},
"oneOf": [
{
"$ref": "#/definitions/bar"
},
{
"$ref": "#/definitions/foo"
}
],
"definitions": {
"bar": {
"properties": {
"resourceType": {
"const": "bar"
},
"valueInteger": {
"type": "integer"
}
},
"required": [
"resourceType",
"valueInteger"
]
},
"foo": {
"properties": {
"resourceType": {
"const": "foo"
},
"valueString": {
"type": "string"
}
},
"required": [
"resourceType",
"valueString"
]
}
}
},
"tests": [
{
"description": "first discriminator valid",
"data": {
"resourceType": "bar",
"valueInteger": 2
},
"valid": true
},
{
"description": "second discriminator valid",
"data": {
"resourceType": "foo",
"valueString": "baz"
},
"valid": true
},
{
"description": "no discriminator match (unknown resourceType)",
"data": {
"resourceType": "baz",
"valueString": "baz"
},
"valid": false
},
{
"description": "no discriminator match (missing required property)",
"data": {
"resourceType": "foo"
},
"valid": false
}
]
}
]
26 changes: 19 additions & 7 deletions validation.go
Expand Up @@ -306,20 +306,32 @@ func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode inte
nbValidated := 0
var bestValidationResult *Result

for _, oneOfSchema := range currentSubSchema.oneOf {
validationResult := oneOfSchema.subValidateWithContext(currentNode, context)
if validationResult.Valid() {
nbValidated++
} else if nbValidated == 0 && (bestValidationResult == nil || validationResult.score > bestValidationResult.score) {
bestValidationResult = validationResult
if currentSubSchema.discriminatorProperty != "" {
key := currentNode.(map[string]interface{})[currentSubSchema.discriminatorProperty].(string)
if discriminatorSchema, ok := currentSubSchema.discriminatorMapping[key]; ok {
validationResult := discriminatorSchema.subValidateWithContext(currentNode, context)
if validationResult.Valid() {
nbValidated++
} else {
bestValidationResult = validationResult
}
}
} else {
for _, oneOfSchema := range currentSubSchema.oneOf {
validationResult := oneOfSchema.subValidateWithContext(currentNode, context)
if validationResult.Valid() {
nbValidated++
} else if nbValidated == 0 && (bestValidationResult == nil || validationResult.score > bestValidationResult.score) {
bestValidationResult = validationResult
}
}
}

if nbValidated != 1 {

result.addInternalError(new(NumberOneOfError), context, currentNode, ErrorDetails{})

if nbValidated == 0 {
if nbValidated == 0 && bestValidationResult != nil {
// add error messages of closest matching subSchema as
// that's probably the one the user was trying to match
result.mergeErrors(bestValidationResult)
Expand Down