Skip to content

Commit 1272774

Browse files
committed
feat: multi-entrypoint compilation
Adds a new `{ multi : true }` option to both validator() and parser(), which allows comiling multiple schemas at once into a single module. Closes: #140
1 parent c9c0b34 commit 1272774

File tree

2 files changed

+205
-7
lines changed

2 files changed

+205
-7
lines changed

src/index.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,38 @@ const jsonCheckWithErrors = (validate) =>
1919
const jsonCheckWithoutErrors = (validate) => (data) =>
2020
deepEqual(data, JSON.parse(JSON.stringify(data))) && validate(data)
2121

22-
const validator = (schema, rawOpts = {}) => {
23-
const { parse = false, jsonCheck = false, isJSON = false, schemas = [], ...opts } = rawOpts
22+
const validator = (
23+
schema,
24+
{ parse = false, multi = false, jsonCheck = false, isJSON = false, schemas = [], ...opts } = {}
25+
) => {
2426
if (jsonCheck && isJSON) throw new Error('Can not specify both isJSON and jsonCheck options')
2527
if (parse && (jsonCheck || isJSON))
2628
throw new Error('jsonCheck and isJSON options are not applicable in parser mode')
2729
const mode = parse ? 'strong' : 'default' // strong mode is default in parser, can be overriden
2830
const willJSON = isJSON || jsonCheck || parse
29-
const options = { mode, ...opts, schemas: buildSchemas(schemas, [schema]), isJSON: willJSON }
30-
const { scope, refs } = compile([schema], options) // only a single ref
31+
const arg = multi ? schema : [schema]
32+
const options = { mode, ...opts, schemas: buildSchemas(schemas, arg), isJSON: willJSON }
33+
const { scope, refs } = compile(arg, options) // only a single ref
3134
if (opts.dryRun) return
3235
const fun = genfun()
3336
if (parse) {
3437
scope.parseWrap = opts.includeErrors ? parseWithErrors : parseWithoutErrors
35-
fun.write('parseWrap(%s)', refs[0])
3638
} else if (jsonCheck) {
3739
scope.deepEqual = deepEqual
3840
scope.jsonCheckWrap = opts.includeErrors ? jsonCheckWithErrors : jsonCheckWithoutErrors
39-
fun.write('jsonCheckWrap(%s)', refs[0])
40-
} else fun.write('%s', refs[0])
41+
}
42+
if (multi) {
43+
fun.write('[')
44+
for (const ref of refs.slice(0, -1)) fun.write('%s,', ref)
45+
if (refs.length > 0) fun.write('%s', refs[refs.length - 1])
46+
fun.write(']')
47+
if (parse) fun.write('.map(parseWrap)')
48+
else if (jsonCheck) fun.write('.map(jsonCheckWrap)')
49+
} else {
50+
if (parse) fun.write('parseWrap(%s)', refs[0])
51+
else if (jsonCheck) fun.write('jsonCheckWrap(%s)', refs[0])
52+
else fun.write('%s', refs[0])
53+
}
4154
const validate = fun.makeFunction(scope)
4255
validate.toModule = ({ semi = true } = {}) => fun.makeModule(scope) + (semi ? ';' : '')
4356
validate.toJSON = () => schema

test/multi.js

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
'use strict'
2+
3+
const tape = require('tape')
4+
const { validator, parser } = require('../')
5+
6+
const $schema = 'https://json-schema.org/draft/2020-12/schema#'
7+
8+
const schemas1 = [{ $schema, type: 'number' }]
9+
const schemas3 = [
10+
{
11+
$id: 'https://example.com/a',
12+
type: 'object',
13+
properties: {
14+
value: { $ref: 'https://example.com/b' },
15+
},
16+
},
17+
{
18+
$id: 'https://example.com/b',
19+
type: 'array',
20+
items: { $ref: 'https://example.com/a' },
21+
},
22+
{ type: 'number' },
23+
]
24+
25+
tape('multi, 0 schemas, validator', (t) => {
26+
const validates = validator([], { multi: true })
27+
t.strictEqual(validates.length, 0)
28+
t.end()
29+
})
30+
31+
tape('multi, 0 schemas, parser', (t) => {
32+
const parses = parser([], { multi: true })
33+
t.strictEqual(parses.length, 0)
34+
t.end()
35+
})
36+
37+
tape('multi, 1 schemas, validator', (t) => {
38+
const validates = validator(schemas1, { multi: true })
39+
t.strictEqual(validates.length, 1)
40+
41+
t.ok(validates[0](1))
42+
t.strictEqual(validates[0].errors, undefined)
43+
t.notOk(validates[0](''))
44+
t.strictEqual(validates[0].errors, undefined)
45+
46+
t.end()
47+
})
48+
49+
tape('multi, 1 schemas, validator, errors', (t) => {
50+
const validates = validator(schemas1, { multi: true, includeErrors: true })
51+
t.strictEqual(validates.length, 1)
52+
53+
t.ok(validates[0](1))
54+
t.strictEqual(validates[0].errors, null)
55+
t.notOk(validates[0](''))
56+
t.deepEqual(validates[0].errors, [{ keywordLocation: '#/type', instanceLocation: '#' }])
57+
58+
t.end()
59+
})
60+
61+
tape('multi, 1 schemas, parser', (t) => {
62+
const parses = parser(schemas1, { multi: true })
63+
t.strictEqual(parses.length, 1)
64+
65+
t.deepEqual(parses[0]('1'), { valid: true, value: 1 })
66+
t.deepEqual(parses[0]('""'), { valid: false })
67+
t.deepEqual(parses[0]('"'), { valid: false })
68+
69+
t.end()
70+
})
71+
72+
tape('multi, 1 schemas, parser, errors', (t) => {
73+
const parses = parser(schemas1, { multi: true, includeErrors: true })
74+
t.strictEqual(parses.length, 1)
75+
76+
t.deepEqual(parses[0]('1'), { valid: true, value: 1 })
77+
t.deepEqual(parses[0]('""'), {
78+
valid: false,
79+
error: 'JSON validation failed for type at #',
80+
errors: [{ keywordLocation: '#/type', instanceLocation: '#' }],
81+
})
82+
t.deepEqual(parses[0]('"'), { valid: false, error: 'Unexpected end of JSON input' })
83+
84+
t.end()
85+
})
86+
87+
tape('multi, 3 schemas, validator', (t) => {
88+
const validates = validator(schemas3, { multi: true })
89+
t.strictEqual(validates.length, 3)
90+
91+
t.ok(validates[0]({ value: [] }))
92+
t.strictEqual(validates[2].errors, undefined)
93+
t.notOk(validates[0]({ value: 10 }))
94+
t.strictEqual(validates[2].errors, undefined)
95+
96+
t.ok(validates[1]([{ value: [] }]))
97+
t.strictEqual(validates[2].errors, undefined)
98+
t.notOk(validates[1]([{ value: 10 }]))
99+
t.strictEqual(validates[2].errors, undefined)
100+
101+
t.ok(validates[2](1))
102+
t.strictEqual(validates[2].errors, undefined)
103+
t.notOk(validates[2](''))
104+
t.strictEqual(validates[2].errors, undefined)
105+
106+
t.end()
107+
})
108+
109+
tape('multi, 3 schemas, validator, errors', (t) => {
110+
const validates = validator(schemas3, { multi: true, includeErrors: true })
111+
t.strictEqual(validates.length, 3)
112+
113+
t.ok(validates[0]({ value: [] }))
114+
t.strictEqual(validates[0].errors, null)
115+
t.notOk(validates[0]({ value: 10 }))
116+
t.deepEqual(validates[0].errors, [
117+
{ keywordLocation: '#/properties/value/$ref/type', instanceLocation: '#/value' },
118+
])
119+
120+
t.ok(validates[1]([{ value: [] }]))
121+
t.strictEqual(validates[1].errors, null)
122+
t.notOk(validates[1]([{ value: 10 }]))
123+
t.deepEqual(validates[1].errors, [
124+
{ keywordLocation: '#/items/$ref/properties/value/$ref/type', instanceLocation: '#/0/value' },
125+
])
126+
127+
t.ok(validates[2](1))
128+
t.strictEqual(validates[2].errors, null)
129+
t.notOk(validates[2](''))
130+
t.deepEqual(validates[2].errors, [{ keywordLocation: '#/type', instanceLocation: '#' }])
131+
132+
t.end()
133+
})
134+
135+
tape('multi, 3 schemas, parser', (t) => {
136+
const parses = parser(schemas3, { multi: true, mode: 'default' })
137+
t.strictEqual(parses.length, 3)
138+
139+
t.deepEqual(parses[0]('{"value":[]}'), { valid: true, value: { value: [] } })
140+
t.deepEqual(parses[0]('{"value":10}'), { valid: false })
141+
t.deepEqual(parses[0]('"'), { valid: false })
142+
143+
t.deepEqual(parses[1]('[{"value":[]}]'), { valid: true, value: [{ value: [] }] })
144+
t.deepEqual(parses[1]('[{"value":10}]'), { valid: false })
145+
t.deepEqual(parses[1]('"'), { valid: false })
146+
147+
t.deepEqual(parses[2]('1'), { valid: true, value: 1 })
148+
t.deepEqual(parses[2]('""'), { valid: false })
149+
t.deepEqual(parses[2]('"'), { valid: false })
150+
151+
t.end()
152+
})
153+
154+
tape('multi, 3 schemas, parser, errors', (t) => {
155+
const parses = parser(schemas3, { multi: true, mode: 'default', includeErrors: true })
156+
t.strictEqual(parses.length, 3)
157+
158+
t.deepEqual(parses[0]('{"value":[]}'), { valid: true, value: { value: [] } })
159+
t.deepEqual(parses[0]('{"value":10}'), {
160+
valid: false,
161+
error: 'JSON validation failed for type at #/value',
162+
errors: [{ keywordLocation: '#/properties/value/$ref/type', instanceLocation: '#/value' }],
163+
})
164+
t.deepEqual(parses[0]('"'), { valid: false, error: 'Unexpected end of JSON input' })
165+
166+
t.deepEqual(parses[1]('[{"value":[]}]'), { valid: true, value: [{ value: [] }] })
167+
t.deepEqual(parses[1]('[{"value":10}]'), {
168+
valid: false,
169+
error: 'JSON validation failed for type at #/0/value',
170+
errors: [
171+
{ keywordLocation: '#/items/$ref/properties/value/$ref/type', instanceLocation: '#/0/value' },
172+
],
173+
})
174+
t.deepEqual(parses[1]('"'), { valid: false, error: 'Unexpected end of JSON input' })
175+
176+
t.deepEqual(parses[2]('1'), { valid: true, value: 1 })
177+
t.deepEqual(parses[2]('""'), {
178+
valid: false,
179+
error: 'JSON validation failed for type at #',
180+
errors: [{ keywordLocation: '#/type', instanceLocation: '#' }],
181+
})
182+
t.deepEqual(parses[2]('"'), { valid: false, error: 'Unexpected end of JSON input' })
183+
184+
t.end()
185+
})

0 commit comments

Comments
 (0)