Skip to content

Commit 83a7f63

Browse files
committed
Implement id parsing in conjuction with $ref fixes
1 parent 212d8a0 commit 83a7f63

File tree

5 files changed

+89
-57
lines changed

5 files changed

+89
-57
lines changed

schema.go

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
package gojsonschema
2828

2929
import (
30-
// "encoding/json"
3130
"errors"
3231
"reflect"
3332
"regexp"
@@ -56,10 +55,11 @@ func NewSchema(l JSONLoader) (*Schema, error) {
5655
d.documentReference = ref
5756
d.referencePool = newSchemaReferencePool()
5857

58+
var spd *schemaPoolDocument
5959
var doc interface{}
6060
if ref.String() != "" {
6161
// Get document from schema pool
62-
spd, err := d.pool.GetDocument(d.documentReference)
62+
spd, err = d.pool.GetDocument(d.documentReference)
6363
if err != nil {
6464
return nil, err
6565
}
@@ -70,8 +70,8 @@ func NewSchema(l JSONLoader) (*Schema, error) {
7070
if err != nil {
7171
return nil, err
7272
}
73-
d.pool.SetStandaloneDocument(doc)
7473
}
74+
d.pool.SetStandaloneDocument(doc)
7575

7676
err = d.parse(doc)
7777
if err != nil {
@@ -113,12 +113,48 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
113113
},
114114
))
115115
}
116+
if currentSchema.parent == nil {
117+
currentSchema.ref = &d.documentReference
118+
currentSchema.id = &d.documentReference
119+
}
120+
121+
if currentSchema.id == nil && currentSchema.parent != nil {
122+
currentSchema.id = currentSchema.parent.id
123+
}
116124

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

