Skip to content

Commit

Permalink
Implement id parsing in conjuction with $ref fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
johandorland committed Dec 21, 2017
1 parent 212d8a0 commit 83a7f63
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 57 deletions.
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

0 comments on commit 83a7f63

Please sign in to comment.