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

Implement id parsing in conjuction with $ref fixes #171

Merged
merged 1 commit into from
Dec 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 72 additions & 36 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
package gojsonschema

import (
// "encoding/json"
"errors"
"reflect"
"regexp"
Expand Down Expand Up @@ -56,10 +55,11 @@ func NewSchema(l JSONLoader) (*Schema, error) {
d.documentReference = ref
d.referencePool = newSchemaReferencePool()

var spd *schemaPoolDocument
var doc interface{}
if ref.String() != "" {
// Get document from schema pool
spd, err := d.pool.GetDocument(d.documentReference)
spd, err = d.pool.GetDocument(d.documentReference)
if err != nil {
return nil, err
}
Expand All @@ -70,8 +70,8 @@ func NewSchema(l JSONLoader) (*Schema, error) {
if err != nil {
return nil, err
}
d.pool.SetStandaloneDocument(doc)
}
d.pool.SetStandaloneDocument(doc)

err = d.parse(doc)
if err != nil {
Expand Down Expand Up @@ -113,12 +113,48 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
},
))
}
if currentSchema.parent == nil {
currentSchema.ref = &d.documentReference
currentSchema.id = &d.documentReference
}

if currentSchema.id == nil && currentSchema.parent != nil {
currentSchema.id = currentSchema.parent.id
}

m := documentNode.(map[string]interface{})