119-
if currentSchema == d.rootSchema {
120-
currentSchema.ref = &d.documentReference
127+
// id
128+
if existsMapKey(m, KEY_ID) && !isKind(m[KEY_ID], reflect.String) {
129+
return errors.New(formatErrorDescription(
130+
Locale.InvalidType(),
131+
ErrorDetails{
132+
"expected": TYPE_STRING,
133+
"given": KEY_ID,
134+
},
135+
))
121136
}
137+
if k, ok := m[KEY_ID].(string); ok {
138+
jsonReference, err := gojsonreference.NewJsonReference(k)
139+
if err != nil {
140+
return err
141+
}
142+
if currentSchema == d.rootSchema {
143+
currentSchema.id = &jsonReference
144+
} else {
145+
ref, err := currentSchema.parent.id.Inherits(jsonReference)
146+
if err != nil {
147+
return err
148+
}
149+
currentSchema.id = ref
150+
}
151+
}
152+
153+
// Add schema to document cache. The same id is passed down to subsequent
154+
// subschemas, but as only the first and top one is used it will always reference
155+
// the correct schema. Doing it once here prevents having
156+
// to do this same step at every corner case.
157+
d.referencePool.Add(currentSchema.id.String(), currentSchema)
122158

123159
// $subSchema
124160
if existsMapKey(m, KEY_SCHEMA) {
@@ -159,19 +195,17 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
159195
if jsonReference.HasFullUrl {
160196
currentSchema.ref = &jsonReference
161197
} else {
162-
inheritedReference, err := currentSchema.ref.Inherits(jsonReference)
198+
inheritedReference, err := currentSchema.id.Inherits(jsonReference)
163199
if err != nil {
164200
return err
165201
}
166-
167202
currentSchema.ref = inheritedReference
168203
}
169-
170-
if sch, ok := d.referencePool.Get(currentSchema.ref.String() + k); ok {
204+
if sch, ok := d.referencePool.Get(currentSchema.ref.String()); ok {
171205
currentSchema.refSchema = sch
172-
173206
} else {
174-
err := d.parseReference(documentNode, currentSchema, k)
207+
err := d.parseReference(documentNode, currentSchema)
208+
175209
if err != nil {
176210
return err
177211
}
@@ -186,11 +220,23 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
186220
currentSchema.definitions = make(map[string]*subSchema)
187221
for dk, dv := range m[KEY_DEFINITIONS].(map[string]interface{}) {
188222
if isKind(dv, reflect.Map) {
189-
newSchema := &subSchema{property: KEY_DEFINITIONS, parent: currentSchema, ref: currentSchema.ref}
223+
224+
ref, err := gojsonreference.NewJsonReference("#/" + KEY_DEFINITIONS + "/" + dk)
225+
if err != nil {
226+
return err
227+
}
228+
229+
newSchemaID, err := currentSchema.id.Inherits(ref)
230+
if err != nil {
231+
return err
232+
}
233+
newSchema := &subSchema{property: KEY_DEFINITIONS, parent: currentSchema, id: newSchemaID}
190234
currentSchema.definitions[dk] = newSchema
191-
err := d.parseSchema(dv, newSchema)
235+
236+
err = d.parseSchema(dv, newSchema)
237+
192238
if err != nil {
193-
return errors.New(err.Error())
239+
return err
194240
}
195241
} else {
196242
return errors.New(formatErrorDescription(
@@ -214,20 +260,6 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
214260

215261
}
216262

217-
// id
218-
if existsMapKey(m, KEY_ID) && !isKind(m[KEY_ID], reflect.String) {
219-
return errors.New(formatErrorDescription(
220-
Locale.InvalidType(),
221-
ErrorDetails{
222-
"expected": TYPE_STRING,
223-
"given": KEY_ID,
224-
},
225-
))
226-
}
227-
if k, ok := m[KEY_ID].(string); ok {
228-
currentSchema.id = &k
229-
}
230-
231263
// title
232264
if existsMapKey(m, KEY_TITLE) && !isKind(m[KEY_TITLE], reflect.String) {
233265
return errors.New(formatErrorDescription(
@@ -798,26 +830,32 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema)
798830
return nil
799831
}
800832

801-
func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSchema, reference string) error {
802-
var refdDocumentNode interface{}
833+
func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSchema) error {
834+
var (
835+
refdDocumentNode interface{}
836+
dsp *schemaPoolDocument
837+
err error
838+
)
803839
jsonPointer := currentSchema.ref.GetPointer()
804840
standaloneDocument := d.pool.GetStandaloneDocument()
805841

806-
if standaloneDocument != nil {
842+
newSchema := &subSchema{property: KEY_REF, parent: currentSchema, ref: currentSchema.ref}
807843

808-
var err error
844+
if currentSchema.ref.HasFragmentOnly {
809845
refdDocumentNode, _, err = jsonPointer.Get(standaloneDocument)
810846
if err != nil {
811847
return err
812848
}
813849

814850
} else {
815-
dsp, err := d.pool.GetDocument(*currentSchema.ref)
851+
dsp, err = d.pool.GetDocument(*currentSchema.ref)
816852
if err != nil {
817853
return err
818854
}
855+
newSchema.id = currentSchema.ref
819856

820857
refdDocumentNode, _, err = jsonPointer.Get(dsp.Document)
858+
821859
if err != nil {
822860
return err
823861
}
@@ -833,10 +871,8 @@ func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSche
833871

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

839-
err := d.parseSchema(newSchemaDocument, newSchema)
875+
err = d.parseSchema(newSchemaDocument, newSchema)
840876
if err != nil {
841877
return err
842878
}

schemaPool.go

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,33 +62,27 @@ func (p *schemaPool) GetStandaloneDocument() (document interface{}) {
6262

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

65+
var (
66+
spd *schemaPoolDocument
67+
ok bool
68+
err error
69+
)
70+
6571
if internalLogEnabled {
6672
internalLog("Get Document ( %s )", reference.String())
6773
}
6874

69-
var err error
70-
7175
// It is not possible to load anything that is not canonical...
7276
if !reference.IsCanonical() {
7377
return nil, errors.New(formatErrorDescription(
7478
Locale.ReferenceMustBeCanonical(),
7579
ErrorDetails{"reference": reference},
7680
))
7781
}
78-
7982
refToUrl := reference
8083
refToUrl.GetUrl().Fragment = ""
8184

82-
var spd *schemaPoolDocument
83-
84-
// Try to find the requested document in the pool
85-
for k := range p.schemaPoolDocuments {
86-
if k == refToUrl.String() {
87-
spd = p.schemaPoolDocuments[k]
88-
}
89-
}
90-
91-
if spd != nil {
85+
if spd, ok = p.schemaPoolDocuments[refToUrl.String()]; ok {
9286
if internalLogEnabled {
9387
internalLog(" From pool")
9488
}

schemaReferencePool.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ func (p *schemaReferencePool) Add(ref string, sch *subSchema) {
6262
if internalLogEnabled {
6363
internalLog(fmt.Sprintf("Add Schema Reference %s to pool", ref))
6464
}
65-
66-
p.documents[ref] = sch
65+
if _, ok := p.documents[ref]; !ok {
66+
p.documents[ref] = sch
67+
}
6768
}

schema_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,12 +360,10 @@ func TestJsonSchemaTestSuite(t *testing.T) {
360360
{"phase": "format validation", "test": "uri format is invalid", "schema": "format/schema_6.json", "data": "format/data_13.json", "valid": "false", "errors": "format"},
361361
{"phase": "format validation", "test": "number format is valid", "schema": "format/schema_7.json", "data": "format/data_29.json", "valid": "true"},
362362
{"phase": "format validation", "test": "number format is valid", "schema": "format/schema_7.json", "data": "format/data_30.json", "valid": "false", "errors": "format"},
363+
{"phase": "change resolution scope", "test": "changed scope ref valid", "schema": "refRemote/schema_3.json", "data": "refRemote/data_30.json", "valid": "true"},
364+
{"phase": "change resolution scope", "test": "changed scope ref invalid", "schema": "refRemote/schema_3.json", "data": "refRemote/data_31.json", "valid": "false", "errors": "invalid_type"},
363365
}
364366

365-
//TODO Pass failed tests : id(s) as scope for references is not implemented yet
366-
//map[string]string{"phase": "change resolution scope", "test": "changed scope ref valid", "schema": "refRemote/schema_3.json", "data": "refRemote/data_30.json", "valid": "true"},
367-
//map[string]string{"phase": "change resolution scope", "test": "changed scope ref invalid", "schema": "refRemote/schema_3.json", "data": "refRemote/data_31.json", "valid": "false"}}
368-
369367
// Setup a small http server on localhost:1234 for testing purposes
370368

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

421422
if !givenValid && testJson["errors"] != "" {

subSchema.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import (
3636

3737
const (
3838
KEY_SCHEMA = "$subSchema"
39-
KEY_ID = "$id"
39+
KEY_ID = "id"
4040
KEY_REF = "$ref"
4141
KEY_TITLE = "title"
4242
KEY_DESCRIPTION = "description"
@@ -73,7 +73,7 @@ const (
7373
type subSchema struct {
7474

7575
// basic subSchema meta properties
76-
id *string
76+
id *gojsonreference.JsonReference
7777
title *string
7878
description *string
7979

0 commit comments

Comments
 (0)