fast-json-stringify 7.0.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/dependabot.yml +43 -2
- package/.github/workflows/ci.yml +1 -1
- package/.github/workflows/lock-threads.yml +1 -1
- package/.github/workflows/release.yml +24 -0
- package/README.md +25 -8
- package/benchmark/bench-thread.js +6 -2
- package/benchmark/bench.js +86 -0
- package/index.js +360 -94
- package/lib/location.js +3 -1
- package/lib/serializer.js +41 -37
- package/lib/standalone.js +22 -3
- package/lib/validator.js +22 -3
- package/package.json +6 -7
- package/test/additionalProperties.test.js +87 -0
- package/test/anyof.test.js +42 -0
- package/test/array.test.js +1 -1
- package/test/basic.test.js +1 -1
- package/test/code-generation-fallbacks.test.js +158 -0
- package/test/compile-validators.test.js +110 -0
- package/test/const.test.js +27 -6
- package/test/date.test.js +28 -1
- package/test/debug-mode.test.js +8 -0
- package/test/deopt.test.js +251 -0
- package/test/if-then-else.test.js +145 -0
- package/test/inferType.test.js +1 -1
- package/test/integer.test.js +1 -1
- package/test/oneof.test.js +242 -0
- package/test/optional-property-prefix.test.js +119 -0
- package/test/patternProperties.test.js +24 -0
- package/test/property-names.test.js +41 -0
- package/test/regex.test.js +1 -1
- package/test/schema-depth.test.js +227 -0
- package/test/standalone-mode.test.js +109 -2
- package/test/string.test.js +8 -0
- package/test/surrogate.test.js +1 -1
- package/test/typesArray.test.js +59 -0
- package/test/utils/validator.js +20 -0
- package/types/index.d.ts +20 -0
- package/types/index.tst.ts +13 -0
package/test/const.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { test } = require('node:test')
|
|
4
|
-
const validator = require('
|
|
4
|
+
const validator = require('./utils/validator')
|
|
5
5
|
const build = require('..')
|
|
6
6
|
|
|
7
7
|
test('schema with const string', (t) => {
|
|
@@ -24,6 +24,26 @@ test('schema with const string', (t) => {
|
|
|
24
24
|
t.assert.ok(validate(JSON.parse(output)), 'valid schema')
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
+
test('schema with const string containing characters that need escaping', (t) => {
|
|
28
|
+
const values = ['back\\slash', 'tab\there', 'quote"here', 'new\nline', "tick's"]
|
|
29
|
+
t.plan(values.length * 2)
|
|
30
|
+
|
|
31
|
+
for (const value of values) {
|
|
32
|
+
const schema = {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
foo: { const: value }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const stringify = build(schema)
|
|
40
|
+
const output = stringify({ foo: value })
|
|
41
|
+
|
|
42
|
+
t.assert.equal(output, JSON.stringify({ foo: value }))
|
|
43
|
+
t.assert.deepStrictEqual(JSON.parse(output), { foo: value })
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
27
47
|
test('schema with const string and different input', (t) => {
|
|
28
48
|
t.plan(2)
|
|
29
49
|
|
|
@@ -259,7 +279,9 @@ test('schema with const and null as type', (t) => {
|
|
|
259
279
|
})
|
|
260
280
|
|
|
261
281
|
t.assert.equal(output, '{"foo":null}')
|
|
262
|
-
|
|
282
|
+
// `const` applies regardless of `type` in JSON Schema, so ajv rejects null
|
|
283
|
+
// here even though the serializer lets it through because the type allows it.
|
|
284
|
+
t.assert.equal(validate(JSON.parse(output)), false)
|
|
263
285
|
|
|
264
286
|
const output2 = stringify({ foo: 'baz' })
|
|
265
287
|
t.assert.equal(output2, '{"foo":"baz"}')
|
|
@@ -267,7 +289,7 @@ test('schema with const and null as type', (t) => {
|
|
|
267
289
|
})
|
|
268
290
|
|
|
269
291
|
test('schema with const as nullable', (t) => {
|
|
270
|
-
t.plan(
|
|
292
|
+
t.plan(2)
|
|
271
293
|
|
|
272
294
|
const schema = {
|
|
273
295
|
type: 'object',
|
|
@@ -276,20 +298,19 @@ test('schema with const as nullable', (t) => {
|
|
|
276
298
|
}
|
|
277
299
|
}
|
|
278
300
|
|
|
279
|
-
|
|
301
|
+
// ajv refuses to compile `nullable` without a `type`, so the output is not
|
|
302
|
+
// validated against the schema in this test.
|
|
280
303
|
const stringify = build(schema)
|
|
281
304
|
const output = stringify({
|
|
282
305
|
foo: null
|
|
283
306
|
})
|
|
284
307
|
|
|
285
308
|
t.assert.equal(output, '{"foo":null}')
|
|
286
|
-
t.assert.ok(validate(JSON.parse(output)), 'valid schema')
|
|
287
309
|
|
|
288
310
|
const output2 = stringify({
|
|
289
311
|
foo: 'baz'
|
|
290
312
|
})
|
|
291
313
|
t.assert.equal(output2, '{"foo":"baz"}')
|
|
292
|
-
t.assert.ok(validate(JSON.parse(output2)), 'valid schema')
|
|
293
314
|
})
|
|
294
315
|
|
|
295
316
|
test('schema with const and invalid object', (t) => {
|
package/test/date.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { test } = require('node:test')
|
|
4
|
-
const validator = require('
|
|
4
|
+
const validator = require('./utils/validator')
|
|
5
5
|
const build = require('..')
|
|
6
6
|
|
|
7
7
|
process.env.TZ = 'UTC'
|
|
@@ -637,3 +637,30 @@ test('should serialize also an invalid string value, even if it is not a valid t
|
|
|
637
637
|
t.assert.equal(output, JSON.stringify(toStringify))
|
|
638
638
|
t.assert.equal(validate(JSON.parse(output)), false, 'valid schema')
|
|
639
639
|
})
|
|
640
|
+
|
|
641
|
+
test('should escape strings that are not valid dates', (t) => {
|
|
642
|
+
const formats = ['date-time', 'date', 'time']
|
|
643
|
+
const values = [
|
|
644
|
+
'2026-01-01T00:00:00Z","admin":true,"x":"',
|
|
645
|
+
'back\\slash',
|
|
646
|
+
'new\nline',
|
|
647
|
+
'lone \ud800 surrogate'
|
|
648
|
+
]
|
|
649
|
+
|
|
650
|
+
t.plan(formats.length * values.length * 2)
|
|
651
|
+
|
|
652
|
+
for (const format of formats) {
|
|
653
|
+
const stringify = build({
|
|
654
|
+
type: 'object',
|
|
655
|
+
properties: {
|
|
656
|
+
ts: { type: 'string', format }
|
|
657
|
+
}
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
for (const value of values) {
|
|
661
|
+
const output = stringify({ ts: value })
|
|
662
|
+
t.assert.equal(output, `{"ts":${JSON.stringify(value)}}`)
|
|
663
|
+
t.assert.deepStrictEqual(JSON.parse(output), { ts: value })
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
})
|
package/test/debug-mode.test.js
CHANGED
|
@@ -128,3 +128,11 @@ test('Validator restoreFromState', t => {
|
|
|
128
128
|
const restored = Validator.restoreFromState(state)
|
|
129
129
|
t.assert.ok(restored instanceof Validator)
|
|
130
130
|
})
|
|
131
|
+
|
|
132
|
+
test('Validator ignores null schemas while converting nested values', t => {
|
|
133
|
+
t.plan(1)
|
|
134
|
+
|
|
135
|
+
const validator = new Validator()
|
|
136
|
+
|
|
137
|
+
t.assert.doesNotThrow(() => validator.convertSchemaToAjvFormat(null))
|
|
138
|
+
})
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// V8 has to optimize the generated serializers and keep them optimized when they see again the
|
|
4
|
+
// values they were optimized for. To see why a case fails: node --trace-deopt test/deopt.test.js
|
|
5
|
+
|
|
6
|
+
const { test } = require('node:test')
|
|
7
|
+
const v8 = require('node:v8')
|
|
8
|
+
|
|
9
|
+
// before the library loads, so its functions collect feedback from the first call
|
|
10
|
+
v8.setFlagsFromString('--allow-natives-syntax')
|
|
11
|
+
v8.setFlagsFromString('--no-lazy-feedback-allocation')
|
|
12
|
+
|
|
13
|
+
const build = require('..')
|
|
14
|
+
|
|
15
|
+
/* eslint no-new-func: "off" */
|
|
16
|
+
const prepare = new Function('fn', '%PrepareFunctionForOptimization(fn)')
|
|
17
|
+
const optimize = new Function('fn', '%OptimizeFunctionOnNextCall(fn)')
|
|
18
|
+
const isOptimized = new Function('fn', 'return %ActiveTierIsTurbofan(fn)')
|
|
19
|
+
|
|
20
|
+
function assertStaysOptimized (t, stringify, inputs) {
|
|
21
|
+
prepare(stringify)
|
|
22
|
+
for (const input of inputs) stringify(input)
|
|
23
|
+
optimize(stringify)
|
|
24
|
+
stringify(inputs[0])
|
|
25
|
+
t.assert.ok(isOptimized(stringify), 'V8 did not optimize it')
|
|
26
|
+
|
|
27
|
+
for (let round = 0; round < 100; round++) {
|
|
28
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
29
|
+
stringify(inputs[i])
|
|
30
|
+
if (!isOptimized(stringify)) t.assert.fail(`deoptimized by input ${i} in round ${round}`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test('string', (t) => {
|
|
36
|
+
assertStaysOptimized(t, build({ type: 'string' }), [
|
|
37
|
+
'hello', 'say "hi"', 'π', '😀', 'line\nbreak', '',
|
|
38
|
+
'x'.repeat(100), 'y"'.repeat(50), 'z'.repeat(6000), null, new Date(0), /re/, 42
|
|
39
|
+
])
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('string formats', (t) => {
|
|
43
|
+
const stringify = build({
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
dateTime: { type: 'string', format: 'date-time' },
|
|
47
|
+
date: { type: 'string', format: 'date' },
|
|
48
|
+
time: { type: 'string', format: 'time' },
|
|
49
|
+
unsafe: { type: 'string', format: 'unsafe' }
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
assertStaysOptimized(t, stringify, [
|
|
53
|
+
{ dateTime: new Date(0), date: new Date(0), time: new Date(0), unsafe: 'raw' },
|
|
54
|
+
{ dateTime: '2020-01-01T00:00:00.000Z', date: '2020-01-01', time: '10:00:00', unsafe: 'raw' },
|
|
55
|
+
{ dateTime: null, date: null, time: null }
|
|
56
|
+
])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('number', (t) => {
|
|
60
|
+
assertStaysOptimized(t, build({ type: 'number' }), [42, -1.5, 1e21, Infinity, '42'])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('integer', (t) => {
|
|
64
|
+
assertStaysOptimized(t, build({ type: 'integer' }), [42, -42, 4.7, 2 ** 40, 10n])
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('integer with rounding', (t) => {
|
|
68
|
+
assertStaysOptimized(t, build({ type: 'integer' }, { rounding: 'round' }), [42, 4.7, -4.2])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('boolean', (t) => {
|
|
72
|
+
assertStaysOptimized(t, build({ type: 'boolean' }), [true, false, 0, 'yes'])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('object', (t) => {
|
|
76
|
+
const stringify = build({
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
id: { type: 'integer' },
|
|
80
|
+
name: { type: 'string' },
|
|
81
|
+
score: { type: 'number' },
|
|
82
|
+
active: { type: 'boolean' },
|
|
83
|
+
role: { type: 'string', default: 'user' },
|
|
84
|
+
note: { type: 'string', nullable: true },
|
|
85
|
+
address: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: { city: { type: 'string' }, zip: { type: 'integer' } }
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
required: ['id', 'name']
|
|
91
|
+
})
|
|
92
|
+
assertStaysOptimized(t, stringify, [
|
|
93
|
+
{ id: 1, name: 'a', score: 1.5, active: true, role: 'admin', note: 'n', address: { city: 'c', zip: 1 } },
|
|
94
|
+
{ id: 2, name: 'b', score: 2, note: null, address: null },
|
|
95
|
+
{ id: 3, name: 'c' }
|
|
96
|
+
])
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('array', (t) => {
|
|
100
|
+
const stringify = build({
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
numbers: { type: 'array', items: { type: 'number' } },
|
|
104
|
+
strings: { type: 'array', items: { type: 'string' } },
|
|
105
|
+
objects: {
|
|
106
|
+
type: 'array',
|
|
107
|
+
items: { type: 'object', properties: { s: { type: 'string' }, n: { type: 'number' } } }
|
|
108
|
+
},
|
|
109
|
+
tuple: { type: 'array', items: [{ type: 'string' }, { type: 'integer' }] },
|
|
110
|
+
open: { type: 'array', items: [{ type: 'string' }], additionalItems: true }
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
assertStaysOptimized(t, stringify, [
|
|
114
|
+
{
|
|
115
|
+
numbers: [1, 2.5],
|
|
116
|
+
strings: ['a', 'b"'],
|
|
117
|
+
objects: [{ s: 'a', n: 1 }, { s: 'b', n: 2.5 }],
|
|
118
|
+
tuple: ['a', 1],
|
|
119
|
+
open: ['a', 1, true]
|
|
120
|
+
},
|
|
121
|
+
{ numbers: [], strings: [], objects: [], tuple: ['a'], open: [] }
|
|
122
|
+
])
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('large array mechanism', (t) => {
|
|
126
|
+
const stringify = build(
|
|
127
|
+
{ type: 'array', items: { type: 'integer' } },
|
|
128
|
+
{ largeArrayMechanism: 'json-stringify', largeArraySize: 3 }
|
|
129
|
+
)
|
|
130
|
+
assertStaysOptimized(t, stringify, [[1, 2], [1, 2, 3, 4]])
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('additionalProperties and patternProperties', (t) => {
|
|
134
|
+
const stringify = build({
|
|
135
|
+
type: 'object',
|
|
136
|
+
properties: { a: { type: 'string' } },
|
|
137
|
+
patternProperties: { '^n': { type: 'number' } },
|
|
138
|
+
additionalProperties: { type: 'string' }
|
|
139
|
+
})
|
|
140
|
+
assertStaysOptimized(t, stringify, [{ a: 'x', n1: 1, n2: 2.5, other: 'y' }, { other: 'y' }])
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('additionalProperties true', (t) => {
|
|
144
|
+
const stringify = build({ type: 'object', additionalProperties: true })
|
|
145
|
+
assertStaysOptimized(t, stringify, [{ a: 'x', b: 1, c: [1, { d: null }] }, {}])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('multiple types', (t) => {
|
|
149
|
+
const stringify = build({
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {
|
|
152
|
+
value: { type: ['string', 'integer', 'null'] },
|
|
153
|
+
list: { type: ['array', 'object'], items: { type: 'integer' }, properties: { a: { type: 'integer' } } }
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
assertStaysOptimized(t, stringify, [
|
|
157
|
+
{ value: 'a', list: [1, 2] },
|
|
158
|
+
{ value: 1, list: { a: 1 } },
|
|
159
|
+
{ value: null }
|
|
160
|
+
])
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('anyOf and oneOf', (t) => {
|
|
164
|
+
const stringify = build({
|
|
165
|
+
type: 'object',
|
|
166
|
+
properties: {
|
|
167
|
+
any: { anyOf: [{ type: 'string' }, { type: 'number' }] },
|
|
168
|
+
one: {
|
|
169
|
+
oneOf: [
|
|
170
|
+
{ type: 'object', properties: { kind: { const: 'a' }, a: { type: 'string' } }, required: ['kind'] },
|
|
171
|
+
{ type: 'object', properties: { kind: { const: 'b' }, b: { type: 'number' } }, required: ['kind'] }
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
assertStaysOptimized(t, stringify, [
|
|
177
|
+
{ any: 'x', one: { kind: 'a', a: 'x' } },
|
|
178
|
+
{ any: 1, one: { kind: 'b', b: 1 } }
|
|
179
|
+
])
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('if then else', (t) => {
|
|
183
|
+
const stringify = build({
|
|
184
|
+
type: 'object',
|
|
185
|
+
properties: { kind: { type: 'string' } },
|
|
186
|
+
if: { type: 'object', properties: { kind: { const: 'a' } } },
|
|
187
|
+
then: { properties: { a: { type: 'string' } } },
|
|
188
|
+
else: { properties: { b: { type: 'number' } } }
|
|
189
|
+
})
|
|
190
|
+
assertStaysOptimized(t, stringify, [{ kind: 'a', a: 'x' }, { kind: 'b', b: 1 }])
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test('allOf', (t) => {
|
|
194
|
+
const stringify = build({
|
|
195
|
+
allOf: [
|
|
196
|
+
{ type: 'object', properties: { a: { type: 'string' } } },
|
|
197
|
+
{ properties: { b: { type: 'number' } } }
|
|
198
|
+
]
|
|
199
|
+
})
|
|
200
|
+
assertStaysOptimized(t, stringify, [{ a: 'x', b: 1 }, {}])
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
test('recursive ref', (t) => {
|
|
204
|
+
const stringify = build({
|
|
205
|
+
$id: 'tree',
|
|
206
|
+
type: 'object',
|
|
207
|
+
properties: {
|
|
208
|
+
value: { type: 'integer' },
|
|
209
|
+
children: { type: 'array', items: { $ref: 'tree#' } }
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
assertStaysOptimized(t, stringify, [
|
|
213
|
+
{ value: 1, children: [{ value: 2, children: [] }, { value: 3, children: [{ value: 4 }] }] },
|
|
214
|
+
{ value: 5 }
|
|
215
|
+
])
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test('external ref', (t) => {
|
|
219
|
+
const defs = {
|
|
220
|
+
$id: 'defs',
|
|
221
|
+
definitions: { item: { type: 'object', properties: { x: { type: 'string' } } } }
|
|
222
|
+
}
|
|
223
|
+
const stringify = build(
|
|
224
|
+
{ type: 'object', properties: { item: { $ref: 'defs#/definitions/item' } } },
|
|
225
|
+
{ schema: { defs } }
|
|
226
|
+
)
|
|
227
|
+
assertStaysOptimized(t, stringify, [{ item: { x: 'y' } }, {}])
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test('toJSON', (t) => {
|
|
231
|
+
const stringify = build({ type: 'object', properties: { a: { type: 'string' } } })
|
|
232
|
+
assertStaysOptimized(t, stringify, [{ a: 'x' }, { toJSON () { return { a: 'y' } } }])
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// V8 does not optimize a function over a size, so a serializer whose code grows too much with
|
|
236
|
+
// every property stops being optimized for a wide object like this one
|
|
237
|
+
test('wide object', (t) => {
|
|
238
|
+
const properties = {}
|
|
239
|
+
const input = {}
|
|
240
|
+
for (let i = 0; i < 300; i++) {
|
|
241
|
+
switch (i % 4) {
|
|
242
|
+
case 0: properties[`s${i}`] = { type: 'string' }; input[`s${i}`] = 'x'; break
|
|
243
|
+
case 1: properties[`n${i}`] = { type: 'number' }; input[`n${i}`] = i; break
|
|
244
|
+
case 2: properties[`b${i}`] = { type: 'boolean' }; input[`b${i}`] = true; break
|
|
245
|
+
case 3:
|
|
246
|
+
properties[`o${i}`] = { type: 'object', properties: { a: { type: 'string' }, b: { type: 'integer' } } }
|
|
247
|
+
input[`o${i}`] = { a: 'y', b: i }
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
assertStaysOptimized(t, build({ type: 'object', properties }), [input, {}])
|
|
251
|
+
})
|
|
@@ -538,3 +538,148 @@ test('if-then-else with allOf', (t) => {
|
|
|
538
538
|
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
539
539
|
t.assert.equal(output, '{"base":"test","foo":"value"}')
|
|
540
540
|
})
|
|
541
|
+
|
|
542
|
+
test('if with a bare top-level $ref validates directly against the registered schema', (t) => {
|
|
543
|
+
t.plan(2)
|
|
544
|
+
|
|
545
|
+
const schema = {
|
|
546
|
+
type: 'object',
|
|
547
|
+
if: { $ref: 'IsFoo#' },
|
|
548
|
+
then: {
|
|
549
|
+
type: 'object',
|
|
550
|
+
properties: {
|
|
551
|
+
foo: { type: 'string' }
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
else: {
|
|
555
|
+
type: 'object',
|
|
556
|
+
properties: {
|
|
557
|
+
bar: { type: 'string' }
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const externalSchema = {
|
|
562
|
+
IsFoo: {
|
|
563
|
+
type: 'object',
|
|
564
|
+
properties: {
|
|
565
|
+
kind: { type: 'string', const: 'foo' }
|
|
566
|
+
},
|
|
567
|
+
required: ['kind']
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const code = build(schema, { mode: 'standalone', schema: externalSchema })
|
|
572
|
+
t.assert.ok(code.includes('validator.validate("IsFoo#"'), 'validates directly against the bare registered id')
|
|
573
|
+
|
|
574
|
+
const stringify = build(schema, { schema: externalSchema })
|
|
575
|
+
t.assert.equal(stringify({ kind: 'foo', foo: 'value', bar: 'ignored' }), '{"foo":"value"}')
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
test('if with a $ref into a JSON-pointer sub-path validates directly against that sub-path', (t) => {
|
|
579
|
+
t.plan(2)
|
|
580
|
+
|
|
581
|
+
const schema = {
|
|
582
|
+
type: 'object',
|
|
583
|
+
if: { $ref: 'Rules#/definitions/IsFoo' },
|
|
584
|
+
then: {
|
|
585
|
+
type: 'object',
|
|
586
|
+
properties: {
|
|
587
|
+
foo: { type: 'string' }
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
else: {
|
|
591
|
+
type: 'object',
|
|
592
|
+
properties: {
|
|
593
|
+
bar: { type: 'string' }
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const externalSchema = {
|
|
598
|
+
Rules: {
|
|
599
|
+
definitions: {
|
|
600
|
+
IsFoo: {
|
|
601
|
+
type: 'object',
|
|
602
|
+
properties: {
|
|
603
|
+
kind: { type: 'string', const: 'foo' }
|
|
604
|
+
},
|
|
605
|
+
required: ['kind']
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const code = build(schema, { mode: 'standalone', schema: externalSchema })
|
|
612
|
+
t.assert.ok(code.includes('validator.validate("Rules#/definitions/IsFoo"'), 'validates directly against the registered sub-path ref')
|
|
613
|
+
|
|
614
|
+
const stringify = build(schema, { schema: externalSchema })
|
|
615
|
+
t.assert.equal(stringify({ kind: 'foo', foo: 'value', bar: 'ignored' }), '{"foo":"value"}')
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
test('if with a $ref that has sibling keywords is left unchanged', (t) => {
|
|
619
|
+
t.plan(2)
|
|
620
|
+
|
|
621
|
+
const schema = {
|
|
622
|
+
type: 'object',
|
|
623
|
+
if: { $ref: 'IsFoo#', required: ['extra'] },
|
|
624
|
+
then: {
|
|
625
|
+
type: 'object',
|
|
626
|
+
properties: {
|
|
627
|
+
foo: { type: 'string' }
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
else: {
|
|
631
|
+
type: 'object',
|
|
632
|
+
properties: {
|
|
633
|
+
bar: { type: 'string' }
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const externalSchema = {
|
|
638
|
+
IsFoo: {
|
|
639
|
+
type: 'object',
|
|
640
|
+
properties: {
|
|
641
|
+
kind: { type: 'string', const: 'foo' }
|
|
642
|
+
},
|
|
643
|
+
required: ['kind']
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const code = build(schema, { mode: 'standalone', schema: externalSchema })
|
|
648
|
+
t.assert.ok(!code.includes('validator.validate("IsFoo#"'), 'ref with sibling keywords is not redirected')
|
|
649
|
+
|
|
650
|
+
const stringify = build(schema, { schema: externalSchema })
|
|
651
|
+
t.assert.equal(stringify({ kind: 'foo', foo: 'ignored', bar: 'fallback' }), '{"bar":"fallback"}')
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
test('if/else without then applies the else branch', (t) => {
|
|
655
|
+
t.plan(4)
|
|
656
|
+
|
|
657
|
+
const schema = {
|
|
658
|
+
type: 'object',
|
|
659
|
+
properties: {
|
|
660
|
+
kind: { type: 'string' }
|
|
661
|
+
},
|
|
662
|
+
if: {
|
|
663
|
+
type: 'object',
|
|
664
|
+
properties: {
|
|
665
|
+
kind: { type: 'string', enum: ['foobar'] }
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
else: {
|
|
669
|
+
type: 'object',
|
|
670
|
+
properties: {
|
|
671
|
+
bar: { type: 'string' }
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const stringify = build(schema)
|
|
677
|
+
|
|
678
|
+
const elseOutput = stringify({ kind: 'other', bar: 'value' })
|
|
679
|
+
t.assert.equal(elseOutput, '{"kind":"other","bar":"value"}')
|
|
680
|
+
t.assert.deepStrictEqual(JSON.parse(elseOutput), { kind: 'other', bar: 'value' })
|
|
681
|
+
|
|
682
|
+
const thenOutput = stringify({ kind: 'foobar', bar: 'value' })
|
|
683
|
+
t.assert.equal(thenOutput, '{"kind":"foobar"}')
|
|
684
|
+
t.assert.deepStrictEqual(JSON.parse(thenOutput), { kind: 'foobar' })
|
|
685
|
+
})
|
package/test/inferType.test.js
CHANGED