if currentSchema == d.rootSchema {
currentSchema.ref = &d.documentReference
// id
if existsMapKey(m, KEY_ID) && !isKind(m[KEY_ID], reflect.String) {
return errors.New(formatErrorDescription(
Locale.InvalidType(),
ErrorDetails{
"expected": TYPE_STRING,
"given": KEY_ID,
},
))
}
if k, ok := m[KEY_ID].(string); ok {
jsonReference, err := gojsonreference.NewJsonReference(k)
if err != nil {
return err
}
if currentSchema == d.rootSchema {
currentSchema.id = &jsonReference
} else {
ref, err := currentSchema.parent.id.Inherits(jsonReference)
if err != nil {
return err
}
currentSchema.id = ref
}
}

// Add schema to document cache. The same id is passed down to subsequent
// subschemas, but as only the first and top one is used it will always reference
// the correct schema. Doing it once here prevents having
// to do this same step at every corner case.
d.referencePool.Add(currentSchema.id.String(), currentSchema)

// $subSchema
if existsMapKey(m, KEY_SCHEMA) {
Expand Down Expand Up @@ -159,19 +195,17 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
if jsonReference.HasFullUrl {
currentSchema.ref = &jsonReference
} else {
inheritedReference, err := currentSchema.ref.Inherits(jsonReference)
inheritedReference, err := currentSchema.id.Inherits(jsonReference)
if err != nil {
return err
}

currentSchema.ref = inheritedReference
}

if sch, ok := d.referencePool.Get(currentSchema.ref.String() + k); ok {
if sch, ok := d.referencePool.Get(currentSchema.ref.String()); ok {
currentSchema.refSchema = sch

} else {
err := d.parseReference(documentNode, currentSchema, k)
err := d.parseReference(documentNode, currentSchema)

if err != nil {
return err
}
Expand All @@ -186,11 +220,23 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
currentSchema.definitions = make(map[string]*subSchema)
for dk, dv := range m[KEY_DEFINITIONS].(map[string]interface{}) {
if isKind(dv, reflect.Map) {
newSchema := &subSchema{property: KEY_DEFINITIONS, parent: currentSchema, ref: currentSchema.ref}

ref, err := gojsonreference.NewJsonReference("#/" + KEY_DEFINITIONS + "/" + dk)
if err != nil {
return err
}

newSchemaID, err := currentSchema.id.Inherits(ref)
if err != nil {
return err
}
newSchema := &subSchema{property: KEY_DEFINITIONS, parent: currentSchema, id: newSchemaID}
currentSchema.definitions[dk] = newSchema
err := d.parseSchema(dv, newSchema)

err = d.parseSchema(dv, newSchema)

if err != nil {
return errors.New(err.Error())
return err
}
} else {
return errors.New(formatErrorDescription(
Expand All @@ -214,20 +260,6 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)

}

// id
if existsMapKey(m, KEY_ID) && !isKind(m[KEY_ID], reflect.String) {
return errors.New(formatErrorDescription(
Locale.InvalidType(),
ErrorDetails{
"expected": TYPE_STRING,
"given": KEY_ID,
},
))
}
if k, ok := m[KEY_ID].(string); ok {
currentSchema.id = &k
}

// title
if existsMapKey(m, KEY_TITLE) && !isKind(m[KEY_TITLE], reflect.String) {
return errors.New(formatErrorDescription(
Expand Down Expand Up @@ -798,26 +830,32 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
return nil
}

func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSchema, reference string) error {
var refdDocumentNode interface{}
func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSchema) error {
var (
refdDocumentNode interface{}
dsp *schemaPoolDocument
err error
)
jsonPointer := currentSchema.ref.GetPointer()
standaloneDocument := d.pool.GetStandaloneDocument()

if standaloneDocument != nil {
newSchema := &subSchema{property: KEY_REF, parent: currentSchema, ref: currentSchema.ref}

var err error
if currentSchema.ref.HasFragmentOnly {
refdDocumentNode, _, err = jsonPointer.Get(standaloneDocument)
if err != nil {
return err
}

} else {
dsp, err := d.pool.GetDocument(*currentSchema.ref)
dsp, err = d.pool.GetDocument(*currentSchema.ref)
if err != nil {
return err
}
newSchema.id = currentSchema.ref

refdDocumentNode, _, err = jsonPointer.Get(dsp.Document)

if err != nil {
return err
}
Expand All @@ -833,10 +871,8 @@ func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSche

// returns the loaded referenced subSchema for the caller to update its current subSchema
newSchemaDocument := refdDocumentNode.(map[string]interface{})
newSchema := &subSchema{property: KEY_REF, parent: currentSchema, ref: currentSchema.ref}
d.referencePool.Add(currentSchema.ref.String()+reference, newSchema)

err := d.parseSchema(newSchemaDocument, newSchema)
err = d.parseSchema(newSchemaDocument, newSchema)
if err != nil {
return err
}
Expand Down
20 changes: 7 additions & 13 deletions schemaPool.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,33 +62,27 @@ func (p *schemaPool) GetStandaloneDocument() (document interface{}) {

func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*schemaPoolDocument, error) {

var (
spd *schemaPoolDocument
ok bool
err error
)

if internalLogEnabled {
internalLog("Get Document ( %s )", reference.String())
}

var err error

// It is not possible to load anything that is not canonical...
if !reference.IsCanonical() {
return nil, errors.New(formatErrorDescription(
Locale.ReferenceMustBeCanonical(),
ErrorDetails{"reference": reference},
))
}

refToUrl := reference
refToUrl.GetUrl().Fragment = ""

var spd *schemaPoolDocument

// Try to find the requested document in the pool
for k := range p.schemaPoolDocuments {
if k == refToUrl.String() {
spd = p.schemaPoolDocuments[k]
}
}

if spd != nil {
if spd, ok = p.schemaPoolDocuments[refToUrl.String()]; ok {
if internalLogEnabled {
internalLog(" From pool")
}
Expand Down
5 changes: 3 additions & 2 deletions schemaReferencePool.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func (p *schemaReferencePool) Add(ref string, sch *subSchema) {
if internalLogEnabled {
internalLog(fmt.Sprintf("Add Schema Reference %s to pool", ref))
}

p.documents[ref] = sch
if _, ok := p.documents[ref]; !ok {
p.documents[ref] = sch
}
}
9 changes: 5 additions & 4 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,10 @@ func TestJsonSchemaTestSuite(t *testing.T) {
{"phase": "format validation", "test": "uri format is invalid", "schema": "format/schema_6.json", "data": "format/data_13.json", "valid": "false", "errors": "format"},
{"phase": "format validation", "test": "number format is valid", "schema": "format/schema_7.json", "data": "format/data_29.json", "valid": "true"},
{"phase": "format validation", "test": "number format is valid", "schema": "format/schema_7.json", "data": "format/data_30.json", "valid": "false", "errors": "format"},
{"phase": "change resolution scope", "test": "changed scope ref valid", "schema": "refRemote/schema_3.json", "data": "refRemote/data_30.json", "valid": "true"},
{"phase": "change resolution scope", "test": "changed scope ref invalid", "schema": "refRemote/schema_3.json", "data": "refRemote/data_31.json", "valid": "false", "errors": "invalid_type"},
}

//TODO Pass failed tests : id(s) as scope for references is not implemented yet
//map[string]string{"phase": "change resolution scope", "test": "changed scope ref valid", "schema": "refRemote/schema_3.json", "data": "refRemote/data_30.json", "valid": "true"},
//map[string]string{"phase": "change resolution scope", "test": "changed scope ref invalid", "schema": "refRemote/schema_3.json", "data": "refRemote/data_31.json", "valid": "false"}}

// Setup a small http server on localhost:1234 for testing purposes

wd, err := os.Getwd()
Expand Down Expand Up @@ -416,6 +414,9 @@ func TestJsonSchemaTestSuite(t *testing.T) {
expectedValid, _ := strconv.ParseBool(testJson["valid"])
if givenValid != expectedValid {
t.Errorf("Test failed : %s :: %s, expects %t, given %t\n", testJson["phase"], testJson["test"], expectedValid, givenValid)
for _, e := range result.Errors() {
fmt.Println("Error: " + e.Type())
}
}

if !givenValid && testJson["errors"] != "" {
Expand Down
4 changes: 2 additions & 2 deletions subSchema.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (

const (
KEY_SCHEMA = "$subSchema"
KEY_ID = "$id"
KEY_ID = "id"
KEY_REF = "$ref"
KEY_TITLE = "title"
KEY_DESCRIPTION = "description"
Expand Down Expand Up @@ -73,7 +73,7 @@ const (
type subSchema struct {

// basic subSchema meta properties
id *string
id *gojsonreference.JsonReference
title *string
description *string

Expand Down