Skip to content

Commit

Permalink
custom vocab from metaschema
Browse files Browse the repository at this point in the history
  • Loading branch information
santhosh-tekuri committed May 1, 2024
1 parent 6d78eb5 commit 2b1ce1d
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 17 deletions.
73 changes: 73 additions & 0 deletions compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,76 @@ func TestCustomVocabValidation(t *testing.T) {
t.Fatal("exception compilation failure")
}
}

func TestCustomVocabMetaschema(t *testing.T) {
metaschema, err := jsonschema.UnmarshalJSON(strings.NewReader(`{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$vocabulary": {
"http://example.com/meta/unique-keys": true,
"https://json-schema.org/draft/2020-12/vocab/validation": true,
"https://json-schema.org/draft/2020-12/vocab/core": true
},
"$dynamicAnchor": "meta",
"allOf": [
{ "$ref": "http://example.com/meta/unique-keys" },
{ "$ref": "https://json-schema.org/draft/2020-12/meta/validation" },
{ "$ref": "https://json-schema.org/draft/2020-12/meta/core" }
]
}`))
if err != nil {
t.Fatal(err)
}
schema, err := jsonschema.UnmarshalJSON(strings.NewReader(`{
"$schema": "http://temp.com/metaschema",
"uniqueKeys": 1
}`))
if err != nil {
t.Fatal(err)
}

c := jsonschema.NewCompiler()
c.RegisterVocabulary(uniqueKeysVocab())
if err := c.AddResource("http://temp.com/metaschema", metaschema); err != nil {
t.Fatal(err)
}
if err := c.AddResource("invalid_schema.json", schema); err != nil {
t.Fatal(err)
}
_, err = c.Compile("invalid_schema.json")
if err == nil {
t.Fatal("exception compilation failure")
}
if _, ok := err.(*jsonschema.SchemaValidationError); !ok {
t.Log("want SchemaValidationError")
t.Fatalf(" got %v", err)
}

schema, err = jsonschema.UnmarshalJSON(strings.NewReader(`{
"$schema": "http://temp.com/metaschema",
"uniqueKeys": "id"
}`))
if err != nil {
t.Fatal(err)
}
inst, err := jsonschema.UnmarshalJSON(strings.NewReader(`[
{ "id": 1, "name": "alice" },
{ "id": 2, "name": "bob" },
{ "id": 1, "name": "scott" }
]`))
if err != nil {
t.Fatal(err)
}

if err := c.AddResource("valid_schema.json", schema); err != nil {
t.Fatal(err)
}
sch, err := c.Compile("valid_schema.json")
if err != nil {
t.Fatal(err)
}

err = sch.Validate(inst)
if err == nil {
t.Fatal("instance should be invalid")
}
}
16 changes: 12 additions & 4 deletions draft.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func (d *Draft) getID(obj map[string]any) string {
return id
}

