Skip to content
Open
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
32 changes: 23 additions & 9 deletions compiler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jsonschema

import (
"context"
"fmt"
"regexp"
"slices"
Expand Down Expand Up @@ -134,7 +135,13 @@ func (c *Compiler) AddResource(url string, doc any) error {

// UseLoader overrides the default [URLLoader] used
// to load schema resources.
//
// Deprecated: use URLLoaderContext
func (c *Compiler) UseLoader(loader URLLoader) {
c.roots.loader.loader = contextAdapterLoader{loader}
}

func (c *Compiler) UseLoaderContext(loader URLLoaderContext) {
c.roots.loader.loader = loader
}

Expand Down Expand Up @@ -175,34 +182,41 @@ func (c *Compiler) MustCompile(loc string) *Schema {
}

// Compile compiles json-schema at given loc.
//
// Deprecated: use [CompileContext]
func (c *Compiler) Compile(loc string) (*Schema, error) {
return c.CompileContext(context.Background(), loc)
}

// CompileContext compiles json-schema at given loc.
func (c *Compiler) CompileContext(ctx context.Context, loc string) (*Schema, error) {
uf, err := absolute(loc)
if err != nil {
return nil, err
}
up, err := c.roots.resolveFragment(*uf)
up, err := c.roots.resolveFragment(ctx, *uf)
if err != nil {
return nil, err
}
return c.doCompile(up)
return c.doCompile(ctx, up)
}

func (c *Compiler) doCompile(up urlPtr) (*Schema, error) {
func (c *Compiler) doCompile(ctx context.Context, up urlPtr) (*Schema, error) {
q := &queue{}
compiled := 0

c.enqueue(q, up)
for q.len() > compiled {
sch := q.at(compiled)
if err := c.roots.ensureSubschema(sch.up); err != nil {
if err := c.roots.ensureSubschema(ctx, sch.up); err != nil {
return nil, err
}
r := c.roots.roots[sch.up.url]
v, err := sch.up.lookup(r.doc)
if err != nil {
return nil, err
}
if err := c.compileValue(v, sch, r, q); err != nil {
if err := c.compileValue(ctx, v, sch, r, q); err != nil {
return nil, err
}
compiled++
Expand All @@ -213,7 +227,7 @@ func (c *Compiler) doCompile(up urlPtr) (*Schema, error) {
return c.schemas[up], nil
}

func (c *Compiler) compileValue(v any, sch *Schema, r *root, q *queue) error {
func (c *Compiler) compileValue(ctx context.Context, v any, sch *Schema, r *root, q *queue) error {
res := r.resource(sch.up.ptr)
sch.DraftVersion = res.dialect.draft.version

Expand All @@ -239,7 +253,7 @@ func (c *Compiler) compileValue(v any, sch *Schema, r *root, q *queue) error {
case bool:
sch.Bool = &v
case map[string]any:
if err := c.compileObject(v, sch, r, q); err != nil {
if err := c.compileObject(ctx, v, sch, r, q); err != nil {
return err
}
}
Expand All @@ -261,7 +275,7 @@ func (c *Compiler) compileValue(v any, sch *Schema, r *root, q *queue) error {
return nil
}

func (c *Compiler) compileObject(obj map[string]any, sch *Schema, r *root, q *queue) error {
func (c *Compiler) compileObject(ctx context.Context, obj map[string]any, sch *Schema, r *root, q *queue) error {
if len(obj) == 0 {
b := true
sch.Bool = &b
Expand All @@ -275,7 +289,7 @@ func (c *Compiler) compileObject(obj map[string]any, sch *Schema, r *root, q *qu
res: r.resource(sch.up.ptr),
q: q,
}
return oc.compile(sch)
return oc.compile(ctx, sch)
}

// queue --
Expand Down
5 changes: 3 additions & 2 deletions draft_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jsonschema

import (
"context"
"reflect"
"strings"
"testing"
Expand Down Expand Up @@ -77,7 +78,7 @@ func TestDraft_collectIds(t *testing.T) {
resources: map[jsonPointer]*resource{},
subschemasProcessed: map[jsonPointer]struct{}{},
}
if err := rr.collectResources(&r, doc, u, jsonPointer(""), dialect{Draft4, nil}); err != nil {
if err := rr.collectResources(context.Background(), &r, doc, u, jsonPointer(""), dialect{Draft4, nil}); err != nil {
t.Fatal(err)
}

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

Expand Down
15 changes: 11 additions & 4 deletions example_http_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jsonschema_test

import (
"context"
"crypto/tls"
"fmt"
"log"
Expand All @@ -13,9 +14,15 @@ import (

type HTTPURLLoader http.Client

func (l *HTTPURLLoader) Load(url string) (any, error) {
func (l *HTTPURLLoader) Load(ctx context.Context, url string) (any, error) {
client := (*http.Client)(l)
resp, err := client.Get(url)

req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}

resp, err := client.Do(req)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -44,14 +51,14 @@ func Example_fromHTTPS() {
schemaURL := "https://raw.githubusercontent.com/santhosh-tekuri/boon/main/tests/examples/schema.json"
instanceFile := "./testdata/examples/instance.json"

loader := jsonschema.SchemeURLLoader{
loader := jsonschema.SchemeURLLoaderContext{
"file": jsonschema.FileLoader{},
"http": newHTTPURLLoader(false),
"https": newHTTPURLLoader(false),
}

c := jsonschema.NewCompiler()
c.UseLoader(loader)
c.UseLoaderContext(loader)
sch, err := c.Compile(schemaURL)
if err != nil {
log.Fatal(err)
Expand Down
7 changes: 4 additions & 3 deletions invalid_schemas_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jsonschema_test

import (
"context"
"encoding/json"
"fmt"
"os"
Expand Down Expand Up @@ -34,11 +35,11 @@ func TestInvalidSchemas(t *testing.T) {
t.Log(test.Description)
url := "http://invalid-schemas.com/schema.json"
c := jsonschema.NewCompiler()
loader := jsonschema.SchemeURLLoader{
loader := jsonschema.SchemeURLLoaderContext{
"file": jsonschema.FileLoader{},
"http": invalidRemotes(test.Remotes),
}
c.UseLoader(loader)
c.UseLoaderContext(loader)
if err := c.AddResource(url, test.Schema); err != nil {
t.Fatalf("addResource failed: %v", err)
}
Expand Down Expand Up @@ -66,7 +67,7 @@ func TestInvalidSchemas(t *testing.T) {

type invalidRemotes map[string]any

func (l invalidRemotes) Load(url string) (any, error) {
func (l invalidRemotes) Load(ctx context.Context, url string) (any, error) {
if v, ok := l[url]; ok {
return v, nil
}
Expand Down
52 changes: 43 additions & 9 deletions loader.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jsonschema

import (
"context"
"embed"
"encoding/json"
"errors"
Expand All @@ -15,17 +16,32 @@ import (
)

// URLLoader knows how to load json from given url.
//
// Deprecated: use URLLoaderContext
type URLLoader interface {
// Load loads json from given absolute url.
Load(url string) (any, error)
}

// URLLoaderContext knows how to load json from given url.
type URLLoaderContext interface {
// Load loads json from given absolute url.
Load(ctx context.Context, url string) (any, error)
}

// --

// contextAdapterLoader is adapter to convert [URLLoader] to [URLLoaderContext]
type contextAdapterLoader struct{ loader URLLoader }

func (l contextAdapterLoader) Load(ctx context.Context, url string) (any, error) {
return l.loader.Load(url)
}

// FileLoader loads json file url.
type FileLoader struct{}

func (l FileLoader) Load(url string) (any, error) {
func (l FileLoader) Load(ctx context.Context, url string) (any, error) {
path, err := l.ToFile(url)
if err != nil {
return nil, err
Expand Down Expand Up @@ -59,6 +75,8 @@ func (l FileLoader) ToFile(url string) (string, error) {

// SchemeURLLoader delegates to other [URLLoaders]
// based on url scheme.
//
// Deprecated: use SchemeURLLoaderContext
type SchemeURLLoader map[string]URLLoader

func (l SchemeURLLoader) Load(url string) (any, error) {
Expand All @@ -73,6 +91,22 @@ func (l SchemeURLLoader) Load(url string) (any, error) {
return ll.Load(url)
}

// SchemeURLLoader delegates to other [URLLoadersContext]
// based on url scheme.
type SchemeURLLoaderContext map[string]URLLoaderContext

func (l SchemeURLLoaderContext) Load(ctx context.Context, url string) (any, error) {
u, err := gourl.Parse(url)
if err != nil {
return nil, err
}
ll, ok := l[u.Scheme]
if !ok {
return nil, &UnsupportedURLSchemeError{u.String()}
}
return ll.Load(ctx, url)
}

// --

//go:embed metaschemas
Expand Down Expand Up @@ -128,7 +162,7 @@ func loadMeta(url string) (any, error) {

type defaultLoader struct {
docs map[url]any // docs loaded so far
loader URLLoader
loader URLLoaderContext
}

func (l *defaultLoader) add(url url, doc any) bool {
Expand All @@ -139,7 +173,7 @@ func (l *defaultLoader) add(url url, doc any) bool {
return true
}

func (l *defaultLoader) load(url url) (any, error) {
func (l *defaultLoader) load(ctx context.Context, url url) (any, error) {
if doc, ok := l.docs[url]; ok {
return doc, nil
}
Expand All @@ -154,15 +188,15 @@ func (l *defaultLoader) load(url url) (any, error) {
if l.loader == nil {
return nil, &LoadURLError{url.String(), errors.New("no URLLoader set")}
}
doc, err = l.loader.Load(url.String())
doc, err = l.loader.Load(ctx, url.String())
if err != nil {
return nil, &LoadURLError{URL: url.String(), Err: err}
}
l.add(url, doc)
return doc, nil
}

func (l *defaultLoader) getDraft(up urlPtr, doc any, defaultDraft *Draft, cycle map[url]struct{}) (*Draft, error) {
func (l *defaultLoader) getDraft(ctx context.Context, up urlPtr, doc any, defaultDraft *Draft, cycle map[url]struct{}) (*Draft, error) {
obj, ok := doc.(map[string]any)
if !ok {
return defaultDraft, nil
Expand All @@ -186,14 +220,14 @@ func (l *defaultLoader) getDraft(up urlPtr, doc any, defaultDraft *Draft, cycle
return nil, &MetaSchemaCycleError{schUrl.String()}
}
cycle[schUrl] = struct{}{}
doc, err := l.load(schUrl)
doc, err := l.load(ctx, schUrl)
if err != nil {
return nil, err
}
return l.getDraft(urlPtr{schUrl, ""}, doc, defaultDraft, cycle)
return l.getDraft(ctx, urlPtr{schUrl, ""}, doc, defaultDraft, cycle)
}

func (l *defaultLoader) getMetaVocabs(doc any, draft *Draft, vocabularies map[string]*Vocabulary) ([]string, error) {
func (l *defaultLoader) getMetaVocabs(ctx context.Context, doc any, draft *Draft, vocabularies map[string]*Vocabulary) ([]string, error) {
obj, ok := doc.(map[string]any)
if !ok {
return nil, nil
Expand All @@ -210,7 +244,7 @@ func (l *defaultLoader) getMetaVocabs(doc any, draft *Draft, vocabularies map[st
return nil, &ParseURLError{sch, err}
}
schUrl := url(sch)
doc, err := l.load(schUrl)
doc, err := l.load(ctx, schUrl)
if err != nil {
return nil, err
}
Expand Down
Loading