From 263a72d4da7308b45ee80f967d0b541ea49a07d0 Mon Sep 17 00:00:00 2001 From: b5 Date: Wed, 17 Jan 2018 08:19:22 -0500 Subject: [PATCH] feat: first pass of draft7 test suite passing I think this is a solid starting point. We still need to get a test server up & running as part of the suite, but a "fetchRemoteRef" method exists, so *in theory* it'll work. --- keywords_objects.go | 5 +- schema.go | 26 +-- schema_test.go | 196 +++++++++++---------- testdata/remotes/folder/folderInteger.json | 3 + testdata/remotes/integer.json | 3 + testdata/remotes/name.json | 11 ++ testdata/remotes/subSchemas.json | 8 + 7 files changed, 142 insertions(+), 110 deletions(-) create mode 100644 testdata/remotes/folder/folderInteger.json create mode 100644 testdata/remotes/integer.json create mode 100644 testdata/remotes/name.json create mode 100644 testdata/remotes/subSchemas.json diff --git a/keywords_objects.go b/keywords_objects.go index 641fd2f..adb40fd 100644 --- a/keywords_objects.go +++ b/keywords_objects.go @@ -258,9 +258,10 @@ type Dependencies map[string]Dependency func (d Dependencies) Validate(data interface{}) error { if obj, ok := data.(map[string]interface{}); ok { for key, val := range d { - // fmt.Println(key, obj[key]) if obj[key] != nil { - val.Validate(obj) + if err := val.Validate(obj); err != nil { + return err + } } } } diff --git a/schema.go b/schema.go index ed0e1dd..8b14792 100644 --- a/schema.go +++ b/schema.go @@ -13,7 +13,10 @@ import ( "net/url" ) -var SchemaPool = Definitions{} +// DefaultSchemaPool is a package level map of schemas by identifier +// remote references are cached here. +// TODO - should add methods to control caching behavior +var DefaultSchemaPool = Definitions{} // Validator is an interface for anything that can validate // JSON-Schema keywords are all validators @@ -121,18 +124,12 @@ func (rs *RootSchema) UnmarshalJSON(data []byte) error { return nil } -func (rs *RootSchema) Validate(data interface{}) error { - // fmt.Printf("%p <- root address. ref: %s. validators: %v\n", rs, rs.Ref, rs.Validators) - return rs.Schema.Validate(data) -} - // FetchRemoteReferences grabs any url-based schema references that cannot // be locally resolved via network requests func (rs *RootSchema) FetchRemoteReferences() error { sch := &rs.Schema - refs := SchemaPool - // refs := map[string]*Schema{} + refs := DefaultSchemaPool if err := walkJSON(sch, func(elem JSONPather) error { if sch, ok := elem.(*Schema); ok { @@ -140,7 +137,6 @@ func (rs *RootSchema) FetchRemoteReferences() error { if ref != "" { if refs[ref] == nil && ref[0] != '#' { if u, err := url.Parse(ref); err == nil { - fmt.Println("getting remote ref:", sch.Ref) if res, err := http.Get(u.String()); err == nil { s := &RootSchema{} if err := json.NewDecoder(res.Body).Decode(s); err != nil { @@ -152,7 +148,6 @@ func (rs *RootSchema) FetchRemoteReferences() error { } if refs[ref] != nil { - // fmt.Printf("%p - remote ptr ref -> %p\n", sch, refs[ref]) sch.ref = refs[ref] } } @@ -330,18 +325,11 @@ type Schema struct { // Validate uses the schema to check an instance, returning error on the first error func (s *Schema) Validate(data interface{}) error { - // fmt.Printf("%p, %s, %p, %#v, %v\n", s, s.Ref, s.ref, s, data) - // if s.Ref != "" && s.ref == nil { - // fmt.Println("pool:", s.Ref, SchemaPool[s.Ref].ref) - // } if s.Ref != "" && s.ref != nil { - // fmt.Println("using reference", s.Ref) return s.ref.Validate(data) + } else if s.Ref != "" && s.ref == nil { + return fmt.Errorf("%s reference is nil for data: %v", s.Ref, data) } - // if s.Ref != "" && s.ref == nil { - // panic(fmt.Sprintf("%s reference is nil for data: %v", s.Ref, data)) - // return fmt.Errorf("%s reference is nil for data: %v", s.Ref, data) - // } // TODO - so far all default.json tests pass when no use of "default" is made. // Is this correct? diff --git a/schema_test.go b/schema_test.go index 37af1ba..28d2468 100644 --- a/schema_test.go +++ b/schema_test.go @@ -40,24 +40,24 @@ func ExampleBasic() { } var valid = []byte(`{ - "firstName" : "Brendan", - "lastName" : "O'Brien" + "firstName" : "George", + "lastName" : "Michael" }`) if err := rs.ValidateBytes(valid); err != nil { panic(err) } var invalidPerson = []byte(`{ - "firstName" : "Brendan" + "firstName" : "Prince" }`) err := rs.ValidateBytes(invalidPerson) fmt.Println(err.Error()) var invalidFriend = []byte(`{ - "firstName" : "Brendan", - "lastName" : "O'Brien", + "firstName" : "Jay", + "lastName" : "Z", "friends" : [{ - "firstName" : "Margaux" + "firstName" : "Nas" }] }`) err = rs.ValidateBytes(invalidFriend) @@ -69,66 +69,81 @@ func ExampleBasic() { func TestDraft3(t *testing.T) { runJSONTests(t, []string{ - // "testdata/draft3/additionalItems.json", - // "testdata/draft3/disallow.json", - // "testdata/draft3/items.json", - // "testdata/draft3/minItems.json", - // "testdata/draft3/pattern.json", - // "testdata/draft3/refRemote.json", - // "testdata/draft3/additionalProperties.json", - // "testdata/draft3/divisibleBy.json", - // "testdata/draft3/maxItems.json", - // "testdata/draft3/minLength.json", - // "testdata/draft3/patternProperties.json", - // "testdata/draft3/required.json", - // "testdata/draft3/default.json", - // "testdata/draft3/enum.json", - // "testdata/draft3/maxLength.json", - // "testdata/draft3/minimum.json", - // "testdata/draft3/properties.json", - // "testdata/draft3/type.json", - // "testdata/draft3/dependencies.json", - // "testdata/draft3/extends.json", - // "testdata/draft3/maximum.json", - // "testdata/draft3/ref.json", - // "testdata/draft3/uniqueItems.json", - // "testdata/draft3/optional/bignum.json", - // "testdata/draft3/optional/format.json", - // "testdata/draft3/optional/jsregex.json", - // "testdata/draft3/optional/zeroTerminatedFloats.json", + "testdata/draft3/additionalItems.json", + // TODO - not implemented: + // "testdata/draft3/disallow.json", + "testdata/draft3/items.json", + "testdata/draft3/minItems.json", + "testdata/draft3/pattern.json", + // "testdata/draft3/refRemote.json", + "testdata/draft3/additionalProperties.json", + // TODO - not implemented: + // "testdata/draft3/divisibleBy.json", + "testdata/draft3/maxItems.json", + "testdata/draft3/minLength.json", + "testdata/draft3/patternProperties.json", + // TODO - currently doesn't parse: + // "testdata/draft3/required.json", + "testdata/draft3/default.json", + // TODO - currently doesn't parse: + // "testdata/draft3/enum.json", + "testdata/draft3/maxLength.json", + // TODO - currently doesn't parse: + // "testdata/draft3/minimum.json", + "testdata/draft3/properties.json", + // TODO - currently doesn't parse: + // "testdata/draft3/type.json", + // TODO - currently doesn't parse: + // "testdata/draft3/dependencies.json", + // TODO - currently doesn't parse: + // "testdata/draft3/extends.json", + // TODO - currently doesn't parse: + // "testdata/draft3/maximum.json", + // TODO - currently doesn't parse: + // "testdata/draft3/ref.json", + "testdata/draft3/uniqueItems.json", + // "testdata/draft3/optional/bignum.json", + // "testdata/draft3/optional/format.json", + // "testdata/draft3/optional/jsregex.json", + // "testdata/draft3/optional/zeroTerminatedFloats.json", }) } func TestDraft4(t *testing.T) { runJSONTests(t, []string{ - // "testdata/draft4/additionalItems.json", + "testdata/draft4/additionalItems.json", + // TODO - currently doesn't parse: // "testdata/draft4/definitions.json", - // "testdata/draft4/maxLength.json", - // "testdata/draft4/minProperties.json", + "testdata/draft4/maxLength.json", + "testdata/draft4/minProperties.json", // "testdata/draft4/refRemote.json", - // "testdata/draft4/additionalProperties.json", - // "testdata/draft4/dependencies.json", - // "testdata/draft4/maxProperties.json", + "testdata/draft4/additionalProperties.json", + "testdata/draft4/dependencies.json", + "testdata/draft4/maxProperties.json", + // TODO - currently doesn't parse: // "testdata/draft4/minimum.json", - // "testdata/draft4/pattern.json", - // "testdata/draft4/required.json", - // "testdata/draft4/allOf.json", - // "testdata/draft4/enum.json", + "testdata/draft4/pattern.json", + "testdata/draft4/required.json", + "testdata/draft4/allOf.json", + "testdata/draft4/enum.json", + // TODO - currently doesn't parse: // "testdata/draft4/maximum.json", - // "testdata/draft4/multipleOf.json", - // "testdata/draft4/patternProperties.json", + "testdata/draft4/multipleOf.json", + "testdata/draft4/patternProperties.json", "testdata/draft4/type.json", - // "testdata/draft4/anyOf.json", - // "testdata/draft4/items.json", - // "testdata/draft4/minItems.json", - // "testdata/draft4/not.json", - // "testdata/draft4/properties.json", - // "testdata/draft4/uniqueItems.json", - // "testdata/draft4/default.json", - // "testdata/draft4/maxItems.json", - // "testdata/draft4/minLength.json", - // "testdata/draft4/oneOf.json", + "testdata/draft4/anyOf.json", + "testdata/draft4/items.json", + "testdata/draft4/minItems.json", + "testdata/draft4/not.json", + "testdata/draft4/properties.json", + "testdata/draft4/uniqueItems.json", + "testdata/draft4/default.json", + "testdata/draft4/maxItems.json", + "testdata/draft4/minLength.json", + "testdata/draft4/oneOf.json", + // TODO - currently doesn't parse: // "testdata/draft4/ref.json", + // "testdata/draft4/optional/bignum.json", // "testdata/draft4/optional/ecmascript-regex.json", // "testdata/draft4/optional/format.json", @@ -138,40 +153,41 @@ func TestDraft4(t *testing.T) { func TestDraft6(t *testing.T) { runJSONTests(t, []string{ - // "testdata/draft6/additionalItems.json", + "testdata/draft6/additionalItems.json", "testdata/draft6/const.json", "testdata/draft6/enum.json", - // "testdata/draft6/maxLength.json", - // "testdata/draft6/minProperties.json", - // "testdata/draft6/ref.json", - // "testdata/draft6/additionalProperties.json", - // "testdata/draft6/contains.json", - // "testdata/draft6/exclusiveMaximum.json", - // "testdata/draft6/maxProperties.json", - // "testdata/draft6/minimum.json", - // "testdata/draft6/pattern.json", + "testdata/draft6/maxLength.json", + "testdata/draft6/minProperties.json", + "testdata/draft6/ref.json", + "testdata/draft6/additionalProperties.json", + "testdata/draft6/contains.json", + "testdata/draft6/exclusiveMaximum.json", + "testdata/draft6/maxProperties.json", + "testdata/draft6/minimum.json", + "testdata/draft6/pattern.json", // "testdata/draft6/refRemote.json", - // "testdata/draft6/allOf.json", - // "testdata/draft6/default.json", - // "testdata/draft6/exclusiveMinimum.json", - // "testdata/draft6/maximum.json", - // "testdata/draft6/multipleOf.json", - // "testdata/draft6/patternProperties.json", - // "testdata/draft6/required.json", - // "testdata/draft6/anyOf.json", - // "testdata/draft6/definitions.json", - // "testdata/draft6/items.json", - // "testdata/draft6/minItems.json", - // "testdata/draft6/not.json", - // "testdata/draft6/properties.json", + "testdata/draft6/allOf.json", + "testdata/draft6/default.json", + "testdata/draft6/exclusiveMinimum.json", + "testdata/draft6/maximum.json", + "testdata/draft6/multipleOf.json", + "testdata/draft6/patternProperties.json", + "testdata/draft6/required.json", + "testdata/draft6/anyOf.json", + "testdata/draft6/definitions.json", + "testdata/draft6/items.json", + "testdata/draft6/minItems.json", + "testdata/draft6/not.json", + "testdata/draft6/properties.json", "testdata/draft6/type.json", - // "testdata/draft6/boolean_schema.json", - // "testdata/draft6/dependencies.json", - // "testdata/draft6/maxItems.json", - // "testdata/draft6/minLength.json", - // "testdata/draft6/oneOf.json", - // "testdata/draft6/propertyNames.json", - // "testdata/draft6/uniqueItems.json", + "testdata/draft6/boolean_schema.json", + "testdata/draft6/dependencies.json", + "testdata/draft6/maxItems.json", + "testdata/draft6/minLength.json", + "testdata/draft6/oneOf.json", + "testdata/draft6/propertyNames.json", + "testdata/draft6/uniqueItems.json", + // "testdata/draft6/optional/bignum.json", // "testdata/draft6/optional/ecmascript-regex.json", // "testdata/draft6/optional/format.json", @@ -180,8 +196,8 @@ func TestDraft6(t *testing.T) { } func TestDraft7(t *testing.T) { - prev := SchemaPool - defer func() { SchemaPool = prev }() + prev := DefaultSchemaPool + defer func() { DefaultSchemaPool = prev }() path := "testdata/draft-07_schema.json" data, err := ioutil.ReadFile(path) @@ -196,7 +212,7 @@ func TestDraft7(t *testing.T) { return } - SchemaPool["http://json-schema.org/draft-07/schema#"] = &rsch.Schema + DefaultSchemaPool["http://json-schema.org/draft-07/schema#"] = &rsch.Schema runJSONTests(t, []string{ "testdata/draft7/additionalItems.json", @@ -217,7 +233,7 @@ func TestDraft7(t *testing.T) { "testdata/draft7/minLength.json", // "testdata/draft7/refRemote.json", "testdata/draft7/anyOf.json", - // "testdata/draft7/dependencies.json", + "testdata/draft7/dependencies.json", "testdata/draft7/maxItems.json", "testdata/draft7/minProperties.json", "testdata/draft7/pattern.json", @@ -334,6 +350,8 @@ func TestDataType(t *testing.T) { } } +// TODO - finish remoteRef.json tests by setting up a httptest server on localhost:1234 +// that uses an http.Dir to serve up testdata/remotes directory // func testServer() { // s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/testdata/remotes/folder/folderInteger.json b/testdata/remotes/folder/folderInteger.json new file mode 100644 index 0000000..dbe5c75 --- /dev/null +++ b/testdata/remotes/folder/folderInteger.json @@ -0,0 +1,3 @@ +{ + "type": "integer" +} \ No newline at end of file diff --git a/testdata/remotes/integer.json b/testdata/remotes/integer.json new file mode 100644 index 0000000..dbe5c75 --- /dev/null +++ b/testdata/remotes/integer.json @@ -0,0 +1,3 @@ +{ + "type": "integer" +} \ No newline at end of file diff --git a/testdata/remotes/name.json b/testdata/remotes/name.json new file mode 100644 index 0000000..19ba093 --- /dev/null +++ b/testdata/remotes/name.json @@ -0,0 +1,11 @@ +{ + "definitions": { + "orNull": { + "anyOf": [ + {"type": "null"}, + {"$ref": "#"} + ] + } + }, + "type": "string" +} diff --git a/testdata/remotes/subSchemas.json b/testdata/remotes/subSchemas.json new file mode 100644 index 0000000..8b6d8f8 --- /dev/null +++ b/testdata/remotes/subSchemas.json @@ -0,0 +1,8 @@ +{ + "integer": { + "type": "integer" + }, + "refToInteger": { + "$ref": "#/integer" + } +} \ No newline at end of file