From b530fb44a01022f2b646332bfcb6d2fe1a608154 Mon Sep 17 00:00:00 2001 From: Alexey Reshetnik Date: Wed, 22 Dec 2021 17:20:31 +0200 Subject: [PATCH 1/5] feat: added phone number validation rules (#7) --- .gitignore | 1 + formats.go | 21 +++++++++++++++++++++ formats_test.go | 17 +++++++++++++++++ go.mod | 1 + go.sum | 8 ++++---- 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 7755fa2..22d3a96 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode +.idea coverage.txt vendor diff --git a/formats.go b/formats.go index 072f52f..6e123bc 100644 --- a/formats.go +++ b/formats.go @@ -5,6 +5,7 @@ package jsonschema import ( + "github.com/nyaruka/phonenumbers" "net" "net/mail" "net/url" @@ -25,6 +26,7 @@ var Formats = map[string]func(interface{}) bool{ "time": isTime, "hostname": isHostname, "email": isEmail, + "tel": isPhone, "ip-address": isIPV4, "ipv4": isIPV4, "ipv6": isIPV6, @@ -167,6 +169,25 @@ func isEmail(v interface{}) bool { return err == nil } +// isPhone tells whether given string is a valid phone number +func isPhone(v interface{}) bool { + num, ok := v.(string) + if !ok { + return false + } + + phoneNumber, err := phonenumbers.Parse(num, "") + if err != nil { + return false + } + + if !phonenumbers.IsValidNumber(phoneNumber) { + return false + } + + return true +} + // isIPV4 tells whether given string is a valid representation of an IPv4 address // according to the "dotted-quad" ABNF syntax as defined in RFC 2673, section 3.2. func isIPV4(v interface{}) bool { diff --git a/formats_test.go b/formats_test.go index 439c5c3..a209996 100644 --- a/formats_test.go +++ b/formats_test.go @@ -92,6 +92,23 @@ func TestIsEmail(t *testing.T) { } } +func TestIsPhone(t *testing.T) { + tests := []test{ + {"+380634872774", true}, + {"+442087599036", true}, + {"+18004444444", true}, + {"1800801920", false}, + {"0634872774", false}, + {"+38-063-482-723-77", false}, // more than 17 characters long + } + + for i, test := range tests { + if test.valid != isPhone(test.str) { + t.Errorf("#%d: %q, valid %t, got valid %t", i, test.str, test.valid, !test.valid) + } + } +} + func TestIsIPV4(t *testing.T) { tests := []test{ {"192.168.0.1", true}, diff --git a/go.mod b/go.mod index 6e214e7..2b4419c 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ replace github.com/oleiade/reflections => github.com/oleiade/reflections v1.0.1 require ( github.com/jandelgado/gcov2lcov v1.0.4 + github.com/nyaruka/phonenumbers v1.0.73 github.com/ory/go-acc v0.2.6 github.com/ory/x v0.0.272 github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518 diff --git a/go.sum b/go.sum index 2c11f91..a3b8f39 100644 --- a/go.sum +++ b/go.sum @@ -155,7 +155,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-bindata/go-bindata v3.1.1+incompatible h1:tR4f0e4VTO7LK6B2YWyAoVEzG9ByG1wrXB4TL9+jiYg= github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -496,7 +495,6 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -553,7 +551,6 @@ github.com/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gotestyourself/gotestyourself v1.3.0/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= @@ -831,6 +828,8 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nyaruka/phonenumbers v1.0.73 h1:bP2WN8/NUP8tQebR+WCIejFaibwYMHOaB7MQVayclUo= +github.com/nyaruka/phonenumbers v1.0.73/go.mod h1:3aiS+PS3DuYwkbK3xdcmRwMiPNECZ0oENH8qUT1lY7Q= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -1362,8 +1361,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 77cd0cde3bd86e2dcca9630371edb1425cc5704b Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Thu, 20 Jan 2022 14:15:29 +0100 Subject: [PATCH 2/5] feat: contextualize methods and add ability to load custom HTTP loader --- base64loader/base64loader.go | 5 +-- base64loader/base64loader_test.go | 6 ++-- cmd/jv/main.go | 5 +-- compiler.go | 53 ++++++++++++++++--------------- draft4.go | 8 +++-- draft6.go | 8 +++-- draft7.go | 4 ++- extension.go | 10 +++--- extension_test.go | 9 ++++-- fileloader/fileloader.go | 3 +- formats.go | 3 +- go.mod | 2 ++ go.sum | 2 ++ httploader/httploader.go | 20 ++++++++++-- httploader/httploader_test.go | 53 +++++++++++++++++++++++++++++++ loaders.go | 11 ++++--- schema.go | 13 ++++---- schema_test.go | 48 +++++++++++++++++----------- tools.go | 3 +- validation_context_test.go | 4 ++- 20 files changed, 190 insertions(+), 80 deletions(-) create mode 100644 httploader/httploader_test.go diff --git a/base64loader/base64loader.go b/base64loader/base64loader.go index ee138c8..384ff16 100644 --- a/base64loader/base64loader.go +++ b/base64loader/base64loader.go @@ -6,10 +6,11 @@ // To use base64loader, link this package into your program: // import _ "github.com/ory/jsonschema/v3/base64loader" // -package fileloader +package base64loader import ( "bytes" + "context" "encoding/base64" "fmt" "io" @@ -20,7 +21,7 @@ import ( ) // Load implements jsonschema.Loader -func Load(url string) (_ io.ReadCloser, err error) { +func Load(ctx context.Context, url string) (_ io.ReadCloser, err error) { encoded := strings.TrimPrefix(url, "base64://") var raw []byte diff --git a/base64loader/base64loader_test.go b/base64loader/base64loader_test.go index 9ca7266..6477a1e 100644 --- a/base64loader/base64loader_test.go +++ b/base64loader/base64loader_test.go @@ -1,14 +1,14 @@ -package fileloader_test +package base64loader_test import ( "bytes" + "context" "encoding/base64" "testing" "github.com/stretchr/testify/require" "github.com/ory/jsonschema/v3" - _ "github.com/ory/jsonschema/v3/base64loader" ) func TestLoad(t *testing.T) { @@ -31,7 +31,7 @@ func TestLoad(t *testing.T) { base64.RawURLEncoding, base64.RawStdEncoding, } { - c, err := jsonschema.Compile("base64://" + enc.EncodeToString([]byte(schema))) + c, err := jsonschema.Compile(context.Background(), "base64://"+enc.EncodeToString([]byte(schema))) require.NoError(t, err) require.EqualError(t, c.Validate(bytes.NewBufferString(`{"bar": 1234}`)), "I[#/bar] S[#/properties/bar/type] expected string, but got number") } diff --git a/cmd/jv/main.go b/cmd/jv/main.go index 6944da2..ae68945 100644 --- a/cmd/jv/main.go +++ b/cmd/jv/main.go @@ -5,6 +5,7 @@ package main import ( + "context" "fmt" "os" @@ -18,14 +19,14 @@ func main() { os.Exit(1) } - schema, err := jsonschema.Compile(os.Args[1]) + schema, err := jsonschema.Compile(context.Background(), os.Args[1]) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } for _, f := range os.Args[2:] { - r, err := jsonschema.LoadURL(f) + r, err := jsonschema.LoadURL(context.Background(), f) if err != nil { fmt.Fprintf(os.Stderr, "error in reading %q. reason: \n%v\n", f, err) os.Exit(1) diff --git a/compiler.go b/compiler.go index 6a13e6a..391d4e7 100644 --- a/compiler.go +++ b/compiler.go @@ -5,6 +5,7 @@ package jsonschema import ( + "context" "encoding/json" "fmt" "io" @@ -44,7 +45,7 @@ type Compiler struct { // LoadURL loads the document at given URL. // // If nil, package global LoadURL is used. - LoadURL func(s string) (io.ReadCloser, error) + LoadURL func(ctx context.Context, s string) (io.ReadCloser, error) } // NewCompiler returns a json-schema Compiler object. @@ -81,8 +82,8 @@ func (c *Compiler) AddResource(url string, r io.Reader) error { // MustCompile is like Compile but panics if the url cannot be compiled to *Schema. // It simplifies safe initialization of global variables holding compiled Schemas. -func (c *Compiler) MustCompile(url string) *Schema { - s, err := c.Compile(url) +func (c *Compiler) MustCompile(ctx context.Context, url string) *Schema { + s, err := c.Compile(ctx, url) if err != nil { panic(fmt.Sprintf("jsonschema: Compile(%q): %s", url, err)) } @@ -91,10 +92,10 @@ func (c *Compiler) MustCompile(url string) *Schema { // Compile parses json-schema at given url returns, if successful, // a Schema object that can be used to match against json. -func (c *Compiler) Compile(url string) (*Schema, error) { +func (c *Compiler) Compile(ctx context.Context, url string) (*Schema, error) { base, fragment := split(url) if _, ok := c.resources[base]; !ok { - r, err := c.loadURL(base) + r, err := c.loadURL(ctx, base) if err != nil { return nil, err } @@ -125,17 +126,17 @@ func (c *Compiler) Compile(url string) (*Schema, error) { r.draft = c.Draft } } - return c.compileRef(r, r.url, fragment) + return c.compileRef(ctx, r, r.url, fragment) } -func (c Compiler) loadURL(s string) (io.ReadCloser, error) { +func (c Compiler) loadURL(ctx context.Context, s string) (io.ReadCloser, error) { if c.LoadURL != nil { - return c.LoadURL(s) + return c.LoadURL(ctx, s) } - return LoadURL(s) + return LoadURL(ctx, s) } -func (c *Compiler) compileRef(r *resource, base, ref string) (*Schema, error) { +func (c *Compiler) compileRef(ctx context.Context, r *resource, base, ref string) (*Schema, error) { var err error if rootFragment(ref) { if _, ok := r.schemas["#"]; !ok { @@ -144,7 +145,7 @@ func (c *Compiler) compileRef(r *resource, base, ref string) (*Schema, error) { } s := &Schema{URL: r.url, Ptr: "#"} r.schemas["#"] = s - if _, err := c.compile(r, s, base, r.doc); err != nil { + if _, err := c.compile(ctx, r, s, base, r.doc); err != nil { return nil, err } } @@ -161,7 +162,7 @@ func (c *Compiler) compileRef(r *resource, base, ref string) (*Schema, error) { return nil, err } r.schemas[ref] = &Schema{URL: base, Ptr: ref} - if _, err := c.compile(r, r.schemas[ref], ptrBase, doc); err != nil { + if _, err := c.compile(ctx, r, r.schemas[ref], ptrBase, doc); err != nil { return nil, err } } @@ -187,7 +188,7 @@ func (c *Compiler) compileRef(r *resource, base, ref string) (*Schema, error) { u, f := split(refURL) s := &Schema{URL: u, Ptr: f} r.schemas[refURL] = s - if err := c.compileMap(r, s, refURL, v); err != nil { + if err := c.compileMap(ctx, r, s, refURL, v); err != nil { return nil, err } return s, nil @@ -197,10 +198,10 @@ func (c *Compiler) compileRef(r *resource, base, ref string) (*Schema, error) { if base == r.url { return nil, fmt.Errorf("invalid ref: %q", refURL) } - return c.Compile(refURL) + return c.Compile(ctx, refURL) } -func (c *Compiler) compile(r *resource, s *Schema, base string, m interface{}) (*Schema, error) { +func (c *Compiler) compile(ctx context.Context, r *resource, s *Schema, base string, m interface{}) (*Schema, error) { if s == nil { s = new(Schema) s.URL, _ = split(base) @@ -210,11 +211,11 @@ func (c *Compiler) compile(r *resource, s *Schema, base string, m interface{}) ( s.Always = &m return s, nil default: - return s, c.compileMap(r, s, base, m.(map[string]interface{})) + return s, c.compileMap(ctx, r, s, base, m.(map[string]interface{})) } } -func (c *Compiler) compileMap(r *resource, s *Schema, base string, m map[string]interface{}) error { +func (c *Compiler) compileMap(ctx context.Context, r *resource, s *Schema, base string, m map[string]interface{}) error { var err error if id, ok := m[r.draft.id]; ok { @@ -225,7 +226,7 @@ func (c *Compiler) compileMap(r *resource, s *Schema, base string, m map[string] if ref, ok := m["$ref"]; ok { b, _ := split(base) - s.Ref, err = c.compileRef(r, b, ref.(string)) + s.Ref, err = c.compileRef(ctx, r, b, ref.(string)) if err != nil { return err } @@ -267,7 +268,7 @@ func (c *Compiler) compileMap(r *resource, s *Schema, base string, m map[string] loadSchema := func(pname string) (*Schema, error) { if pvalue, ok := m[pname]; ok { - return c.compile(r, nil, base, pvalue) + return c.compile(ctx, r, nil, base, pvalue) } return nil, nil } @@ -281,7 +282,7 @@ func (c *Compiler) compileMap(r *resource, s *Schema, base string, m map[string] pvalue := pvalue.([]interface{}) schemas := make([]*Schema, len(pvalue)) for i, v := range pvalue { - sch, err := c.compile(r, nil, base, v) + sch, err := c.compile(ctx, r, nil, base, v) if err != nil { return nil, err } @@ -318,7 +319,7 @@ func (c *Compiler) compileMap(r *resource, s *Schema, base string, m map[string] props := props.(map[string]interface{}) s.Properties = make(map[string]*Schema, len(props)) for pname, pmap := range props { - s.Properties[pname], err = c.compile(r, nil, base, pmap) + s.Properties[pname], err = c.compile(ctx, r, nil, base, pmap) if err != nil { return err } @@ -333,7 +334,7 @@ func (c *Compiler) compileMap(r *resource, s *Schema, base string, m map[string] patternProps := patternProps.(map[string]interface{}) s.PatternProperties = make(map[*regexp.Regexp]*Schema, len(patternProps)) for pattern, pmap := range patternProps { - s.PatternProperties[regexp.MustCompile(pattern)], err = c.compile(r, nil, base, pmap) + s.PatternProperties[regexp.MustCompile(pattern)], err = c.compile(ctx, r, nil, base, pmap) if err != nil { return err } @@ -347,7 +348,7 @@ func (c *Compiler) compileMap(r *resource, s *Schema, base string, m map[string] s.AdditionalProperties = false } case map[string]interface{}: - s.AdditionalProperties, err = c.compile(r, nil, base, additionalProps) + s.AdditionalProperties, err = c.compile(ctx, r, nil, base, additionalProps) if err != nil { return err } @@ -362,7 +363,7 @@ func (c *Compiler) compileMap(r *resource, s *Schema, base string, m map[string] case []interface{}: s.Dependencies[pname] = toStrings(pvalue) default: - s.Dependencies[pname], err = c.compile(r, nil, base, pvalue) + s.Dependencies[pname], err = c.compile(ctx, r, nil, base, pvalue) if err != nil { return err } @@ -388,7 +389,7 @@ func (c *Compiler) compileMap(r *resource, s *Schema, base string, m map[string] case bool: s.AdditionalItems = additionalItems case map[string]interface{}: - s.AdditionalItems, err = c.compile(r, nil, base, additionalItems) + s.AdditionalItems, err = c.compile(ctx, r, nil, base, additionalItems) if err != nil { return err } @@ -397,7 +398,7 @@ func (c *Compiler) compileMap(r *resource, s *Schema, base string, m map[string] s.AdditionalItems = true } default: - s.Items, err = c.compile(r, nil, base, items) + s.Items, err = c.compile(ctx, r, nil, base, items) if err != nil { return err } diff --git a/draft4.go b/draft4.go index 17463d8..4dee42d 100644 --- a/draft4.go +++ b/draft4.go @@ -4,7 +4,10 @@ package jsonschema -import "strings" +import ( + "context" + "strings" +) // Draft4 respresents http://json-schema.org/specification-links.html#draft-4 var Draft4 = &Draft{id: "id", version: 4, url: "http://json-schema.org/draft-04/schema", data: `{ @@ -162,10 +165,11 @@ var Draft4 = &Draft{id: "id", version: 4, url: "http://json-schema.org/draft-04/ }`} func init() { + ctx := context.Background() c := NewCompiler() err := c.AddResource(Draft4.url, strings.NewReader(Draft4.data)) if err != nil { panic(err) } - Draft4.meta = c.MustCompile(Draft4.url) + Draft4.meta = c.MustCompile(ctx, Draft4.url) } diff --git a/draft6.go b/draft6.go index 1138a9a..248aa31 100644 --- a/draft6.go +++ b/draft6.go @@ -4,7 +4,10 @@ package jsonschema -import "strings" +import ( + "context" + "strings" +) // Draft6 respresents http://json-schema.org/specification-links.html#draft-6 var Draft6 = &Draft{id: "$id", version: 6, url: "http://json-schema.org/draft-06/schema", data: `{ @@ -160,10 +163,11 @@ var Draft6 = &Draft{id: "$id", version: 6, url: "http://json-schema.org/draft-06 }`} func init() { + ctx := context.Background() c := NewCompiler() err := c.AddResource(Draft6.url, strings.NewReader(Draft6.data)) if err != nil { panic(err) } - Draft6.meta = c.MustCompile(Draft6.url) + Draft6.meta = c.MustCompile(ctx, Draft6.url) } diff --git a/draft7.go b/draft7.go index 90856d7..17dd92c 100644 --- a/draft7.go +++ b/draft7.go @@ -5,6 +5,7 @@ package jsonschema import ( + "context" "strings" ) @@ -186,10 +187,11 @@ var Draft7 = &Draft{id: "$id", version: 7, url: "http://json-schema.org/draft-07 }`} func init() { + ctx := context.Background() c := NewCompiler() err := c.AddResource(Draft7.url, strings.NewReader(Draft7.data)) if err != nil { panic(err) } - Draft7.meta = c.MustCompile(Draft7.url) + Draft7.meta = c.MustCompile(ctx, Draft7.url) } diff --git a/extension.go b/extension.go index bf3c0ca..85462a4 100644 --- a/extension.go +++ b/extension.go @@ -4,6 +4,8 @@ package jsonschema +import "context" + // Extension is used to define additional keywords to standard jsonschema. // An extension can implement more than one keyword. // @@ -33,14 +35,14 @@ type CompilerContext struct { // Compile compiles given value v into *Schema. This is useful in implementing // keyword like allOf/oneOf -func (ctx CompilerContext) Compile(v interface{}) (*Schema, error) { - return ctx.c.compile(ctx.r, nil, ctx.base, v) +func (ctx CompilerContext) Compile(c context.Context, v interface{}) (*Schema, error) { + return ctx.c.compile(c, ctx.r, nil, ctx.base, v) } // CompileRef compiles the schema referenced by ref uri -func (ctx CompilerContext) CompileRef(ref string) (*Schema, error) { +func (ctx CompilerContext) CompileRef(c context.Context, ref string) (*Schema, error) { b, _ := split(ctx.base) - return ctx.c.compileRef(ctx.r, b, ref) + return ctx.c.compileRef(c, ctx.r, b, ref) } // ValidationContext provides additional context required in validating for extension. diff --git a/extension_test.go b/extension_test.go index f4b6540..5f5ae4c 100644 --- a/extension_test.go +++ b/extension_test.go @@ -5,6 +5,7 @@ package jsonschema_test import ( + "context" "encoding/json" "fmt" "strconv" @@ -14,8 +15,10 @@ import ( "github.com/ory/jsonschema/v3" ) +var ctx = context.Background() + func powerOfExt() jsonschema.Extension { - meta, err := jsonschema.CompileString("powerOf.json", `{ + meta, err := jsonschema.CompileString(ctx, "powerOf.json", `{ "properties" : { "powerOf": { "type": "integer", @@ -62,7 +65,7 @@ func TestPowerOfExt(t *testing.T) { if err := c.AddResource("test.json", strings.NewReader(`{"powerOf": "hello"}`)); err != nil { t.Fatal(err) } - _, err := c.Compile("test.json") + _, err := c.Compile(ctx, "test.json") if err == nil { t.Fatal("error expected") } @@ -74,7 +77,7 @@ func TestPowerOfExt(t *testing.T) { if err := c.AddResource("test.json", strings.NewReader(`{"powerOf": 10}`)); err != nil { t.Fatal(err) } - sch, err := c.Compile("test.json") + sch, err := c.Compile(ctx, "test.json") if err != nil { t.Fatal(err) } diff --git a/fileloader/fileloader.go b/fileloader/fileloader.go index 2fcec71..795f3d4 100644 --- a/fileloader/fileloader.go +++ b/fileloader/fileloader.go @@ -9,6 +9,7 @@ package fileloader import ( + "context" "io" "os" "strings" @@ -17,7 +18,7 @@ import ( ) // Load implements jsonschema.Loader -func Load(url string) (io.ReadCloser, error) { +func Load(ctx context.Context, url string) (io.ReadCloser, error) { f, err := os.Open(strings.TrimPrefix(url, "file://")) if err != nil { return nil, err diff --git a/formats.go b/formats.go index 6e123bc..ad380ad 100644 --- a/formats.go +++ b/formats.go @@ -5,7 +5,6 @@ package jsonschema import ( - "github.com/nyaruka/phonenumbers" "net" "net/mail" "net/url" @@ -13,6 +12,8 @@ import ( "strconv" "strings" "time" + + "github.com/nyaruka/phonenumbers" ) // Formats is a registry of functions, which know how to validate diff --git a/go.mod b/go.mod index 2b4419c..7c4884f 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.16 replace github.com/oleiade/reflections => github.com/oleiade/reflections v1.0.1 require ( + github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 + github.com/hashicorp/go-retryablehttp v0.6.8 github.com/jandelgado/gcov2lcov v1.0.4 github.com/nyaruka/phonenumbers v1.0.73 github.com/ory/go-acc v0.2.6 diff --git a/go.sum b/go.sum index a3b8f39..1b3ed0a 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmatcuk/doublestar/v2 v2.0.3/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -709,6 +710,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= diff --git a/httploader/httploader.go b/httploader/httploader.go index 72ef695..59fd7dd 100644 --- a/httploader/httploader.go +++ b/httploader/httploader.go @@ -13,25 +13,41 @@ package httploader import ( + "context" "fmt" "io" "net/http" + "github.com/hashicorp/go-retryablehttp" + "github.com/ory/x/httpx" "github.com/ory/jsonschema/v3" ) +const ContextKey = "github.com/ory/jsonschema/v3/httploader.HTTPClient" + // Load implements jsonschemav2.Loader -func Load(url string) (io.ReadCloser, error) { - resp, err := httpx.NewResilientClient().Get(url) +func Load(ctx context.Context, url string) (io.ReadCloser, error) { + var hc *retryablehttp.Client + if v := ctx.Value(ContextKey); v == nil { + hc = httpx.NewResilientClient() + } else if c, ok := v.(*retryablehttp.Client); ok { + hc = c + } else { + return nil, fmt.Errorf("invalid context value for %s expected %T but got: %T", ContextKey, new(retryablehttp.Client), v) + } + + resp, err := hc.Get(url) if err != nil { return nil, err } + if resp.StatusCode != http.StatusOK { _ = resp.Body.Close() return nil, fmt.Errorf("%s returned status code %d", url, resp.StatusCode) } + return resp.Body, nil } diff --git a/httploader/httploader_test.go b/httploader/httploader_test.go new file mode 100644 index 0000000..df2e1da --- /dev/null +++ b/httploader/httploader_test.go @@ -0,0 +1,53 @@ +package httploader + +import ( + "context" + "errors" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/bmizerany/assert" + "github.com/hashicorp/go-retryablehttp" + "github.com/stretchr/testify/require" +) + +var fooErr = errors.New("foo") + +type rt struct{} + +func (r rt) RoundTrip(_ *http.Request) (*http.Response, error) { + return nil, fooErr +} + +var _ http.RoundTripper = new(rt) + +func TestHTTPLoader(t *testing.T) { + const expectedBody = "Hello, client" + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(expectedBody)) + })) + t.Cleanup(ts.Close) + + mr := func(t *testing.T, ctx context.Context) string { + res, err := Load(context.Background(), ts.URL) + require.NoError(t, err) + defer res.Close() + body, err := ioutil.ReadAll(res) + require.NoError(t, err) + return string(body) + } + + assert.Equal(t, expectedBody, mr(t, context.Background())) + + hc := retryablehttp.NewClient() + hc.RetryMax = 1 + hc.HTTPClient.Transport = new(rt) + _, err := Load(context.WithValue(context.Background(), ContextKey, hc), ts.URL) + require.ErrorIs(t, err, fooErr) + + _, err = Load(context.WithValue(context.Background(), ContextKey, new(struct{})), ts.URL) + require.Error(t, err, fooErr) + assert.Equal(t, "invalid context value for github.com/ory/jsonschema/v3/httploader.HTTPClient expected *retryablehttp.Client but got: *struct {}", err.Error()) +} diff --git a/loaders.go b/loaders.go index f2f984c..2842533 100644 --- a/loaders.go +++ b/loaders.go @@ -1,6 +1,7 @@ package jsonschema import ( + "context" "fmt" "io" "net/url" @@ -10,11 +11,11 @@ import ( "strings" ) -func loadFile(path string) (io.ReadCloser, error) { +func loadFile(ctx context.Context, path string) (io.ReadCloser, error) { return os.Open(path) } -func loadFileURL(s string) (io.ReadCloser, error) { +func loadFileURL(ctx context.Context, s string) (io.ReadCloser, error) { u, err := url.Parse(s) if err != nil { return nil, err @@ -32,7 +33,7 @@ func loadFileURL(s string) (io.ReadCloser, error) { // // New loaders can be registered by adding to this map. Key is schema, // value is function that knows how to load url of that schema -var Loaders = map[string]func(url string) (io.ReadCloser, error){ +var Loaders = map[string]func(ctx context.Context, url string) (io.ReadCloser, error){ "": loadFile, "file": loadFileURL, } @@ -51,7 +52,7 @@ func (s SchemeNotRegisteredError) Error() string { // Users can change this variable, if they would like to take complete // responsibility of loading given URL. Used by Compiler if its LoadURL // field is nil. -var LoadURL = func(s string) (io.ReadCloser, error) { +var LoadURL = func(ctx context.Context, s string) (io.ReadCloser, error) { u, err := url.Parse(s) if err != nil { return nil, err @@ -61,5 +62,5 @@ var LoadURL = func(s string) (io.ReadCloser, error) { return nil, SchemeNotRegisteredError(u.Scheme) } - return loader(s) + return loader(ctx, s) } diff --git a/schema.go b/schema.go index af4bf4e..8bdcd14 100644 --- a/schema.go +++ b/schema.go @@ -5,6 +5,7 @@ package jsonschema import ( + "context" "encoding/json" "fmt" "io" @@ -90,23 +91,23 @@ type Schema struct { // a Schema object that can be used to match against json. // // Returned error can be *SchemaError -func Compile(url string) (*Schema, error) { - return NewCompiler().Compile(url) +func Compile(ctx context.Context, url string) (*Schema, error) { + return NewCompiler().Compile(ctx, url) } // MustCompile is like Compile but panics if the url cannot be compiled to *Schema. // It simplifies safe initialization of global variables holding compiled Schemas. -func MustCompile(url string) *Schema { - return NewCompiler().MustCompile(url) +func MustCompile(ctx context.Context, url string) *Schema { + return NewCompiler().MustCompile(ctx, url) } // CompileString parses and compiles the given schema with given base url. -func CompileString(url, schema string) (*Schema, error) { +func CompileString(ctx context.Context, url, schema string) (*Schema, error) { c := NewCompiler() if err := c.AddResource(url, strings.NewReader(schema)); err != nil { return nil, err } - return c.Compile(url) + return c.Compile(ctx, url) } // Validate validates the given json data, against the json-schema. diff --git a/schema_test.go b/schema_test.go index 8245078..4c66f6a 100644 --- a/schema_test.go +++ b/schema_test.go @@ -6,6 +6,7 @@ package jsonschema_test import ( "bytes" + "context" "crypto/tls" "encoding/json" "errors" @@ -65,6 +66,7 @@ type testGroup struct { } func testFolder(t *testing.T, folder string, draft *jsonschema.Draft) { + ctx := context.Background() server := &http.Server{Addr: "localhost:1234", Handler: http.FileServer(http.Dir("testdata/remotes"))} go func() { if err := server.ListenAndServe(); err != http.ErrServerClosed { @@ -116,7 +118,7 @@ func testFolder(t *testing.T, folder string, draft *jsonschema.Draft) { t.Errorf(" FAIL: add resource failed, reason: %v\n", err) continue } - schema, err := c.Compile("test.json") + schema, err := c.Compile(ctx, "test.json") if err != nil { t.Errorf(" FAIL: schema compilation failed, reason: %v\n", err) continue @@ -158,7 +160,7 @@ func testFolder(t *testing.T, folder string, draft *jsonschema.Draft) { if err := c.AddResource("test.json", strings.NewReader("{}")); err != nil { t.Fatal(err) } - s, err := c.Compile("test.json") + s, err := c.Compile(ctx, "test.json") if err != nil { t.Fatal(err) } @@ -172,13 +174,14 @@ func testFolder(t *testing.T, folder string, draft *jsonschema.Draft) { } func TestInvalidSchema(t *testing.T) { + ctx := context.Background() t.Run("MustCompile with panic", func(t *testing.T) { defer func() { if r := recover(); r == nil { t.Error("panic expected") } }() - jsonschema.MustCompile("testdata/invalid_schema.json") + jsonschema.MustCompile(ctx, "testdata/invalid_schema.json") }) t.Run("MustCompile without panic", func(t *testing.T) { @@ -187,7 +190,7 @@ func TestInvalidSchema(t *testing.T) { t.Error("panic not expected") } }() - jsonschema.MustCompile("testdata/customer_schema.json#/0") + jsonschema.MustCompile(ctx, "testdata/customer_schema.json#/0") }) t.Run("invalid json", func(t *testing.T) { @@ -229,7 +232,7 @@ func TestInvalidSchema(t *testing.T) { if len(test.Fragment) > 0 { url += test.Fragment } - if _, err = c.Compile(url); err == nil { + if _, err = c.Compile(ctx, url); err == nil { t.Error("error expected") } else { t.Log(err) @@ -239,6 +242,7 @@ func TestInvalidSchema(t *testing.T) { } func TestCompileURL(t *testing.T) { + ctx := context.Background() tr := http.DefaultTransport.(*http.Transport) if tr.TLSClientConfig == nil { tr.TLSClientConfig = &tls.Config{} @@ -262,7 +266,7 @@ func TestCompileURL(t *testing.T) { } for i, test := range validTests { t.Logf("valid #%d: %+v", i, test) - s, err := jsonschema.Compile(test.schema) + s, err := jsonschema.Compile(ctx, test.schema) if err != nil { t.Errorf("valid #%d: %v", i, err) return @@ -288,7 +292,7 @@ func TestCompileURL(t *testing.T) { } for i, test := range invalidTests { t.Logf("invalid #%d: %v", i, test) - if _, err := jsonschema.Compile(test); err == nil { + if _, err := jsonschema.Compile(ctx, test); err == nil { t.Errorf("invalid #%d: expected error", i) } else { t.Logf("invalid #%d: %v", i, err) @@ -297,6 +301,8 @@ func TestCompileURL(t *testing.T) { } func TestValidateInterface(t *testing.T) { + ctx := context.Background() + files := []string{ "testdata/draft4/type.json", "testdata/draft4/minimum.json", @@ -322,7 +328,7 @@ func TestValidateInterface(t *testing.T) { continue } c.Draft = jsonschema.Draft4 - schema, err := c.Compile("test.json") + schema, err := c.Compile(ctx, "test.json") if err != nil { t.Errorf(" FAIL: schema compilation failed, reason: %v\n", err) continue @@ -353,12 +359,13 @@ func TestValidateInterface(t *testing.T) { } func TestInvalidJsonTypeError(t *testing.T) { + ctx := context.Background() compiler := jsonschema.NewCompiler() err := compiler.AddResource("test.json", strings.NewReader(`{ "type": "string"}`)) if err != nil { t.Fatalf("addResource failed. reason: %v\n", err) } - schema, err := compiler.Compile("test.json") + schema, err := compiler.Compile(ctx, "test.json") if err != nil { t.Fatalf("schema compilation failed. reason: %v\n", err) } @@ -373,6 +380,7 @@ func TestInvalidJsonTypeError(t *testing.T) { } func TestExtractAnnotations(t *testing.T) { + ctx := context.Background() t.Run("false", func(t *testing.T) { compiler := jsonschema.NewCompiler() @@ -383,7 +391,7 @@ func TestExtractAnnotations(t *testing.T) { t.Fatalf("addResource failed. reason: %v\n", err) } - schema, err := compiler.Compile("test.json") + schema, err := compiler.Compile(ctx, "test.json") if err != nil { t.Fatalf("schema compilation failed. reason: %v\n", err) } @@ -404,7 +412,7 @@ func TestExtractAnnotations(t *testing.T) { t.Fatalf("addResource failed. reason: %v\n", err) } - schema, err := compiler.Compile("test.json") + schema, err := compiler.Compile(ctx, "test.json") if err != nil { t.Fatalf("schema compilation failed. reason: %v\n", err) } @@ -427,7 +435,7 @@ func TestExtractAnnotations(t *testing.T) { t.Fatalf("addResource failed. reason: %v\n", err) } - schema, err := compiler.Compile("test.json") + schema, err := compiler.Compile(ctx, "test.json") if err != nil { t.Fatalf("schema compilation failed. reason: %v\n", err) } @@ -459,6 +467,7 @@ func toFileURL(path string) string { // TestPanic tests https://github.com/ory/jsonschema/issues/18 func TestPanic(t *testing.T) { + ctx := context.Background() schema_d := ` { "type": "object", @@ -489,13 +498,14 @@ func TestPanic(t *testing.T) { t.Fatal(err) } - if _, err := c.Compile("schema.json"); err != nil { + if _, err := c.Compile(ctx, "schema.json"); err != nil { t.Error("no error expected") return } } func TestNonStringFormat(t *testing.T) { + ctx := context.Background() jsonschema.Formats["even-number"] = func(v interface{}) bool { switch v := v.(type) { case json.Number: @@ -513,7 +523,7 @@ func TestNonStringFormat(t *testing.T) { if err := c.AddResource("schema.json", strings.NewReader(schema)); err != nil { t.Fatal(err) } - s, err := c.Compile("schema.json") + s, err := c.Compile(ctx, "schema.json") if err != nil { t.Fatal(err) } @@ -526,13 +536,14 @@ func TestNonStringFormat(t *testing.T) { } func TestCompiler_LoadURL(t *testing.T) { + ctx := context.Background() const ( base = `{ "type": "string" }` schema = `{ "allOf": [{ "$ref": "base.json" }, { "maxLength": 3 }] }` ) c := jsonschema.NewCompiler() - c.LoadURL = func(s string) (io.ReadCloser, error) { + c.LoadURL = func(ctx context.Context, s string) (io.ReadCloser, error) { switch s { case "base.json": return ioutil.NopCloser(strings.NewReader(base)), nil @@ -542,7 +553,7 @@ func TestCompiler_LoadURL(t *testing.T) { return nil, errors.New("unsupported schema") } } - s, err := c.Compile("schema.json") + s, err := c.Compile(ctx, "schema.json") if err != nil { t.Fatal(err) } @@ -555,6 +566,7 @@ func TestCompiler_LoadURL(t *testing.T) { } func TestSchemaReferencesDrafts(t *testing.T) { + ctx := context.Background() c := jsonschema.NewCompiler() file := "testdata/reference_draft.json" t.Log(filepath.Base(file)) @@ -567,8 +579,8 @@ func TestSchemaReferencesDrafts(t *testing.T) { if err != nil { t.Fatalf("addResource failed. reason: %v\n", err) } - _, err = c.Compile("reference_draft.json") + _, err = c.Compile(ctx, "reference_draft.json") if err != nil { t.Fatalf("compile should not error. reason: %v\n", err) } -} \ No newline at end of file +} diff --git a/tools.go b/tools.go index d8d3651..4902242 100644 --- a/tools.go +++ b/tools.go @@ -1,3 +1,4 @@ +//go:build tools // +build tools package jsonschema @@ -5,7 +6,7 @@ package jsonschema import ( _ "github.com/sqs/goreturns" + _ "github.com/jandelgado/gcov2lcov" _ "github.com/ory/go-acc" _ "github.com/ory/x/tools/listx" - _ "github.com/jandelgado/gcov2lcov" ) diff --git a/validation_context_test.go b/validation_context_test.go index c5e434b..06da308 100644 --- a/validation_context_test.go +++ b/validation_context_test.go @@ -2,6 +2,7 @@ package jsonschema_test import ( "bytes" + "context" "fmt" "reflect" "testing" @@ -64,8 +65,9 @@ func TestErrorsContext(t *testing.T) { }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { + ctx := context.Background() var ( - schema = jsonschema.MustCompile(tc.path) + schema = jsonschema.MustCompile(ctx, tc.path) err = schema.Validate(bytes.NewBufferString(tc.doc)) ) From 68e5cd2e155aff8fc83186cf9983e4d947d3435d Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Thu, 20 Jan 2022 15:15:24 +0100 Subject: [PATCH 3/5] fix: contextualize everything --- extension_test.go | 3 --- go.sum | 5 ----- httploader/httploader.go | 4 +--- httploader/httploader_test.go | 2 +- schema_test.go | 25 +++++++------------------ 5 files changed, 9 insertions(+), 30 deletions(-) diff --git a/extension_test.go b/extension_test.go index 5f5ae4c..ce97273 100644 --- a/extension_test.go +++ b/extension_test.go @@ -5,7 +5,6 @@ package jsonschema_test import ( - "context" "encoding/json" "fmt" "strconv" @@ -15,8 +14,6 @@ import ( "github.com/ory/jsonschema/v3" ) -var ctx = context.Background() - func powerOfExt() jsonschema.Extension { meta, err := jsonschema.CompileString(ctx, "powerOf.json", `{ "properties" : { diff --git a/go.sum b/go.sum index 1b3ed0a..ea3ef87 100644 --- a/go.sum +++ b/go.sum @@ -33,7 +33,6 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmDlfhUrweEy1R1Fj1gu5iIM= github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -339,7 +338,6 @@ github.com/gobuffalo/helpers v0.5.0/go.mod h1:stpgxJ2C7T99NLyAxGUnYMM2zAtBk5NKQR github.com/gobuffalo/helpers v0.6.0/go.mod h1:pncVrer7x/KRvnL5aJABLAuT/RhKRR9klL6dkUOhyv8= github.com/gobuffalo/helpers v0.6.1/go.mod h1:wInbDi0vTJKZBviURTLRMFLE4+nF2uRuuL2fnlYo7w4= github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= -github.com/gobuffalo/httptest v1.0.2 h1:LWp2khlgA697h4BIYWW2aRxvB93jMnBrbakQ/r2KLzs= github.com/gobuffalo/httptest v1.0.2/go.mod h1:7T1IbSrg60ankme0aDLVnEY0h056g9M1/ZvpVThtB7E= github.com/gobuffalo/licenser v0.0.0-20180924033006-eae28e638a42/go.mod h1:Ubo90Np8gpsSZqNScZZkVXXAo5DGhTb+WYFIjlnog8w= github.com/gobuffalo/licenser v0.0.0-20181025145548-437d89de4f75/go.mod h1:x3lEpYxkRG/XtGCUNkio+6RZ/dlOvLzTI9M1auIwFcw= @@ -683,7 +681,6 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= @@ -747,7 +744,6 @@ github.com/markbates/deplist v1.1.3/go.mod h1:BF7ioVzAJYEtzQN/os4rt8H8Ti3h0T7EoN github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= github.com/markbates/going v1.0.2/go.mod h1:UWCk3zm0UKefHZ7l8BNqi26UyiEMniznk8naLdTcy6c= github.com/markbates/grift v1.0.4/go.mod h1:wbmtW74veyx+cgfwFhlnnMWqhoz55rnHR47oMXzsyVs= -github.com/markbates/hmax v1.0.0 h1:yo2N0gBoCnUMKhV/VRLHomT6Y9wUm+oQQENuWJqCdlM= github.com/markbates/hmax v1.0.0/go.mod h1:cOkR9dktiESxIMu+65oc/r/bdY4bE8zZw3OLhLx0X2c= github.com/markbates/inflect v1.0.0/go.mod h1:oTeZL2KHA7CUX6X+fovmK9OvIOFuqu0TwdQrZjLTh88= github.com/markbates/inflect v1.0.1/go.mod h1:uv3UVNBe5qBIfCm8O8Q+DW+S1EopeyINj+Ikhc7rnCk= @@ -1103,7 +1099,6 @@ github.com/unrolled/secure v0.0.0-20180918153822-f340ee86eb8b/go.mod h1:mnPT77IA github.com/unrolled/secure v0.0.0-20181005190816-ff9db2ff917f/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= diff --git a/httploader/httploader.go b/httploader/httploader.go index 59fd7dd..894fcc4 100644 --- a/httploader/httploader.go +++ b/httploader/httploader.go @@ -20,8 +20,6 @@ import ( "github.com/hashicorp/go-retryablehttp" - "github.com/ory/x/httpx" - "github.com/ory/jsonschema/v3" ) @@ -31,7 +29,7 @@ const ContextKey = "github.com/ory/jsonschema/v3/httploader.HTTPClient" func Load(ctx context.Context, url string) (io.ReadCloser, error) { var hc *retryablehttp.Client if v := ctx.Value(ContextKey); v == nil { - hc = httpx.NewResilientClient() + return nil, fmt.Errorf("expected a client to be set for %s but received nil", ContextKey) } else if c, ok := v.(*retryablehttp.Client); ok { hc = c } else { diff --git a/httploader/httploader_test.go b/httploader/httploader_test.go index df2e1da..9fe9da5 100644 --- a/httploader/httploader_test.go +++ b/httploader/httploader_test.go @@ -31,7 +31,7 @@ func TestHTTPLoader(t *testing.T) { t.Cleanup(ts.Close) mr := func(t *testing.T, ctx context.Context) string { - res, err := Load(context.Background(), ts.URL) + res, err := Load(context.WithValue(context.Background(), ContextKey, retryablehttp.NewClient()), ts.URL) require.NoError(t, err) defer res.Close() body, err := ioutil.ReadAll(res) diff --git a/schema_test.go b/schema_test.go index 4c66f6a..d1f8ff4 100644 --- a/schema_test.go +++ b/schema_test.go @@ -7,9 +7,10 @@ package jsonschema_test import ( "bytes" "context" - "crypto/tls" "encoding/json" "errors" + "github.com/hashicorp/go-retryablehttp" + "github.com/ory/jsonschema/v3/httploader" "io" "io/ioutil" "net/http" @@ -27,6 +28,8 @@ import ( var draft4, draft6, draft7 []byte +var ctx = context.WithValue(context.Background(), httploader.ContextKey, retryablehttp.NewClient()) + func init() { var err error draft4, err = ioutil.ReadFile("testdata/draft4.json") @@ -66,7 +69,6 @@ type testGroup struct { } func testFolder(t *testing.T, folder string, draft *jsonschema.Draft) { - ctx := context.Background() server := &http.Server{Addr: "localhost:1234", Handler: http.FileServer(http.Dir("testdata/remotes"))} go func() { if err := server.ListenAndServe(); err != http.ErrServerClosed { @@ -174,7 +176,6 @@ func testFolder(t *testing.T, folder string, draft *jsonschema.Draft) { } func TestInvalidSchema(t *testing.T) { - ctx := context.Background() t.Run("MustCompile with panic", func(t *testing.T) { defer func() { if r := recover(); r == nil { @@ -242,18 +243,14 @@ func TestInvalidSchema(t *testing.T) { } func TestCompileURL(t *testing.T) { - ctx := context.Background() - tr := http.DefaultTransport.(*http.Transport) - if tr.TLSClientConfig == nil { - tr.TLSClientConfig = &tls.Config{} - } - tr.TLSClientConfig.InsecureSkipVerify = true - handler := http.FileServer(http.Dir("testdata")) httpServer := httptest.NewServer(handler) defer httpServer.Close() httpsServer := httptest.NewTLSServer(handler) defer httpsServer.Close() + c := retryablehttp.NewClient() + c.HTTPClient = httpsServer.Client() + ctx := context.WithValue(context.Background(), httploader.ContextKey, c) validTests := []struct { schema, doc string @@ -301,8 +298,6 @@ func TestCompileURL(t *testing.T) { } func TestValidateInterface(t *testing.T) { - ctx := context.Background() - files := []string{ "testdata/draft4/type.json", "testdata/draft4/minimum.json", @@ -359,7 +354,6 @@ func TestValidateInterface(t *testing.T) { } func TestInvalidJsonTypeError(t *testing.T) { - ctx := context.Background() compiler := jsonschema.NewCompiler() err := compiler.AddResource("test.json", strings.NewReader(`{ "type": "string"}`)) if err != nil { @@ -380,7 +374,6 @@ func TestInvalidJsonTypeError(t *testing.T) { } func TestExtractAnnotations(t *testing.T) { - ctx := context.Background() t.Run("false", func(t *testing.T) { compiler := jsonschema.NewCompiler() @@ -467,7 +460,6 @@ func toFileURL(path string) string { // TestPanic tests https://github.com/ory/jsonschema/issues/18 func TestPanic(t *testing.T) { - ctx := context.Background() schema_d := ` { "type": "object", @@ -505,7 +497,6 @@ func TestPanic(t *testing.T) { } func TestNonStringFormat(t *testing.T) { - ctx := context.Background() jsonschema.Formats["even-number"] = func(v interface{}) bool { switch v := v.(type) { case json.Number: @@ -536,7 +527,6 @@ func TestNonStringFormat(t *testing.T) { } func TestCompiler_LoadURL(t *testing.T) { - ctx := context.Background() const ( base = `{ "type": "string" }` schema = `{ "allOf": [{ "$ref": "base.json" }, { "maxLength": 3 }] }` @@ -566,7 +556,6 @@ func TestCompiler_LoadURL(t *testing.T) { } func TestSchemaReferencesDrafts(t *testing.T) { - ctx := context.Background() c := jsonschema.NewCompiler() file := "testdata/reference_draft.json" t.Log(filepath.Base(file)) From fa85aea1f1ec16a70299d98d17d2be1f6edc59cc Mon Sep 17 00:00:00 2001 From: hackerman <3372410+aeneasr@users.noreply.github.com> Date: Fri, 21 Jan 2022 09:37:36 +0100 Subject: [PATCH 4/5] docs: clarify use case --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 0a07bfd..4b39169 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ [![CircleCI](https://circleci.com/gh/ory/jsonschema/tree/master.svg?style=badge)](https://circleci.com/gh/ory/jsonschema/tree/master) [![Coverage Status](https://coveralls.io/repos/github/ory/jsonschema/badge.svg?branch=master)](https://coveralls.io/github/ory/jsonschema?branch=master) +!!! WARNING !!! + +This library is used internally at Ory and subject to rapid change. + +!!! WARNING !!! + Package jsonschema provides json-schema compilation and validation. This implementation of JSON Schema, supports draft4, draft6 and draft7. From d2f6636cb9e19443a451754426e06556c28fa8d4 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Fri, 21 Jan 2022 15:34:02 +0100 Subject: [PATCH 5/5] feat: make context a dedicated type --- httploader/httploader.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/httploader/httploader.go b/httploader/httploader.go index 894fcc4..5eb0db6 100644 --- a/httploader/httploader.go +++ b/httploader/httploader.go @@ -23,7 +23,9 @@ import ( "github.com/ory/jsonschema/v3" ) -const ContextKey = "github.com/ory/jsonschema/v3/httploader.HTTPClient" +type key string + +const ContextKey key = "github.com/ory/jsonschema/v3/httploader.HTTPClient" // Load implements jsonschemav2.Loader func Load(ctx context.Context, url string) (io.ReadCloser, error) {