Add an intermediate SchemaLoader object that allows for the manual adding of additional schemas.
If multiple schemas try to identify as the same $id an error is returned
diff --git a/jsonLoader.go b/jsonLoader.go
index 5557025..cbe4664 100644
--- a/jsonLoader.go
+++ b/jsonLoader.go
@@ -107,7 +107,7 @@
}
// NewReferenceLoader returns a JSON reference loader using the given source and the local OS file system.
-func NewReferenceLoader(source string) *jsonReferenceLoader {
+func NewReferenceLoader(source string) JSONLoader {
return &jsonReferenceLoader{
fs: osFS,
source: source,
@@ -115,7 +115,7 @@
}
// NewReferenceLoaderFileSystem returns a JSON reference loader using the given source and file system.
-func NewReferenceLoaderFileSystem(source string, fs http.FileSystem) *jsonReferenceLoader {
+func NewReferenceLoaderFileSystem(source string, fs http.FileSystem) JSONLoader {
return &jsonReferenceLoader{
fs: fs,
source: source,
@@ -219,7 +219,7 @@
return &DefaultJSONLoaderFactory{}
}
-func NewStringLoader(source string) *jsonStringLoader {
+func NewStringLoader(source string) JSONLoader {
return &jsonStringLoader{source: source}
}
@@ -247,7 +247,7 @@
return &DefaultJSONLoaderFactory{}
}
-func NewBytesLoader(source []byte) *jsonBytesLoader {
+func NewBytesLoader(source []byte) JSONLoader {
return &jsonBytesLoader{source: source}
}
@@ -274,7 +274,7 @@
return &DefaultJSONLoaderFactory{}
}
-func NewGoLoader(source interface{}) *jsonGoLoader {
+func NewGoLoader(source interface{}) JSONLoader {
return &jsonGoLoader{source: source}
}
@@ -295,12 +295,12 @@
buf *bytes.Buffer
}
-func NewReaderLoader(source io.Reader) (*jsonIOLoader, io.Reader) {
+func NewReaderLoader(source io.Reader) (JSONLoader, io.Reader) {
buf := &bytes.Buffer{}
return &jsonIOLoader{buf: buf}, io.TeeReader(source, buf)
}
-func NewWriterLoader(source io.Writer) (*jsonIOLoader, io.Writer) {
+func NewWriterLoader(source io.Writer) (JSONLoader, io.Writer) {
buf := &bytes.Buffer{}
return &jsonIOLoader{buf: buf}, io.MultiWriter(source, buf)
}
diff --git a/schema.go b/schema.go
index 0746a59..4ae3c62 100644
--- a/schema.go
+++ b/schema.go
@@ -46,41 +46,7 @@
)
func NewSchema(l JSONLoader) (*Schema, error) {
- ref, err := l.JsonReference()
- if err != nil {
- return nil, err
- }
-
- d := Schema{}
- d.pool = newSchemaPool(l.LoaderFactory())
- 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)
- if err != nil {
- return nil, err
- }
- doc = spd.Document
- } else {
- // Load JSON directly
- doc, err = l.LoadJSON()
- if err != nil {
- return nil, err
- }
- }
-
- d.pool.ParseReferences(doc, ref)
-
- err = d.parse(doc)
- if err != nil {
- return nil, err
- }
-
- return &d, nil
+ return NewSchemaLoader().Compile(l)
}
type Schema struct {
diff --git a/schemaLoader.go b/schemaLoader.go
new file mode 100644
index 0000000..09d3a37
--- /dev/null
+++ b/schemaLoader.go
@@ -0,0 +1,102 @@
+package gojsonschema
+
+import (
+ "github.com/xeipuuv/gojsonreference"
+)
+
+type SchemaLoader struct {
+ pool *schemaPool
+}
+
+func NewSchemaLoader() *SchemaLoader {
+
+ ps := &SchemaLoader{
+ pool: &schemaPool{
+ schemaPoolDocuments: make(map[string]*schemaPoolDocument),
+ },
+ }
+
+ return ps
+}
+
+// AddSchemas adds an arbritrary amount of schemas to the schema cache. As this function does not require
+// an explicit URL, every schema should contain an $id, so that it can be referenced by the main schema
+func (sl *SchemaLoader) AddSchemas(loaders ...JSONLoader) error {
+ emptyRef, _ := gojsonreference.NewJsonReference("")
+
+ for _, loader := range loaders {
+ doc, err := loader.LoadJSON()
+ if err != nil {
+ return err
+ }
+ // Directly use the Recursive function, so that it get only added to the schema pool by $id
+ // and not by the ref of the document as it's empty
+ if err = sl.pool.parseReferencesRecursive(doc, emptyRef); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+//AddSchema adds a schema under the provided URL to the schema cache
+func (sl *SchemaLoader) AddSchema(url string, loader JSONLoader) error {
+
+ ref, err := gojsonreference.NewJsonReference(url)
+
+ if err != nil {
+ return err
+ }
+
+ doc, err := loader.LoadJSON()
+
+ if err != nil {
+ return err
+ }
+
+ return sl.pool.ParseReferences(doc, ref)
+}
+
+func (sl *SchemaLoader) Compile(rootSchema JSONLoader) (*Schema, error) {
+
+ ref, err := rootSchema.JsonReference()
+
+ if err != nil {
+ return nil, err
+ }
+
+ d := Schema{}
+ d.pool = sl.pool
+ d.pool.jsonLoaderFactory = rootSchema.LoaderFactory()
+ d.documentReference = ref
+ d.referencePool = newSchemaReferencePool()
+
+ var doc interface{}
+ if ref.String() != "" {
+ // Get document from schema pool
+ spd, err := d.pool.GetDocument(d.documentReference)
+ if err != nil {
+ return nil, err
+ }
+ doc = spd.Document
+ } else {
+ // Load JSON directly
+ doc, err = rootSchema.LoadJSON()
+ if err != nil {
+ return nil, err
+ }
+ // References need only be parsed if loading JSON directly
+ // as pool.GetDocument already does this for us if loading by reference
+ err = d.pool.ParseReferences(doc, ref)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ err = d.parse(doc)
+ if err != nil {
+ return nil, err
+ }
+
+ return &d, nil
+}
diff --git a/schemaLoader_test.go b/schemaLoader_test.go
new file mode 100644
index 0000000..3ba3a8b
--- /dev/null
+++ b/schemaLoader_test.go
@@ -0,0 +1,60 @@
+package gojsonschema
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSchemaLoaderWithReferenceToAddedSchema(t *testing.T) {
+ ps := NewSchemaLoader()
+ err := ps.AddSchemas(NewStringLoader(`{
+ "$id" : "http://localhost:1234/test1.json",
+ "type" : "integer"
+ }`))
+
+ assert.Nil(t, err)
+ schema, err := ps.Compile(NewReferenceLoader("http://localhost:1234/test1.json"))
+ assert.Nil(t, err)
+ result, err := schema.Validate(NewStringLoader(`"hello"`))
+ assert.Nil(t, err)
+ if len(result.Errors()) != 1 || result.Errors()[0].Type() != "invalid_type" {
+ t.Errorf("Expected invalid type erorr, instead got %v", result.Errors())
+ }
+}
+
+func TestCrossReference(t *testing.T) {
+ schema1 := NewStringLoader(`{
+ "$ref" : "http://localhost:1234/test3.json",
+ "definitions" : {
+ "foo" : {
+ "type" : "integer"
+ }
+ }
+ }`)
+ schema2 := NewStringLoader(`{
+ "$ref" : "http://localhost:1234/test2.json#/definitions/foo"
+ }`)
+
+ ps := NewSchemaLoader()
+ err := ps.AddSchema("http://localhost:1234/test2.json", schema1)
+ assert.Nil(t, err)
+ err = ps.AddSchema("http://localhost:1234/test3.json", schema2)
+ assert.Nil(t, err)
+ schema, err := ps.Compile(NewStringLoader(`{"$ref" : "http://localhost:1234/test2.json"}`))
+ assert.Nil(t, err)
+ result, err := schema.Validate(NewStringLoader(`"hello"`))
+ assert.Nil(t, err)
+ if len(result.Errors()) != 1 || result.Errors()[0].Type() != "invalid_type" {
+ t.Errorf("Expected invalid type erorr, instead got %v", result.Errors())
+ }
+}
+
+// Multiple schemas identifying under the same $id should throw an error
+func TestDoubleIDRefernce(t *testing.T) {
+ ps := NewSchemaLoader()
+ err := ps.AddSchema("http://localhost:1234/test4.json", NewStringLoader("{}"))
+ assert.Nil(t, err)
+ err = ps.AddSchemas(NewStringLoader(`{ "$id" : "http://localhost:1234/test4.json"}`))
+ assert.NotNil(t, err)
+}
diff --git a/schemaPool.go b/schemaPool.go
index 65c91e4..c3de8e5 100644
--- a/schemaPool.go
+++ b/schemaPool.go
@@ -28,6 +28,7 @@
import (
"errors"
+ "fmt"
"reflect"
"github.com/xeipuuv/gojsonreference"
@@ -51,13 +52,17 @@
return p
}
-func (p *schemaPool) ParseReferences(document interface{}, ref gojsonreference.JsonReference) {
+func (p *schemaPool) ParseReferences(document interface{}, ref gojsonreference.JsonReference) error {
// Only the root document should be added to the schema pool
+ if _, ok := p.schemaPoolDocuments[ref.String()]; ok {
+ return fmt.Errorf("Reference already exists: \"%s\"", ref.String())
+ }
+ err := p.parseReferencesRecursive(document, ref)
p.schemaPoolDocuments[ref.String()] = &schemaPoolDocument{Document: document}
- p.parseReferencesRecursive(document, ref)
+ return err
}
-func (p *schemaPool) parseReferencesRecursive(document interface{}, ref gojsonreference.JsonReference) {
+func (p *schemaPool) parseReferencesRecursive(document interface{}, ref gojsonreference.JsonReference) error {
// parseReferencesRecursive parses a JSON document and resolves all $id and $ref references.
// For $ref references it takes into account the $id scope it is in and replaces
// the reference by the absolute resolved reference
@@ -81,6 +86,9 @@
if err == nil {
localRef, err = ref.Inherits(jsonReference)
if err == nil {
+ if _, ok := p.schemaPoolDocuments[localRef.String()]; ok {
+ return fmt.Errorf("Reference already exists: \"%s\"", localRef.String())
+ }
p.schemaPoolDocuments[localRef.String()] = &schemaPoolDocument{Document: document}
}
}
@@ -114,6 +122,7 @@
}
}
}
+ return nil
}
func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*schemaPoolDocument, error) {