fast-json-stringify 5.10.0 → 5.11.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/index.js +266 -237
- package/lib/location.js +1 -30
- package/lib/merge-schemas.js +9 -0
- package/package.json +4 -4
- package/test/allof.test.js +134 -17
- package/test/anyof.test.js +121 -0
- package/test/if-then-else.test.js +58 -9
- package/test/required.test.js +4 -3
package/index.js
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint no-prototype-builtins: 0 */
|
|
4
4
|
|
|
5
|
-
const merge = require('@fastify/deepmerge')()
|
|
6
|
-
const clone = require('rfdc')({ proto: true })
|
|
7
5
|
const { RefResolver } = require('json-schema-ref-resolver')
|
|
8
6
|
|
|
9
|
-
const validate = require('./lib/schema-validator')
|
|
10
7
|
const Serializer = require('./lib/serializer')
|
|
11
8
|
const Validator = require('./lib/validator')
|
|
12
9
|
const Location = require('./lib/location')
|
|
10
|
+
const validate = require('./lib/schema-validator')
|
|
11
|
+
const mergeSchemas = require('./lib/merge-schemas')
|
|
13
12
|
|
|
14
13
|
const SINGLE_TICK = /'/g
|
|
15
14
|
|
|
@@ -28,10 +27,10 @@ const validLargeArrayMechanisms = [
|
|
|
28
27
|
'json-stringify'
|
|
29
28
|
]
|
|
30
29
|
|
|
31
|
-
const addComma = '!addComma && (addComma = true) || (json += \',\')'
|
|
32
|
-
|
|
33
30
|
let schemaIdCounter = 0
|
|
34
31
|
|
|
32
|
+
const mergedSchemaRef = Symbol('fjs-merged-schema-ref')
|
|
33
|
+
|
|
35
34
|
function isValidSchema (schema, name) {
|
|
36
35
|
if (!validate(schema)) {
|
|
37
36
|
if (name) {
|
|
@@ -46,13 +45,15 @@ function isValidSchema (schema, name) {
|
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
function resolveRef (context, location
|
|
48
|
+
function resolveRef (context, location) {
|
|
49
|
+
const ref = location.schema.$ref
|
|
50
|
+
|
|
50
51
|
let hashIndex = ref.indexOf('#')
|
|
51
52
|
if (hashIndex === -1) {
|
|
52
53
|
hashIndex = ref.length
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
const schemaId = ref.slice(0, hashIndex) || location.
|
|
56
|
+
const schemaId = ref.slice(0, hashIndex) || location.schemaId
|
|
56
57
|
const jsonPointer = ref.slice(hashIndex) || '#'
|
|
57
58
|
|
|
58
59
|
const schema = context.refResolver.getSchema(schemaId, jsonPointer)
|
|
@@ -62,12 +63,17 @@ function resolveRef (context, location, ref) {
|
|
|
62
63
|
|
|
63
64
|
const newLocation = new Location(schema, schemaId, jsonPointer)
|
|
64
65
|
if (schema.$ref !== undefined) {
|
|
65
|
-
return resolveRef(context, newLocation
|
|
66
|
+
return resolveRef(context, newLocation)
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
return newLocation
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
function getMergedLocation (context, mergedSchemaId) {
|
|
73
|
+
const mergedSchema = context.refResolver.getSchema(mergedSchemaId, '#')
|
|
74
|
+
return new Location(mergedSchema, mergedSchemaId, '#')
|
|
75
|
+
}
|
|
76
|
+
|
|
71
77
|
function getSchemaId (schema, rootSchemaId) {
|
|
72
78
|
if (schema.$id && schema.$id.charAt(0) !== '#') {
|
|
73
79
|
return schema.$id
|
|
@@ -85,7 +91,6 @@ function build (schema, options) {
|
|
|
85
91
|
functionsCounter: 0,
|
|
86
92
|
functionsNamesBySchema: new Map(),
|
|
87
93
|
options,
|
|
88
|
-
wrapObjects: true,
|
|
89
94
|
refResolver: new RefResolver(),
|
|
90
95
|
rootSchemaId: schema.$id || `__fjs_root_${schemaIdCounter++}`,
|
|
91
96
|
validatorSchemasIds: new Set()
|
|
@@ -255,7 +260,7 @@ function inferTypeByKeyword (schema) {
|
|
|
255
260
|
return schema.type
|
|
256
261
|
}
|
|
257
262
|
|
|
258
|
-
function buildExtraObjectPropertiesSerializer (context, location) {
|
|
263
|
+
function buildExtraObjectPropertiesSerializer (context, location, addComma) {
|
|
259
264
|
const schema = location.schema
|
|
260
265
|
const propertiesKeys = Object.keys(schema.properties || {})
|
|
261
266
|
|
|
@@ -314,225 +319,137 @@ function buildExtraObjectPropertiesSerializer (context, location) {
|
|
|
314
319
|
}
|
|
315
320
|
|
|
316
321
|
function buildInnerObject (context, location) {
|
|
317
|
-
let code = ''
|
|
318
322
|
const schema = location.schema
|
|
319
|
-
const required = schema.required || []
|
|
320
323
|
|
|
321
324
|
const propertiesLocation = location.getPropertyLocation('properties')
|
|
325
|
+
const requiredProperties = schema.required || []
|
|
322
326
|
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
|
|
333
|
-
}
|
|
327
|
+
// Should serialize required properties first
|
|
328
|
+
const propertiesKeys = Object.keys(schema.properties || {}).sort(
|
|
329
|
+
(key1, key2) => {
|
|
330
|
+
const required1 = requiredProperties.includes(key1)
|
|
331
|
+
const required2 = requiredProperties.includes(key2)
|
|
332
|
+
return required1 === required2 ? 0 : required1 ? -1 : 1
|
|
333
|
+
}
|
|
334
|
+
)
|
|
335
|
+
const hasRequiredProperties = requiredProperties.includes(propertiesKeys[0])
|
|
334
336
|
|
|
335
|
-
|
|
337
|
+
let code = ''
|
|
336
338
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (defaultValue === undefined) {
|
|
341
|
-
code += `if (obj[${sanitizedKey}] === undefined) throw new Error('${sanitizedKey} is required!')\n`
|
|
342
|
-
requiredWithoutDefault.push(key)
|
|
343
|
-
}
|
|
344
|
-
requiredWithDefault.push(key)
|
|
339
|
+
for (const key of requiredProperties) {
|
|
340
|
+
if (!propertiesKeys.includes(key)) {
|
|
341
|
+
code += `if (obj['${key}'] === undefined) throw new Error('"${key}" is required!')\n`
|
|
345
342
|
}
|
|
346
343
|
}
|
|
347
344
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
345
|
+
code += 'let json = \'{\'\n'
|
|
346
|
+
|
|
347
|
+
let addComma = ''
|
|
348
|
+
if (!hasRequiredProperties) {
|
|
349
|
+
code += 'let addComma = false\n'
|
|
350
|
+
addComma = '!addComma && (addComma = true) || (json += \',\')'
|
|
352
351
|
}
|
|
353
352
|
|
|
354
|
-
|
|
355
|
-
let
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
context.wrapObjects = true
|
|
360
|
-
|
|
361
|
-
if (schema.properties) {
|
|
362
|
-
for (const key of Object.keys(schema.properties)) {
|
|
363
|
-
let propertyLocation = propertiesLocation.getPropertyLocation(key)
|
|
364
|
-
if (propertyLocation.schema.$ref) {
|
|
365
|
-
propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
|
|
366
|
-
}
|
|
353
|
+
for (const key of propertiesKeys) {
|
|
354
|
+
let propertyLocation = propertiesLocation.getPropertyLocation(key)
|
|
355
|
+
if (propertyLocation.schema.$ref) {
|
|
356
|
+
propertyLocation = resolveRef(context, propertyLocation)
|
|
357
|
+
}
|
|
367
358
|
|
|
368
|
-
|
|
359
|
+
const sanitizedKey = JSON.stringify(key)
|
|
360
|
+
const defaultValue = propertyLocation.schema.default
|
|
361
|
+
const isRequired = requiredProperties.includes(key)
|
|
369
362
|
|
|
370
|
-
|
|
371
|
-
|
|
363
|
+
code += `
|
|
364
|
+
if (obj[${sanitizedKey}] !== undefined) {
|
|
372
365
|
${addComma}
|
|
373
366
|
json += ${JSON.stringify(sanitizedKey + ':')}
|
|
374
367
|
${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)}
|
|
368
|
+
}`
|
|
369
|
+
|
|
370
|
+
if (defaultValue !== undefined) {
|
|
371
|
+
code += ` else {
|
|
372
|
+
${addComma}
|
|
373
|
+
json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))}
|
|
374
|
+
}
|
|
375
375
|
`
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
code += `
|
|
380
|
-
if (obj[${sanitizedKey}] !== undefined) {
|
|
381
|
-
${addComma}
|
|
382
|
-
json += ${JSON.stringify(sanitizedKey + ':')}
|
|
383
|
-
${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)}
|
|
384
|
-
}
|
|
385
|
-
`
|
|
386
|
-
const defaultValue = propertyLocation.schema.default
|
|
387
|
-
if (defaultValue !== undefined) {
|
|
388
|
-
code += `
|
|
389
|
-
else {
|
|
390
|
-
${addComma}
|
|
391
|
-
json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))}
|
|
392
|
-
}
|
|
393
|
-
`
|
|
394
|
-
}
|
|
376
|
+
} else if (isRequired) {
|
|
377
|
+
code += ` else {
|
|
378
|
+
throw new Error('${sanitizedKey} is required!')
|
|
395
379
|
}
|
|
380
|
+
`
|
|
381
|
+
} else {
|
|
382
|
+
code += '\n'
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (hasRequiredProperties) {
|
|
386
|
+
addComma = 'json += \',\''
|
|
396
387
|
}
|
|
397
388
|
}
|
|
398
389
|
|
|
399
390
|
if (schema.patternProperties || schema.additionalProperties) {
|
|
400
|
-
code += buildExtraObjectPropertiesSerializer(context, location)
|
|
391
|
+
code += buildExtraObjectPropertiesSerializer(context, location, addComma)
|
|
401
392
|
}
|
|
402
393
|
|
|
403
|
-
context.wrapObjects = wrapObjects
|
|
404
394
|
code += `
|
|
405
|
-
|
|
395
|
+
return json + '}'
|
|
406
396
|
`
|
|
407
397
|
return code
|
|
408
398
|
}
|
|
409
399
|
|
|
410
|
-
function
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (allOfSchema.$ref) {
|
|
417
|
-
const allOfSchemaLocation = allOfLocation.getPropertyLocation(i)
|
|
418
|
-
allOfSchema = resolveRef(context, allOfSchemaLocation, allOfSchema.$ref).schema
|
|
400
|
+
function mergeLocations (context, mergedSchemaId, mergedLocations) {
|
|
401
|
+
for (let i = 0; i < mergedLocations.length; i++) {
|
|
402
|
+
const location = mergedLocations[i]
|
|
403
|
+
const schema = location.schema
|
|
404
|
+
if (schema.$ref) {
|
|
405
|
+
mergedLocations[i] = resolveRef(context, location)
|
|
419
406
|
}
|
|
407
|
+
}
|
|
420
408
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
409
|
+
const mergedSchemas = []
|
|
410
|
+
for (const location of mergedLocations) {
|
|
411
|
+
const schema = cloneOriginSchema(location.schema, location.schemaId)
|
|
412
|
+
delete schema.$id
|
|
425
413
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
mergedSchema.type !== undefined &&
|
|
429
|
-
mergedSchema.type !== allOfSchemaType
|
|
430
|
-
) {
|
|
431
|
-
throw new Error('allOf schemas have different type values')
|
|
432
|
-
}
|
|
433
|
-
mergedSchema.type = allOfSchemaType
|
|
434
|
-
}
|
|
414
|
+
mergedSchemas.push(schema)
|
|
415
|
+
}
|
|
435
416
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
mergedSchema.format !== undefined &&
|
|
439
|
-
mergedSchema.format !== allOfSchema.format
|
|
440
|
-
) {
|
|
441
|
-
throw new Error('allOf schemas have different format values')
|
|
442
|
-
}
|
|
443
|
-
mergedSchema.format = allOfSchema.format
|
|
444
|
-
}
|
|
417
|
+
const mergedSchema = mergeSchemas(mergedSchemas)
|
|
418
|
+
const mergedLocation = new Location(mergedSchema, mergedSchemaId)
|
|
445
419
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
mergedSchema.nullable !== allOfSchema.nullable
|
|
450
|
-
) {
|
|
451
|
-
throw new Error('allOf schemas have different nullable values')
|
|
452
|
-
}
|
|
453
|
-
mergedSchema.nullable = allOfSchema.nullable
|
|
454
|
-
}
|
|
420
|
+
context.refResolver.addSchema(mergedSchema, mergedSchemaId)
|
|
421
|
+
return mergedLocation
|
|
422
|
+
}
|
|
455
423
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
mergedSchema.properties = {}
|
|
459
|
-
}
|
|
460
|
-
Object.assign(mergedSchema.properties, allOfSchema.properties)
|
|
461
|
-
}
|
|
424
|
+
function cloneOriginSchema (schema, schemaId) {
|
|
425
|
+
const clonedSchema = Array.isArray(schema) ? [] : {}
|
|
462
426
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
427
|
+
if (
|
|
428
|
+
schema.$id !== undefined &&
|
|
429
|
+
schema.$id.charAt(0) !== '#'
|
|
430
|
+
) {
|
|
431
|
+
schemaId = schema.$id
|
|
432
|
+
}
|
|
469
433
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
Object.assign(mergedSchema.patternProperties, allOfSchema.patternProperties)
|
|
475
|
-
}
|
|
434
|
+
if (schema[mergedSchemaRef]) {
|
|
435
|
+
clonedSchema[mergedSchemaRef] = schema[mergedSchemaRef]
|
|
436
|
+
}
|
|
476
437
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
mergedSchema.required = []
|
|
480
|
-
}
|
|
481
|
-
mergedSchema.required.push(...allOfSchema.required)
|
|
482
|
-
}
|
|
438
|
+
for (const key in schema) {
|
|
439
|
+
let value = schema[key]
|
|
483
440
|
|
|
484
|
-
if (
|
|
485
|
-
|
|
486
|
-
mergedSchema.oneOf = []
|
|
487
|
-
}
|
|
488
|
-
mergedSchema.oneOf.push(...allOfSchema.oneOf)
|
|
441
|
+
if (key === '$ref' && value.charAt(0) === '#') {
|
|
442
|
+
value = schemaId + value
|
|
489
443
|
}
|
|
490
444
|
|
|
491
|
-
if (
|
|
492
|
-
|
|
493
|
-
mergedSchema.anyOf = []
|
|
494
|
-
}
|
|
495
|
-
mergedSchema.anyOf.push(...allOfSchema.anyOf)
|
|
445
|
+
if (typeof value === 'object' && value !== null) {
|
|
446
|
+
value = cloneOriginSchema(value, schemaId)
|
|
496
447
|
}
|
|
497
448
|
|
|
498
|
-
|
|
499
|
-
mergeAllOfSchema(context, location, allOfSchema, mergedSchema)
|
|
500
|
-
}
|
|
449
|
+
clonedSchema[key] = value
|
|
501
450
|
}
|
|
502
|
-
delete mergedSchema.allOf
|
|
503
451
|
|
|
504
|
-
|
|
505
|
-
context.refResolver.addSchema(mergedSchema)
|
|
506
|
-
location.addMergedSchema(mergedSchema, mergedSchema.$id)
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function addIfThenElse (context, location, input) {
|
|
510
|
-
context.validatorSchemasIds.add(location.getSchemaId())
|
|
511
|
-
|
|
512
|
-
const schema = merge({}, location.schema)
|
|
513
|
-
const thenSchema = schema.then
|
|
514
|
-
const elseSchema = schema.else || { additionalProperties: true }
|
|
515
|
-
|
|
516
|
-
delete schema.if
|
|
517
|
-
delete schema.then
|
|
518
|
-
delete schema.else
|
|
519
|
-
|
|
520
|
-
const ifLocation = location.getPropertyLocation('if')
|
|
521
|
-
const ifSchemaRef = ifLocation.getSchemaRef()
|
|
522
|
-
|
|
523
|
-
const thenLocation = location.getPropertyLocation('then')
|
|
524
|
-
thenLocation.schema = merge(schema, thenSchema)
|
|
525
|
-
|
|
526
|
-
const elseLocation = location.getPropertyLocation('else')
|
|
527
|
-
elseLocation.schema = merge(schema, elseSchema)
|
|
528
|
-
|
|
529
|
-
return `
|
|
530
|
-
if (validator.validate("${ifSchemaRef}", ${input})) {
|
|
531
|
-
${buildValue(context, thenLocation, input)}
|
|
532
|
-
} else {
|
|
533
|
-
${buildValue(context, elseLocation, input)}
|
|
534
|
-
}
|
|
535
|
-
`
|
|
452
|
+
return clonedSchema
|
|
536
453
|
}
|
|
537
454
|
|
|
538
455
|
function toJSON (variableName) {
|
|
@@ -582,7 +499,7 @@ function buildArray (context, location) {
|
|
|
582
499
|
itemsLocation.schema = itemsLocation.schema || {}
|
|
583
500
|
|
|
584
501
|
if (itemsLocation.schema.$ref) {
|
|
585
|
-
itemsLocation = resolveRef(context, itemsLocation
|
|
502
|
+
itemsLocation = resolveRef(context, itemsLocation)
|
|
586
503
|
}
|
|
587
504
|
|
|
588
505
|
const itemsSchema = itemsLocation.schema
|
|
@@ -860,81 +777,193 @@ function buildConstSerializer (location, input) {
|
|
|
860
777
|
return code
|
|
861
778
|
}
|
|
862
779
|
|
|
863
|
-
function
|
|
864
|
-
|
|
780
|
+
function buildAllOf (context, location, input) {
|
|
781
|
+
const schema = location.schema
|
|
865
782
|
|
|
866
|
-
|
|
867
|
-
|
|
783
|
+
let mergedSchemaId = schema[mergedSchemaRef]
|
|
784
|
+
if (mergedSchemaId) {
|
|
785
|
+
const mergedLocation = getMergedLocation(context, mergedSchemaId)
|
|
786
|
+
return buildValue(context, mergedLocation, input)
|
|
868
787
|
}
|
|
869
788
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
789
|
+
mergedSchemaId = `__fjs_merged_${schemaIdCounter++}`
|
|
790
|
+
schema[mergedSchemaRef] = mergedSchemaId
|
|
791
|
+
|
|
792
|
+
const { allOf, ...schemaWithoutAllOf } = location.schema
|
|
793
|
+
const locations = [
|
|
794
|
+
new Location(
|
|
795
|
+
schemaWithoutAllOf,
|
|
796
|
+
location.schemaId,
|
|
797
|
+
location.jsonPointer
|
|
798
|
+
)
|
|
799
|
+
]
|
|
800
|
+
|
|
801
|
+
const allOfsLocation = location.getPropertyLocation('allOf')
|
|
802
|
+
for (let i = 0; i < allOf.length; i++) {
|
|
803
|
+
locations.push(allOfsLocation.getPropertyLocation(i))
|
|
873
804
|
}
|
|
874
805
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
806
|
+
const mergedLocation = mergeLocations(context, mergedSchemaId, locations)
|
|
807
|
+
return buildValue(context, mergedLocation, input)
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function buildOneOf (context, location, input) {
|
|
811
|
+
context.validatorSchemasIds.add(location.schemaId)
|
|
812
|
+
|
|
813
|
+
const schema = location.schema
|
|
814
|
+
|
|
815
|
+
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
816
|
+
const { [type]: oneOfs, ...schemaWithoutAnyOf } = location.schema
|
|
817
|
+
|
|
818
|
+
const locationWithoutOneOf = new Location(
|
|
819
|
+
schemaWithoutAnyOf,
|
|
820
|
+
location.schemaId,
|
|
821
|
+
location.jsonPointer
|
|
822
|
+
)
|
|
823
|
+
const oneOfsLocation = location.getPropertyLocation(type)
|
|
824
|
+
|
|
825
|
+
let code = ''
|
|
826
|
+
|
|
827
|
+
for (let index = 0; index < oneOfs.length; index++) {
|
|
828
|
+
const optionLocation = oneOfsLocation.getPropertyLocation(index)
|
|
829
|
+
const optionSchema = optionLocation.schema
|
|
830
|
+
|
|
831
|
+
let mergedSchemaId = optionSchema[mergedSchemaRef]
|
|
832
|
+
let mergedLocation = null
|
|
833
|
+
if (mergedSchemaId) {
|
|
834
|
+
mergedLocation = getMergedLocation(context, mergedSchemaId)
|
|
835
|
+
} else {
|
|
836
|
+
mergedSchemaId = `__fjs_merged_${schemaIdCounter++}`
|
|
837
|
+
optionSchema[mergedSchemaRef] = mergedSchemaId
|
|
838
|
+
|
|
839
|
+
mergedLocation = mergeLocations(context, mergedSchemaId, [
|
|
840
|
+
locationWithoutOneOf,
|
|
841
|
+
optionLocation
|
|
842
|
+
])
|
|
879
843
|
}
|
|
844
|
+
|
|
845
|
+
const nestedResult = buildValue(context, mergedLocation, input)
|
|
846
|
+
const schemaRef = optionLocation.getSchemaRef()
|
|
847
|
+
code += `
|
|
848
|
+
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
|
|
849
|
+
${nestedResult}
|
|
850
|
+
`
|
|
880
851
|
}
|
|
881
852
|
|
|
882
|
-
|
|
883
|
-
|
|
853
|
+
let schemaRef = location.getSchemaRef()
|
|
854
|
+
if (schemaRef.startsWith(context.rootSchemaId)) {
|
|
855
|
+
schemaRef = schemaRef.replace(context.rootSchemaId, '')
|
|
884
856
|
}
|
|
885
857
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
858
|
+
code += `
|
|
859
|
+
else throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`)
|
|
860
|
+
`
|
|
861
|
+
|
|
862
|
+
return code
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function buildIfThenElse (context, location, input) {
|
|
866
|
+
context.validatorSchemasIds.add(location.schemaId)
|
|
867
|
+
|
|
868
|
+
const {
|
|
869
|
+
if: ifSchema,
|
|
870
|
+
then: thenSchema,
|
|
871
|
+
else: elseSchema,
|
|
872
|
+
...schemaWithoutIfThenElse
|
|
873
|
+
} = location.schema
|
|
874
|
+
|
|
875
|
+
const rootLocation = new Location(
|
|
876
|
+
schemaWithoutIfThenElse,
|
|
877
|
+
location.schemaId,
|
|
878
|
+
location.jsonPointer
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
const ifLocation = location.getPropertyLocation('if')
|
|
882
|
+
const ifSchemaRef = ifLocation.getSchemaRef()
|
|
883
|
+
|
|
884
|
+
const thenLocation = location.getPropertyLocation('then')
|
|
885
|
+
let thenMergedSchemaId = thenSchema[mergedSchemaRef]
|
|
886
|
+
let thenMergedLocation = null
|
|
887
|
+
if (thenMergedSchemaId) {
|
|
888
|
+
thenMergedLocation = getMergedLocation(context, thenMergedSchemaId)
|
|
889
|
+
} else {
|
|
890
|
+
thenMergedSchemaId = `__fjs_merged_${schemaIdCounter++}`
|
|
891
|
+
thenSchema[mergedSchemaRef] = thenMergedSchemaId
|
|
892
|
+
|
|
893
|
+
thenMergedLocation = mergeLocations(context, thenMergedSchemaId, [
|
|
894
|
+
rootLocation,
|
|
895
|
+
thenLocation
|
|
896
|
+
])
|
|
889
897
|
}
|
|
890
898
|
|
|
891
|
-
|
|
899
|
+
if (!elseSchema) {
|
|
900
|
+
return `
|
|
901
|
+
if (validator.validate("${ifSchemaRef}", ${input})) {
|
|
902
|
+
${buildValue(context, thenMergedLocation, input)}
|
|
903
|
+
} else {
|
|
904
|
+
${buildValue(context, rootLocation, input)}
|
|
905
|
+
}
|
|
906
|
+
`
|
|
907
|
+
}
|
|
892
908
|
|
|
893
|
-
|
|
909
|
+
const elseLocation = location.getPropertyLocation('else')
|
|
910
|
+
let elseMergedSchemaId = elseSchema[mergedSchemaRef]
|
|
911
|
+
let elseMergedLocation = null
|
|
912
|
+
if (elseMergedSchemaId) {
|
|
913
|
+
elseMergedLocation = getMergedLocation(context, elseMergedSchemaId)
|
|
914
|
+
} else {
|
|
915
|
+
elseMergedSchemaId = `__fjs_merged_${schemaIdCounter++}`
|
|
916
|
+
elseSchema[mergedSchemaRef] = elseMergedSchemaId
|
|
894
917
|
|
|
895
|
-
|
|
896
|
-
|
|
918
|
+
elseMergedLocation = mergeLocations(context, elseMergedSchemaId, [
|
|
919
|
+
rootLocation,
|
|
920
|
+
elseLocation
|
|
921
|
+
])
|
|
922
|
+
}
|
|
897
923
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
json += ${funcName}(${input})
|
|
904
|
-
json += ','
|
|
905
|
-
`
|
|
924
|
+
return `
|
|
925
|
+
if (validator.validate("${ifSchemaRef}", ${input})) {
|
|
926
|
+
${buildValue(context, thenMergedLocation, input)}
|
|
927
|
+
} else {
|
|
928
|
+
${buildValue(context, elseMergedLocation, input)}
|
|
906
929
|
}
|
|
930
|
+
`
|
|
931
|
+
}
|
|
907
932
|
|
|
908
|
-
|
|
909
|
-
|
|
933
|
+
function buildValue (context, location, input) {
|
|
934
|
+
let schema = location.schema
|
|
910
935
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
const nestedResult = buildValue(context, optionLocation, input)
|
|
915
|
-
code += `
|
|
916
|
-
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
|
|
917
|
-
${nestedResult}
|
|
918
|
-
`
|
|
919
|
-
}
|
|
936
|
+
if (typeof schema === 'boolean') {
|
|
937
|
+
return `json += JSON.stringify(${input})`
|
|
938
|
+
}
|
|
920
939
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
940
|
+
if (schema.$ref) {
|
|
941
|
+
location = resolveRef(context, location)
|
|
942
|
+
schema = location.schema
|
|
943
|
+
}
|
|
925
944
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
945
|
+
if (schema.allOf) {
|
|
946
|
+
return buildAllOf(context, location, input)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (schema.anyOf || schema.oneOf) {
|
|
950
|
+
return buildOneOf(context, location, input)
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
if (schema.if && schema.then) {
|
|
954
|
+
return buildIfThenElse(context, location, input)
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (schema.type === undefined) {
|
|
958
|
+
const inferredType = inferTypeByKeyword(schema)
|
|
959
|
+
if (inferredType) {
|
|
960
|
+
schema.type = inferredType
|
|
934
961
|
}
|
|
935
|
-
return code
|
|
936
962
|
}
|
|
937
963
|
|
|
964
|
+
let code = ''
|
|
965
|
+
|
|
966
|
+
const type = schema.type
|
|
938
967
|
const nullable = schema.nullable === true
|
|
939
968
|
if (nullable) {
|
|
940
969
|
code += `
|
package/lib/location.js
CHANGED
|
@@ -5,7 +5,6 @@ class Location {
|
|
|
5
5
|
this.schema = schema
|
|
6
6
|
this.schemaId = schemaId
|
|
7
7
|
this.jsonPointer = jsonPointer
|
|
8
|
-
this.mergedSchemaId = null
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
getPropertyLocation (propertyName) {
|
|
@@ -14,39 +13,11 @@ class Location {
|
|
|
14
13
|
this.schemaId,
|
|
15
14
|
this.jsonPointer + '/' + propertyName
|
|
16
15
|
)
|
|
17
|
-
|
|
18
|
-
if (this.mergedSchemaId !== null) {
|
|
19
|
-
propertyLocation.addMergedSchema(
|
|
20
|
-
this.schema[propertyName],
|
|
21
|
-
this.mergedSchemaId,
|
|
22
|
-
this.jsonPointer + '/' + propertyName
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
16
|
return propertyLocation
|
|
27
17
|
}
|
|
28
18
|
|
|
29
|
-
// Use this method to get current schema location.
|
|
30
|
-
// Use it when you need to create reference to the current location.
|
|
31
|
-
getSchemaId () {
|
|
32
|
-
return this.mergedSchemaId || this.schemaId
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Use this method to get original schema id for resolving user schema $refs
|
|
36
|
-
// Don't join it with a JSON pointer to get the current location.
|
|
37
|
-
getOriginSchemaId () {
|
|
38
|
-
return this.schemaId
|
|
39
|
-
}
|
|
40
|
-
|
|
41
19
|
getSchemaRef () {
|
|
42
|
-
|
|
43
|
-
return schemaId + this.jsonPointer
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
addMergedSchema (mergedSchema, schemaId, jsonPointer = '#') {
|
|
47
|
-
this.schema = mergedSchema
|
|
48
|
-
this.mergedSchemaId = schemaId
|
|
49
|
-
this.jsonPointer = jsonPointer
|
|
20
|
+
return this.schemaId + this.jsonPointer
|
|
50
21
|
}
|
|
51
22
|
}
|
|
52
23
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-json-stringify",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.0",
|
|
4
4
|
"description": "Stringify your JSON at max speed",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -46,18 +46,18 @@
|
|
|
46
46
|
"simple-git": "^3.7.1",
|
|
47
47
|
"standard": "^17.0.0",
|
|
48
48
|
"tap": "^16.0.1",
|
|
49
|
-
"tsd": "^0.
|
|
49
|
+
"tsd": "^0.30.3",
|
|
50
50
|
"webpack": "^5.40.0",
|
|
51
51
|
"fast-json-stringify": "."
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@fastify/deepmerge": "^1.0.0",
|
|
55
54
|
"ajv": "^8.10.0",
|
|
56
55
|
"ajv-formats": "^2.1.1",
|
|
57
56
|
"fast-deep-equal": "^3.1.3",
|
|
58
57
|
"fast-uri": "^2.1.0",
|
|
59
58
|
"rfdc": "^1.2.0",
|
|
60
|
-
"json-schema-ref-resolver": "^1.0.1"
|
|
59
|
+
"json-schema-ref-resolver": "^1.0.1",
|
|
60
|
+
"@fastify/merge-json-schemas": "^0.1.0"
|
|
61
61
|
},
|
|
62
62
|
"standard": {
|
|
63
63
|
"ignore": [
|
package/test/allof.test.js
CHANGED
|
@@ -121,7 +121,7 @@ test('object with allOf and multiple schema on the allOf', (t) => {
|
|
|
121
121
|
id: 1,
|
|
122
122
|
name: 'string',
|
|
123
123
|
tag: 'otherString'
|
|
124
|
-
}), '{"name":"string","tag":"otherString"
|
|
124
|
+
}), '{"name":"string","id":1,"tag":"otherString"}')
|
|
125
125
|
})
|
|
126
126
|
|
|
127
127
|
test('object with allOf and one schema on the allOf', (t) => {
|
|
@@ -553,8 +553,60 @@ test('allof with local anchor reference', (t) => {
|
|
|
553
553
|
t.equal(stringify(data), JSON.stringify(data))
|
|
554
554
|
})
|
|
555
555
|
|
|
556
|
+
test('allOf: multiple nested $ref properties', (t) => {
|
|
557
|
+
t.plan(2)
|
|
558
|
+
|
|
559
|
+
const externalSchema1 = {
|
|
560
|
+
$id: 'externalSchema1',
|
|
561
|
+
oneOf: [
|
|
562
|
+
{ $ref: '#/definitions/id1' }
|
|
563
|
+
],
|
|
564
|
+
definitions: {
|
|
565
|
+
id1: {
|
|
566
|
+
type: 'object',
|
|
567
|
+
properties: {
|
|
568
|
+
id1: {
|
|
569
|
+
type: 'integer'
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
additionalProperties: false
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const externalSchema2 = {
|
|
578
|
+
$id: 'externalSchema2',
|
|
579
|
+
oneOf: [
|
|
580
|
+
{ $ref: '#/definitions/id2' }
|
|
581
|
+
],
|
|
582
|
+
definitions: {
|
|
583
|
+
id2: {
|
|
584
|
+
type: 'object',
|
|
585
|
+
properties: {
|
|
586
|
+
id2: {
|
|
587
|
+
type: 'integer'
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
additionalProperties: false
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const schema = {
|
|
596
|
+
anyOf: [
|
|
597
|
+
{ $ref: 'externalSchema1' },
|
|
598
|
+
{ $ref: 'externalSchema2' }
|
|
599
|
+
]
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const stringify = build(schema, { schema: [externalSchema1, externalSchema2] })
|
|
603
|
+
|
|
604
|
+
t.equal(stringify({ id1: 1 }), JSON.stringify({ id1: 1 }))
|
|
605
|
+
t.equal(stringify({ id2: 2 }), JSON.stringify({ id2: 2 }))
|
|
606
|
+
})
|
|
607
|
+
|
|
556
608
|
test('allOf: throw Error if types mismatch ', (t) => {
|
|
557
|
-
t.plan(
|
|
609
|
+
t.plan(3)
|
|
558
610
|
|
|
559
611
|
const schema = {
|
|
560
612
|
allOf: [
|
|
@@ -562,11 +614,18 @@ test('allOf: throw Error if types mismatch ', (t) => {
|
|
|
562
614
|
{ type: 'number' }
|
|
563
615
|
]
|
|
564
616
|
}
|
|
565
|
-
|
|
617
|
+
try {
|
|
618
|
+
build(schema)
|
|
619
|
+
t.fail('should throw the MergeError')
|
|
620
|
+
} catch (error) {
|
|
621
|
+
t.ok(error instanceof Error)
|
|
622
|
+
t.equal(error.message, 'Failed to merge "type" keyword schemas.')
|
|
623
|
+
t.same(error.schemas, [['string'], ['number']])
|
|
624
|
+
}
|
|
566
625
|
})
|
|
567
626
|
|
|
568
627
|
test('allOf: throw Error if format mismatch ', (t) => {
|
|
569
|
-
t.plan(
|
|
628
|
+
t.plan(3)
|
|
570
629
|
|
|
571
630
|
const schema = {
|
|
572
631
|
allOf: [
|
|
@@ -574,29 +633,87 @@ test('allOf: throw Error if format mismatch ', (t) => {
|
|
|
574
633
|
{ format: 'time' }
|
|
575
634
|
]
|
|
576
635
|
}
|
|
577
|
-
|
|
636
|
+
try {
|
|
637
|
+
build(schema)
|
|
638
|
+
t.fail('should throw the MergeError')
|
|
639
|
+
} catch (error) {
|
|
640
|
+
t.ok(error instanceof Error)
|
|
641
|
+
t.equal(error.message, 'Failed to merge "format" keyword schemas.')
|
|
642
|
+
t.same(error.schemas, ['date', 'time'])
|
|
643
|
+
}
|
|
578
644
|
})
|
|
579
645
|
|
|
580
|
-
test('
|
|
646
|
+
test('recursive nested allOfs', (t) => {
|
|
581
647
|
t.plan(1)
|
|
582
648
|
|
|
583
649
|
const schema = {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
650
|
+
type: 'object',
|
|
651
|
+
properties: {
|
|
652
|
+
foo: {
|
|
653
|
+
additionalProperties: false,
|
|
654
|
+
allOf: [{ $ref: '#' }]
|
|
655
|
+
}
|
|
656
|
+
}
|
|
588
657
|
}
|
|
589
|
-
|
|
658
|
+
|
|
659
|
+
const data = { foo: {} }
|
|
660
|
+
const stringify = build(schema)
|
|
661
|
+
t.equal(stringify(data), JSON.stringify(data))
|
|
590
662
|
})
|
|
591
663
|
|
|
592
|
-
test('
|
|
664
|
+
test('recursive nested allOfs', (t) => {
|
|
593
665
|
t.plan(1)
|
|
594
666
|
|
|
595
667
|
const schema = {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
668
|
+
type: 'object',
|
|
669
|
+
properties: {
|
|
670
|
+
foo: {
|
|
671
|
+
additionalProperties: false,
|
|
672
|
+
allOf: [{ allOf: [{ $ref: '#' }] }]
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const data = { foo: {} }
|
|
678
|
+
const stringify = build(schema)
|
|
679
|
+
t.equal(stringify(data), JSON.stringify(data))
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
test('external recursive allOfs', (t) => {
|
|
683
|
+
t.plan(1)
|
|
684
|
+
|
|
685
|
+
const externalSchema = {
|
|
686
|
+
type: 'object',
|
|
687
|
+
properties: {
|
|
688
|
+
foo: {
|
|
689
|
+
properties: {
|
|
690
|
+
bar: { type: 'string' }
|
|
691
|
+
},
|
|
692
|
+
allOf: [{ $ref: '#' }]
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const schema = {
|
|
698
|
+
type: 'object',
|
|
699
|
+
properties: {
|
|
700
|
+
a: { $ref: 'externalSchema#/properties/foo' },
|
|
701
|
+
b: { $ref: 'externalSchema#/properties/foo' }
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const data = {
|
|
706
|
+
a: {
|
|
707
|
+
foo: {},
|
|
708
|
+
bar: '42',
|
|
709
|
+
baz: 42
|
|
710
|
+
},
|
|
711
|
+
b: {
|
|
712
|
+
foo: {},
|
|
713
|
+
bar: '42',
|
|
714
|
+
baz: 42
|
|
715
|
+
}
|
|
600
716
|
}
|
|
601
|
-
|
|
717
|
+
const stringify = build(schema, { schema: { externalSchema } })
|
|
718
|
+
t.equal(stringify(data), '{"a":{"bar":"42","foo":{}},"b":{"bar":"42","foo":{}}}')
|
|
602
719
|
})
|
package/test/anyof.test.js
CHANGED
|
@@ -644,3 +644,124 @@ test('object with ref and validated properties', (t) => {
|
|
|
644
644
|
const stringify = build(schema, { schema: externalSchemas })
|
|
645
645
|
t.equal(stringify({ id: 1, reference: 'hi' }), '{"id":1,"reference":"hi"}')
|
|
646
646
|
})
|
|
647
|
+
|
|
648
|
+
test('anyOf required props', (t) => {
|
|
649
|
+
t.plan(3)
|
|
650
|
+
|
|
651
|
+
const schema = {
|
|
652
|
+
type: 'object',
|
|
653
|
+
properties: {
|
|
654
|
+
prop1: { type: 'string' },
|
|
655
|
+
prop2: { type: 'string' },
|
|
656
|
+
prop3: { type: 'string' }
|
|
657
|
+
},
|
|
658
|
+
required: ['prop1'],
|
|
659
|
+
anyOf: [{ required: ['prop2'] }, { required: ['prop3'] }]
|
|
660
|
+
}
|
|
661
|
+
const stringify = build(schema)
|
|
662
|
+
t.equal(stringify({ prop1: 'test', prop2: 'test2' }), '{"prop1":"test","prop2":"test2"}')
|
|
663
|
+
t.equal(stringify({ prop1: 'test', prop3: 'test3' }), '{"prop1":"test","prop3":"test3"}')
|
|
664
|
+
t.equal(stringify({ prop1: 'test', prop2: 'test2', prop3: 'test3' }), '{"prop1":"test","prop2":"test2","prop3":"test3"}')
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
test('anyOf required props', (t) => {
|
|
668
|
+
t.plan(3)
|
|
669
|
+
|
|
670
|
+
const schema = {
|
|
671
|
+
type: 'object',
|
|
672
|
+
properties: {
|
|
673
|
+
prop1: { type: 'string' }
|
|
674
|
+
},
|
|
675
|
+
anyOf: [
|
|
676
|
+
{
|
|
677
|
+
properties: {
|
|
678
|
+
prop2: { type: 'string' }
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
properties: {
|
|
683
|
+
prop3: { type: 'string' }
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
]
|
|
687
|
+
}
|
|
688
|
+
const stringify = build(schema)
|
|
689
|
+
t.equal(stringify({ prop1: 'test1' }), '{"prop1":"test1"}')
|
|
690
|
+
t.equal(stringify({ prop2: 'test2' }), '{"prop2":"test2"}')
|
|
691
|
+
t.equal(stringify({ prop1: 'test1', prop2: 'test2' }), '{"prop1":"test1","prop2":"test2"}')
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
test('recursive nested anyOfs', (t) => {
|
|
695
|
+
t.plan(1)
|
|
696
|
+
|
|
697
|
+
const schema = {
|
|
698
|
+
type: 'object',
|
|
699
|
+
properties: {
|
|
700
|
+
foo: {
|
|
701
|
+
additionalProperties: false,
|
|
702
|
+
anyOf: [{ $ref: '#' }]
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const data = { foo: {} }
|
|
708
|
+
const stringify = build(schema)
|
|
709
|
+
t.equal(stringify(data), JSON.stringify(data))
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
test('recursive nested anyOfs', (t) => {
|
|
713
|
+
t.plan(1)
|
|
714
|
+
|
|
715
|
+
const schema = {
|
|
716
|
+
type: 'object',
|
|
717
|
+
properties: {
|
|
718
|
+
foo: {
|
|
719
|
+
additionalProperties: false,
|
|
720
|
+
anyOf: [{ anyOf: [{ $ref: '#' }] }]
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const data = { foo: {} }
|
|
726
|
+
const stringify = build(schema)
|
|
727
|
+
t.equal(stringify(data), JSON.stringify(data))
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
test('external recursive anyOfs', (t) => {
|
|
731
|
+
t.plan(1)
|
|
732
|
+
|
|
733
|
+
const externalSchema = {
|
|
734
|
+
type: 'object',
|
|
735
|
+
properties: {
|
|
736
|
+
foo: {
|
|
737
|
+
properties: {
|
|
738
|
+
bar: { type: 'string' }
|
|
739
|
+
},
|
|
740
|
+
anyOf: [{ $ref: '#' }]
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const schema = {
|
|
746
|
+
type: 'object',
|
|
747
|
+
properties: {
|
|
748
|
+
a: { $ref: 'externalSchema#/properties/foo' },
|
|
749
|
+
b: { $ref: 'externalSchema#/properties/foo' }
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const data = {
|
|
754
|
+
a: {
|
|
755
|
+
foo: {},
|
|
756
|
+
bar: '42',
|
|
757
|
+
baz: 42
|
|
758
|
+
},
|
|
759
|
+
b: {
|
|
760
|
+
foo: {},
|
|
761
|
+
bar: '42',
|
|
762
|
+
baz: 42
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const stringify = build(schema, { schema: { externalSchema } })
|
|
766
|
+
t.equal(stringify(data), '{"a":{"bar":"42","foo":{}},"b":{"bar":"42","foo":{}}}')
|
|
767
|
+
})
|
|
@@ -249,15 +249,7 @@ const alphabetOutput = JSON.stringify({
|
|
|
249
249
|
const deepFoobarOutput = JSON.stringify({
|
|
250
250
|
foobar: JSON.parse(foobarOutput)
|
|
251
251
|
})
|
|
252
|
-
const noElseGreetingOutput = JSON.stringify({
|
|
253
|
-
kind: 'greeting',
|
|
254
|
-
foo: 'FOO',
|
|
255
|
-
bar: 42,
|
|
256
|
-
hi: 'HI',
|
|
257
|
-
hello: 45,
|
|
258
|
-
a: 'A',
|
|
259
|
-
b: 35
|
|
260
|
-
})
|
|
252
|
+
const noElseGreetingOutput = JSON.stringify({})
|
|
261
253
|
|
|
262
254
|
t.test('if-then-else', t => {
|
|
263
255
|
const tests = [
|
|
@@ -419,3 +411,60 @@ t.test('if/else with array', (t) => {
|
|
|
419
411
|
t.equal(stringify(['1']), JSON.stringify(['1']))
|
|
420
412
|
t.equal(stringify(['1', '2']), JSON.stringify([1, 2]))
|
|
421
413
|
})
|
|
414
|
+
|
|
415
|
+
t.test('external recursive if/then/else', (t) => {
|
|
416
|
+
t.plan(1)
|
|
417
|
+
|
|
418
|
+
const externalSchema = {
|
|
419
|
+
type: 'object',
|
|
420
|
+
properties: {
|
|
421
|
+
base: { type: 'string' },
|
|
422
|
+
self: { $ref: 'externalSchema#' }
|
|
423
|
+
},
|
|
424
|
+
if: {
|
|
425
|
+
type: 'object',
|
|
426
|
+
properties: {
|
|
427
|
+
foo: { type: 'string', const: '41' }
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
then: {
|
|
431
|
+
type: 'object',
|
|
432
|
+
properties: {
|
|
433
|
+
bar: { type: 'string', const: '42' }
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
else: {
|
|
437
|
+
type: 'object',
|
|
438
|
+
properties: {
|
|
439
|
+
baz: { type: 'string', const: '43' }
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const schema = {
|
|
445
|
+
type: 'object',
|
|
446
|
+
properties: {
|
|
447
|
+
a: { $ref: 'externalSchema#/properties/self' },
|
|
448
|
+
b: { $ref: 'externalSchema#/properties/self' }
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const data = {
|
|
453
|
+
a: {
|
|
454
|
+
base: 'a',
|
|
455
|
+
foo: '41',
|
|
456
|
+
bar: '42',
|
|
457
|
+
baz: '43',
|
|
458
|
+
ignore: 'ignored'
|
|
459
|
+
},
|
|
460
|
+
b: {
|
|
461
|
+
base: 'b',
|
|
462
|
+
foo: 'not-41',
|
|
463
|
+
bar: '42',
|
|
464
|
+
baz: '43',
|
|
465
|
+
ignore: 'ignored'
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const stringify = build(schema, { schema: { externalSchema } })
|
|
469
|
+
t.equal(stringify(data), '{"a":{"base":"a","bar":"42"},"b":{"base":"b","baz":"43"}}')
|
|
470
|
+
})
|
package/test/required.test.js
CHANGED
|
@@ -126,17 +126,18 @@ test('object with multiple required field not in properties schema', (t) => {
|
|
|
126
126
|
stringify({})
|
|
127
127
|
t.fail()
|
|
128
128
|
} catch (e) {
|
|
129
|
-
t.equal(e.message, '"
|
|
129
|
+
t.equal(e.message, '"key1" is required!')
|
|
130
130
|
t.pass()
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
try {
|
|
134
134
|
stringify({
|
|
135
|
-
|
|
135
|
+
key1: 42,
|
|
136
|
+
key2: 42
|
|
136
137
|
})
|
|
137
138
|
t.fail()
|
|
138
139
|
} catch (e) {
|
|
139
|
-
t.equal(e.message, '"
|
|
140
|
+
t.equal(e.message, '"num" is required!')
|
|
140
141
|
t.pass()
|
|
141
142
|
}
|
|
142
143
|
|