func (d *Draft) getVocabs(url url, doc any) ([]string, error) {
func (d *Draft) getVocabs(url url, doc any, vocabularies map[string]*Vocabulary) ([]string, error) {
if d.version < 2019 {
return nil, nil
}
Expand All @@ -246,11 +246,19 @@ func (d *Draft) getVocabs(url url, doc any) ([]string, error) {
continue
}
name, ok := strings.CutPrefix(vocab, d.vocabPrefix)
if !ok {
if ok {
if _, ok := d.allVocabs[name]; ok {
if !slices.Contains(vocabs, name) {
vocabs = append(vocabs, name)
continue
}
}
}
if _, ok := vocabularies[vocab]; !ok {
return nil, &UnsupportedVocabularyError{url.String(), vocab}
}
if !slices.Contains(vocabs, name) {
vocabs = append(vocabs, name)
if !slices.Contains(vocabs, vocab) {
vocabs = append(vocabs, vocab)
}
}
return vocabs, nil
Expand Down
4 changes: 2 additions & 2 deletions draft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func TestDraft_collectIds(t *testing.T) {
resources: map[jsonPointer]*resource{},
subschemasProcessed: map[jsonPointer]struct{}{},
}
if err := r.collectResources(nil, doc, u, jsonPointer(""), dialect{Draft4, nil}); err != nil {
if err := r.collectResources(nil, nil, doc, u, jsonPointer(""), dialect{Draft4, nil}); err != nil {
t.Fatal(err)
}

Expand Down Expand Up @@ -122,7 +122,7 @@ func TestDraft_collectAnchors(t *testing.T) {
resources: map[jsonPointer]*resource{},
subschemasProcessed: map[jsonPointer]struct{}{},
}
if err := r.collectResources(nil, doc, u, jsonPointer(""), dialect{Draft2020, nil}); err != nil {
if err := r.collectResources(nil, nil, doc, u, jsonPointer(""), dialect{Draft2020, nil}); err != nil {
t.Fatal(err)
}

Expand Down
4 changes: 2 additions & 2 deletions loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (l *defaultLoader) getDraft(up urlPtr, doc any, defaultDraft *Draft, cycle
return l.getDraft(urlPtr{schUrl, ""}, doc, defaultDraft, cycle)
}

func (l *defaultLoader) getMetaVocabs(doc any, draft *Draft) ([]string, error) {
func (l *defaultLoader) getMetaVocabs(doc any, draft *Draft, vocabularies map[string]*Vocabulary) ([]string, error) {
obj, ok := doc.(map[string]any)
if !ok {
return nil, nil
Expand All @@ -186,7 +186,7 @@ func (l *defaultLoader) getMetaVocabs(doc any, draft *Draft) ([]string, error) {
if err != nil {
return nil, err
}
return draft.getVocabs(schUrl, doc)
return draft.getVocabs(schUrl, doc, vocabularies)
}

// --
Expand Down
14 changes: 7 additions & 7 deletions root.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,18 @@ func (r *root) resolve(uf urlFrag) (*urlPtr, error) {
return &up, err
}

func (r *root) collectResources(loader *defaultLoader, sch any, base url, schPtr jsonPointer, fallback dialect) error {
func (r *root) collectResources(loader *defaultLoader, vocabularies map[string]*Vocabulary, sch any, base url, schPtr jsonPointer, fallback dialect) error {
if _, ok := r.subschemasProcessed[schPtr]; ok {
return nil
}
if err := r._collectResources(loader, sch, base, schPtr, fallback); err != nil {
if err := r._collectResources(loader, vocabularies, sch, base, schPtr, fallback); err != nil {
return err
}
r.subschemasProcessed[schPtr] = struct{}{}
return nil
}

func (r *root) _collectResources(loader *defaultLoader, sch any, base url, schPtr jsonPointer, fallback dialect) error {
func (r *root) _collectResources(loader *defaultLoader, vocabularies map[string]*Vocabulary, sch any, base url, schPtr jsonPointer, fallback dialect) error {
if _, ok := sch.(bool); ok {
if schPtr.isEmpty() {
// root resource
Expand Down Expand Up @@ -150,7 +150,7 @@ func (r *root) _collectResources(loader *defaultLoader, sch any, base url, schPt
}
if !found {
if hasSchema {
vocabs, err := loader.getMetaVocabs(sch, draft)
vocabs, err := loader.getMetaVocabs(sch, draft, vocabularies)
if err != nil {
return err
}
Expand All @@ -177,22 +177,22 @@ func (r *root) _collectResources(loader *defaultLoader, sch any, base url, schPt
subschemas := map[jsonPointer]any{}
draft.subschemas.collect(obj, schPtr, subschemas)
for ptr, v := range subschemas {
if err := r.collectResources(loader, v, base, ptr, fallback); err != nil {
if err := r.collectResources(loader, vocabularies, v, base, ptr, fallback); err != nil {
return err
}
}

return nil
}

func (r *root) addSubschema(loader *defaultLoader, ptr jsonPointer) error {
func (r *root) addSubschema(loader *defaultLoader, vocabularies map[string]*Vocabulary, ptr jsonPointer) error {
v, err := (&urlPtr{r.url, ptr}).lookup(r.doc)
if err != nil {
return err
}
base := r.resource(ptr)
baseURL := base.id
if err := r.collectResources(loader, v, baseURL, ptr, base.dialect); err != nil {
if err := r.collectResources(loader, vocabularies, v, baseURL, ptr, base.dialect); err != nil {
return err
}

Expand Down
4 changes: 2 additions & 2 deletions roots.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (rr *roots) addRoot(u url, doc any) (*root, error) {
resources: map[jsonPointer]*resource{},
subschemasProcessed: map[jsonPointer]struct{}{},
}
if err := r.collectResources(&rr.loader, doc, u, "", dialect{rr.defaultDraft, nil}); err != nil {
if err := r.collectResources(&rr.loader, rr.vocabularies, doc, u, "", dialect{rr.defaultDraft, nil}); err != nil {
return nil, err
}
if !strings.HasPrefix(u.String(), "http://json-schema.org/") &&
Expand Down Expand Up @@ -80,7 +80,7 @@ func (rr *roots) ensureSubschema(up urlPtr) error {
return err
}
rClone := r.clone()
if err := rClone.addSubschema(&rr.loader, up.ptr); err != nil {
if err := rClone.addSubschema(&rr.loader, rr.vocabularies, up.ptr); err != nil {
return err
}
if err := rr.validate(rClone, v, up.ptr); err != nil {
Expand Down

0 comments on commit 2b1ce1d

Please sign in to comment.