Skip to content

Commit 1326056

Browse files
authored
basic support to test against official JSON-Schema-Test-Suite (#122)
This PR will add basic support to test against the official https://github.com/json-schema-org/JSON-Schema-Test-Suite. It contains only tests for the `required` feature.
1 parent a5c9e7a commit 1326056

File tree

13 files changed

+441
-4
lines changed

13 files changed

+441
-4
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,6 @@ yarn.lock
4444

4545
# vim swap files
4646
*.swp
47+
48+
# vscode
49+
.vscode

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
language: node_js
22
sudo: false
33
node_js:
4-
- '4'
54
- '6'
65
- '8'
7-
- '9'
86
- '10'
97

108
notifications:

index.js

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ function build (schema, options) {
6262
`
6363
}
6464

65+
if (schema.type === undefined) {
66+
schema.type = inferTypeByKeyword(schema)
67+
}
68+
6569
var hasSchemaSomeIf = hasIf(schema)
6670

6771
var main
@@ -114,6 +118,59 @@ function build (schema, options) {
114118
return (Function.apply(null, dependenciesName).apply(null, dependencies))
115119
}
116120

121+
const objectKeywords = [
122+
'maxProperties',
123+
'minProperties',
124+
'required',
125+
'properties',
126+
'patternProperties',
127+
'additionalProperties',
128+
'dependencies'
129+
]
130+
131+
const arrayKeywords = [
132+
'items',
133+
'additionalItems',
134+
'maxItems',
135+
'minItems',
136+
'uniqueItems',
137+
'contains'
138+
]
139+
140+
const stringKeywords = [
141+
'maxLength',
142+
'minLength',
143+
'pattern'
144+
]
145+
146+
const numberKeywords = [
147+
'multipleOf',
148+
'maximum',
149+
'exclusiveMaximum',
150+
'minimum',
151+
'exclusiveMinimum'
152+
]
153+
154+
/**
155+
* Infer type based on keyword in order to generate optimized code
156+
* https://json-schema.org/latest/json-schema-validation.html#rfc.section.6
157+
*/
158+
function inferTypeByKeyword (schema) {
159+
for (const keyword of objectKeywords) {
160+
if (keyword in schema) return 'object'
161+
}
162+
for (const keyword of arrayKeywords) {
163+
if (keyword in schema) return 'array'
164+
}
165+
for (const keyword of stringKeywords) {
166+
if (keyword in schema) return 'string'
167+
}
168+
for (const keyword of numberKeywords) {
169+
if (keyword in schema) return 'number'
170+
}
171+
return schema.type
172+
}
173+
117174
function hasAnyOf (schema) {
118175
if ('anyOf' in schema) { return true }
119176

@@ -694,7 +751,16 @@ function buildArrayTypeCondition (type, accessor) {
694751
function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKey) {
695752
var code = ''
696753
var funcName
754+
755+
if (schema.type === undefined) {
756+
var inferedType = inferTypeByKeyword(schema)
757+
if (inferedType) {
758+
schema.type = inferedType
759+
}
760+
}
761+
697762
var type = schema.type
763+
698764
var accessor = key.indexOf('[') === 0 ? key : `['${key}']`
699765
switch (type) {
700766
case 'null':
@@ -754,7 +820,7 @@ function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKe
754820
json += JSON.stringify(obj${accessor})
755821
`
756822
} else {
757-
throw new Error(`${schema} unsupported`)
823+
throw new Error(`${schema.type} unsupported`)
758824
}
759825
break
760826
default:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"scripts": {
77
"benchmark": "node bench.js",
88
"lint": "standard",
9-
"unit": "tap -j4 test/*.test.js",
9+
"unit": "tap -j4 test/**/*.test.js",
1010
"test": "npm run lint && npm run unit"
1111
},
1212
"precommit": "test",

test/inferType.test.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
'use strict'
2+
3+
const test = require('tap').test
4+
const validator = require('is-my-json-valid')
5+
const build = require('..')
6+
7+
function buildTest (schema, toStringify) {
8+
test(`render a ${schema.title} as JSON`, (t) => {
9+
t.plan(5)
10+
11+
const validate = validator(schema)
12+
const stringify = build(schema)
13+
const stringifyUgly = build(schema, { uglify: true })
14+
const output = stringify(toStringify)
15+
const outputUglify = stringifyUgly(toStringify)
16+
17+
t.deepEqual(JSON.parse(output), toStringify)
18+
t.deepEqual(JSON.parse(outputUglify), toStringify)
19+
t.equal(output, JSON.stringify(toStringify))
20+
t.equal(outputUglify, JSON.stringify(toStringify))
21+
t.ok(validate(JSON.parse(output)), 'valid schema')
22+
})
23+
}
24+
25+
buildTest({
26+
'title': 'infer type object by keyword',
27+
// 'type': 'object',
28+
'properties': {
29+
'name': {
30+
'type': 'string'
31+
}
32+
}
33+
}, {
34+
name: 'foo'
35+
})
36+
37+
buildTest({
38+
'title': 'infer type of nested object by keyword',
39+
// 'type': 'object',
40+
'properties': {
41+
'more': {
42+
'description': 'more properties',
43+
// 'type': 'object',
44+
'properties': {
45+
'something': {
46+
'type': 'string'
47+
}
48+
}
49+
}
50+
}
51+
}, {
52+
more: {
53+
something: 'else'
54+
}
55+
})
56+
57+
buildTest({
58+
'title': 'infer type array by keyword',
59+
'type': 'object',
60+
'properties': {
61+
'ids': {
62+
// 'type': 'array',
63+
'items': {
64+
'type': 'string'
65+
}
66+
}
67+
}
68+
}, {
69+
ids: ['test']
70+
})
71+
72+
buildTest({
73+
'title': 'infer type string by keyword',
74+
'type': 'object',
75+
'properties': {
76+
'name': {
77+
// 'type': 'string',
78+
'maxLength': 3
79+
}
80+
}
81+
}, {
82+
name: 'foo'
83+
})
84+
85+
buildTest({
86+
'title': 'infer type number by keyword',
87+
'type': 'object',
88+
'properties': {
89+
'age': {
90+
// 'type': 'number',
91+
'maximum': 18
92+
}
93+
}
94+
}, {
95+
age: 18
96+
})

test/json-schema-test-suite/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# JSON-Schema-Test-Suite
2+
3+
You can find all test cases [here](https://github.com/json-schema-org/JSON-Schema-Test-Suite).
4+
It contains a set of JSON objects that implementors of JSON Schema validation libraries can use to test their validators.
5+
6+
# How to add another test case?
7+
8+
1. Navigate to [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/master/tests)
9+
2. Choose a draft `draft4`, `draft6` or `draft7`
10+
3. Copy & paste the `test-case.json` to the project and add a test like in the `draft4.test.js`
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict'
2+
3+
const test = require('tap').test
4+
const { counTests, runTests } = require('./util')
5+
6+
const requiredTestSuite = require('./draft4/required.json')
7+
8+
test('required', (t) => {
9+
const skippedTests = ['ignores arrays', 'ignores strings', 'ignores other non-objects']
10+
t.plan(counTests(requiredTestSuite, skippedTests))
11+
runTests(t, requiredTestSuite, skippedTests)
12+
})
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
[
2+
{
3+
"description": "required validation",
4+
"schema": {
5+
"properties": {
6+
"foo": {},
7+
"bar": {}
8+
},
9+
"required": ["foo"]
10+
},
11+
"tests": [
12+
{
13+
"description": "present required property is valid",
14+
"data": {"foo": 1},
15+
"valid": true
16+
},
17+
{
18+
"description": "non-present required property is invalid",
19+
"data": {"bar": 1},
20+
"valid": false
21+
},
22+
{
23+
"description": "ignores arrays",
24+
"data": [],
25+
"valid": true
26+
},
27+
{
28+
"description": "ignores strings",
29+
"data": "",
30+
"valid": true
31+
},
32+
{
33+
"description": "ignores other non-objects",
34+
"data": 12,
35+
"valid": true
36+
}
37+
]
38+
},
39+
{
40+
"description": "required default validation",
41+
"schema": {
42+
"properties": {
43+
"foo": {}
44+
}
45+
},
46+
"tests": [
47+
{
48+
"description": "not required by default",
49+
"data": {},
50+
"valid": true
51+
}
52+
]
53+
}
54+
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict'
2+
3+
const test = require('tap').test
4+
const { counTests, runTests } = require('./util')
5+
6+
const requiredTestSuite = require('./draft6/required.json')
7+
8+
test('required', (t) => {
9+
const skippedTests = ['ignores arrays', 'ignores strings', 'ignores other non-objects']
10+
t.plan(counTests(requiredTestSuite, skippedTests))
11+
runTests(t, requiredTestSuite, skippedTests)
12+
})
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
[
2+
{
3+
"description": "required validation",
4+
"schema": {
5+
"properties": {
6+
"foo": {},
7+
"bar": {}
8+
},
9+
"required": ["foo"]
10+
},
11+
"tests": [
12+
{
13+
"description": "present required property is valid",
14+
"data": {"foo": 1},
15+
"valid": true
16+
},
17+
{
18+
"description": "non-present required property is invalid",
19+
"data": {"bar": 1},
20+
"valid": false
21+
},
22+
{
23+
"description": "ignores arrays",
24+
"data": [],
25+
"valid": true
26+
},
27+
{
28+
"description": "ignores strings",
29+
"data": "",
30+
"valid": true
31+
},
32+
{
33+
"description": "ignores other non-objects",
34+
"data": 12,
35+
"valid": true
36+
}
37+
]
38+
},
39+
{
40+
"description": "required default validation",
41+
"schema": {
42+
"properties": {
43+
"foo": {}
44+
}
45+
},
46+
"tests": [
47+
{
48+
"description": "not required by default",
49+
"data": {},
50+
"valid": true
51+
}
52+
]
53+
},
54+
{
55+
"description": "required with empty array",
56+
"schema": {
57+
"properties": {
58+
"foo": {}
59+
},
60+
"required": []
61+
},
62+
"tests": [
63+
{
64+
"description": "property not required",
65+
"data": {},
66+
"valid": true
67+
}
68+
]
69+
}
70+
]

0 commit comments

Comments
 (0)