fast-json-stringify
Advanced tools
Comparing version 5.10.0 to 5.11.0
503
index.js
@@ -5,10 +5,9 @@ 'use strict' | ||
const merge = require('@fastify/deepmerge')() | ||
const clone = require('rfdc')({ proto: true }) | ||
const { RefResolver } = require('json-schema-ref-resolver') | ||
const validate = require('./lib/schema-validator') | ||
const Serializer = require('./lib/serializer') | ||
const Validator = require('./lib/validator') | ||
const Location = require('./lib/location') | ||
const validate = require('./lib/schema-validator') | ||
const mergeSchemas = require('./lib/merge-schemas') | ||
@@ -32,6 +31,6 @@ const SINGLE_TICK = /'/g | ||
const addComma = '!addComma && (addComma = true) || (json += \',\')' | ||
let schemaIdCounter = 0 | ||
const mergedSchemaRef = Symbol('fjs-merged-schema-ref') | ||
function isValidSchema (schema, name) { | ||
@@ -51,3 +50,5 @@ if (!validate(schema)) { | ||
function resolveRef (context, location, ref) { | ||
function resolveRef (context, location) { | ||
const ref = location.schema.$ref | ||
let hashIndex = ref.indexOf('#') | ||
@@ -58,3 +59,3 @@ if (hashIndex === -1) { | ||
const schemaId = ref.slice(0, hashIndex) || location.getOriginSchemaId() | ||
const schemaId = ref.slice(0, hashIndex) || location.schemaId | ||
const jsonPointer = ref.slice(hashIndex) || '#' | ||
@@ -69,3 +70,3 @@ | ||
if (schema.$ref !== undefined) { | ||
return resolveRef(context, newLocation, schema.$ref) | ||
return resolveRef(context, newLocation) | ||
} | ||
@@ -76,2 +77,7 @@ | ||
function getMergedLocation (context, mergedSchemaId) { | ||
const mergedSchema = context.refResolver.getSchema(mergedSchemaId, '#') | ||
return new Location(mergedSchema, mergedSchemaId, '#') | ||
} | ||
function getSchemaId (schema, rootSchemaId) { | ||
@@ -94,3 +100,2 @@ if (schema.$id && schema.$id.charAt(0) !== '#') { | ||
options, | ||
wrapObjects: true, | ||
refResolver: new RefResolver(), | ||
@@ -265,3 +270,3 @@ rootSchemaId: schema.$id || `__fjs_root_${schemaIdCounter++}`, | ||
function buildExtraObjectPropertiesSerializer (context, location) { | ||
function buildExtraObjectPropertiesSerializer (context, location, addComma) { | ||
const schema = location.schema | ||
@@ -325,91 +330,76 @@ const propertiesKeys = Object.keys(schema.properties || {}) | ||
function buildInnerObject (context, location) { | ||
let code = '' | ||
const schema = location.schema | ||
const required = schema.required || [] | ||
const propertiesLocation = location.getPropertyLocation('properties') | ||
const requiredProperties = schema.required || [] | ||
const requiredWithDefault = [] | ||
const requiredWithoutDefault = [] | ||
if (schema.properties) { | ||
for (const key of Object.keys(schema.properties)) { | ||
if (required.indexOf(key) === -1) { | ||
continue | ||
} | ||
let propertyLocation = propertiesLocation.getPropertyLocation(key) | ||
if (propertyLocation.schema.$ref) { | ||
propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref) | ||
} | ||
// Should serialize required properties first | ||
const propertiesKeys = Object.keys(schema.properties || {}).sort( | ||
(key1, key2) => { | ||
const required1 = requiredProperties.includes(key1) | ||
const required2 = requiredProperties.includes(key2) | ||
return required1 === required2 ? 0 : required1 ? -1 : 1 | ||
} | ||
) | ||
const hasRequiredProperties = requiredProperties.includes(propertiesKeys[0]) | ||
const sanitizedKey = JSON.stringify(key) | ||
let code = '' | ||
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons, | ||
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion. | ||
const defaultValue = propertyLocation.schema.default | ||
if (defaultValue === undefined) { | ||
code += `if (obj[${sanitizedKey}] === undefined) throw new Error('${sanitizedKey} is required!')\n` | ||
requiredWithoutDefault.push(key) | ||
} | ||
requiredWithDefault.push(key) | ||
for (const key of requiredProperties) { | ||
if (!propertiesKeys.includes(key)) { | ||
code += `if (obj['${key}'] === undefined) throw new Error('"${key}" is required!')\n` | ||
} | ||
} | ||
// handle extraneous required fields | ||
for (const requiredProperty of required) { | ||
if (requiredWithDefault.indexOf(requiredProperty) !== -1) continue | ||
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n` | ||
code += 'let json = \'{\'\n' | ||
let addComma = '' | ||
if (!hasRequiredProperties) { | ||
code += 'let addComma = false\n' | ||
addComma = '!addComma && (addComma = true) || (json += \',\')' | ||
} | ||
code += ` | ||
let addComma = false | ||
let json = '${context.wrapObjects ? '{' : ''}' | ||
` | ||
const wrapObjects = context.wrapObjects | ||
context.wrapObjects = true | ||
for (const key of propertiesKeys) { | ||
let propertyLocation = propertiesLocation.getPropertyLocation(key) | ||
if (propertyLocation.schema.$ref) { | ||
propertyLocation = resolveRef(context, propertyLocation) | ||
} | ||
if (schema.properties) { | ||
for (const key of Object.keys(schema.properties)) { | ||
let propertyLocation = propertiesLocation.getPropertyLocation(key) | ||
if (propertyLocation.schema.$ref) { | ||
propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref) | ||
} | ||
const sanitizedKey = JSON.stringify(key) | ||
const defaultValue = propertyLocation.schema.default | ||
const isRequired = requiredProperties.includes(key) | ||
const sanitizedKey = JSON.stringify(key) | ||
if (requiredWithoutDefault.indexOf(key) !== -1) { | ||
code += ` | ||
code += ` | ||
if (obj[${sanitizedKey}] !== undefined) { | ||
${addComma} | ||
json += ${JSON.stringify(sanitizedKey + ':')} | ||
${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)} | ||
}` | ||
if (defaultValue !== undefined) { | ||
code += ` else { | ||
${addComma} | ||
json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))} | ||
} | ||
` | ||
} else { | ||
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons, | ||
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion. | ||
code += ` | ||
if (obj[${sanitizedKey}] !== undefined) { | ||
${addComma} | ||
json += ${JSON.stringify(sanitizedKey + ':')} | ||
${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)} | ||
} | ||
` | ||
const defaultValue = propertyLocation.schema.default | ||
if (defaultValue !== undefined) { | ||
code += ` | ||
else { | ||
${addComma} | ||
json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))} | ||
} | ||
` | ||
} | ||
} else if (isRequired) { | ||
code += ` else { | ||
throw new Error('${sanitizedKey} is required!') | ||
} | ||
` | ||
} else { | ||
code += '\n' | ||
} | ||
if (hasRequiredProperties) { | ||
addComma = 'json += \',\'' | ||
} | ||
} | ||
if (schema.patternProperties || schema.additionalProperties) { | ||
code += buildExtraObjectPropertiesSerializer(context, location) | ||
code += buildExtraObjectPropertiesSerializer(context, location, addComma) | ||
} | ||
context.wrapObjects = wrapObjects | ||
code += ` | ||
return json${context.wrapObjects ? ' + \'}\'' : ''} | ||
return json + '}' | ||
` | ||
@@ -419,130 +409,57 @@ return code | ||
function mergeAllOfSchema (context, location, schema, mergedSchema) { | ||
const allOfLocation = location.getPropertyLocation('allOf') | ||
for (let i = 0; i < schema.allOf.length; i++) { | ||
let allOfSchema = schema.allOf[i] | ||
if (allOfSchema.$ref) { | ||
const allOfSchemaLocation = allOfLocation.getPropertyLocation(i) | ||
allOfSchema = resolveRef(context, allOfSchemaLocation, allOfSchema.$ref).schema | ||
function mergeLocations (context, mergedSchemaId, mergedLocations) { | ||
for (let i = 0; i < mergedLocations.length; i++) { | ||
const location = mergedLocations[i] | ||
const schema = location.schema | ||
if (schema.$ref) { | ||
mergedLocations[i] = resolveRef(context, location) | ||
} | ||
} | ||
let allOfSchemaType = allOfSchema.type | ||
if (allOfSchemaType === undefined) { | ||
allOfSchemaType = inferTypeByKeyword(allOfSchema) | ||
} | ||
const mergedSchemas = [] | ||
for (const location of mergedLocations) { | ||
const schema = cloneOriginSchema(location.schema, location.schemaId) | ||
delete schema.$id | ||
if (allOfSchemaType !== undefined) { | ||
if ( | ||
mergedSchema.type !== undefined && | ||
mergedSchema.type !== allOfSchemaType | ||
) { | ||
throw new Error('allOf schemas have different type values') | ||
} | ||
mergedSchema.type = allOfSchemaType | ||
} | ||
mergedSchemas.push(schema) | ||
} | ||
if (allOfSchema.format !== undefined) { | ||
if ( | ||
mergedSchema.format !== undefined && | ||
mergedSchema.format !== allOfSchema.format | ||
) { | ||
throw new Error('allOf schemas have different format values') | ||
} | ||
mergedSchema.format = allOfSchema.format | ||
} | ||
const mergedSchema = mergeSchemas(mergedSchemas) | ||
const mergedLocation = new Location(mergedSchema, mergedSchemaId) | ||
if (allOfSchema.nullable !== undefined) { | ||
if ( | ||
mergedSchema.nullable !== undefined && | ||
mergedSchema.nullable !== allOfSchema.nullable | ||
) { | ||
throw new Error('allOf schemas have different nullable values') | ||
} | ||
mergedSchema.nullable = allOfSchema.nullable | ||
} | ||
context.refResolver.addSchema(mergedSchema, mergedSchemaId) | ||
return mergedLocation | ||
} | ||
if (allOfSchema.properties !== undefined) { | ||
if (mergedSchema.properties === undefined) { | ||
mergedSchema.properties = {} | ||
} | ||
Object.assign(mergedSchema.properties, allOfSchema.properties) | ||
} | ||
function cloneOriginSchema (schema, schemaId) { | ||
const clonedSchema = Array.isArray(schema) ? [] : {} | ||
if (allOfSchema.additionalProperties !== undefined) { | ||
if (mergedSchema.additionalProperties === undefined) { | ||
mergedSchema.additionalProperties = {} | ||
} | ||
Object.assign(mergedSchema.additionalProperties, allOfSchema.additionalProperties) | ||
} | ||
if ( | ||
schema.$id !== undefined && | ||
schema.$id.charAt(0) !== '#' | ||
) { | ||
schemaId = schema.$id | ||
} | ||
if (allOfSchema.patternProperties !== undefined) { | ||
if (mergedSchema.patternProperties === undefined) { | ||
mergedSchema.patternProperties = {} | ||
} | ||
Object.assign(mergedSchema.patternProperties, allOfSchema.patternProperties) | ||
} | ||
if (schema[mergedSchemaRef]) { | ||
clonedSchema[mergedSchemaRef] = schema[mergedSchemaRef] | ||
} | ||
if (allOfSchema.required !== undefined) { | ||
if (mergedSchema.required === undefined) { | ||
mergedSchema.required = [] | ||
} | ||
mergedSchema.required.push(...allOfSchema.required) | ||
} | ||
for (const key in schema) { | ||
let value = schema[key] | ||
if (allOfSchema.oneOf !== undefined) { | ||
if (mergedSchema.oneOf === undefined) { | ||
mergedSchema.oneOf = [] | ||
} | ||
mergedSchema.oneOf.push(...allOfSchema.oneOf) | ||
if (key === '$ref' && value.charAt(0) === '#') { | ||
value = schemaId + value | ||
} | ||
if (allOfSchema.anyOf !== undefined) { | ||
if (mergedSchema.anyOf === undefined) { | ||
mergedSchema.anyOf = [] | ||
} | ||
mergedSchema.anyOf.push(...allOfSchema.anyOf) | ||
if (typeof value === 'object' && value !== null) { | ||
value = cloneOriginSchema(value, schemaId) | ||
} | ||
if (allOfSchema.allOf !== undefined) { | ||
mergeAllOfSchema(context, location, allOfSchema, mergedSchema) | ||
} | ||
clonedSchema[key] = value | ||
} | ||
delete mergedSchema.allOf | ||
mergedSchema.$id = `__fjs_merged_${schemaIdCounter++}` | ||
context.refResolver.addSchema(mergedSchema) | ||
location.addMergedSchema(mergedSchema, mergedSchema.$id) | ||
return clonedSchema | ||
} | ||
function addIfThenElse (context, location, input) { | ||
context.validatorSchemasIds.add(location.getSchemaId()) | ||
const schema = merge({}, location.schema) | ||
const thenSchema = schema.then | ||
const elseSchema = schema.else || { additionalProperties: true } | ||
delete schema.if | ||
delete schema.then | ||
delete schema.else | ||
const ifLocation = location.getPropertyLocation('if') | ||
const ifSchemaRef = ifLocation.getSchemaRef() | ||
const thenLocation = location.getPropertyLocation('then') | ||
thenLocation.schema = merge(schema, thenSchema) | ||
const elseLocation = location.getPropertyLocation('else') | ||
elseLocation.schema = merge(schema, elseSchema) | ||
return ` | ||
if (validator.validate("${ifSchemaRef}", ${input})) { | ||
${buildValue(context, thenLocation, input)} | ||
} else { | ||
${buildValue(context, elseLocation, input)} | ||
} | ||
` | ||
} | ||
function toJSON (variableName) { | ||
@@ -595,3 +512,3 @@ return `(${variableName} && typeof ${variableName}.toJSON === 'function') | ||
if (itemsLocation.schema.$ref) { | ||
itemsLocation = resolveRef(context, itemsLocation, itemsLocation.schema.$ref) | ||
itemsLocation = resolveRef(context, itemsLocation) | ||
} | ||
@@ -874,77 +791,189 @@ | ||
function buildValue (context, location, input) { | ||
let schema = location.schema | ||
function buildAllOf (context, location, input) { | ||
const schema = location.schema | ||
if (typeof schema === 'boolean') { | ||
return `json += JSON.stringify(${input})` | ||
let mergedSchemaId = schema[mergedSchemaRef] | ||
if (mergedSchemaId) { | ||
const mergedLocation = getMergedLocation(context, mergedSchemaId) | ||
return buildValue(context, mergedLocation, input) | ||
} | ||
if (schema.$ref) { | ||
location = resolveRef(context, location, schema.$ref) | ||
schema = location.schema | ||
mergedSchemaId = `__fjs_merged_${schemaIdCounter++}` | ||
schema[mergedSchemaRef] = mergedSchemaId | ||
const { allOf, ...schemaWithoutAllOf } = location.schema | ||
const locations = [ | ||
new Location( | ||
schemaWithoutAllOf, | ||
location.schemaId, | ||
location.jsonPointer | ||
) | ||
] | ||
const allOfsLocation = location.getPropertyLocation('allOf') | ||
for (let i = 0; i < allOf.length; i++) { | ||
locations.push(allOfsLocation.getPropertyLocation(i)) | ||
} | ||
if (schema.type === undefined) { | ||
const inferredType = inferTypeByKeyword(schema) | ||
if (inferredType) { | ||
schema.type = inferredType | ||
const mergedLocation = mergeLocations(context, mergedSchemaId, locations) | ||
return buildValue(context, mergedLocation, input) | ||
} | ||
function buildOneOf (context, location, input) { | ||
context.validatorSchemasIds.add(location.schemaId) | ||
const schema = location.schema | ||
const type = schema.anyOf ? 'anyOf' : 'oneOf' | ||
const { [type]: oneOfs, ...schemaWithoutAnyOf } = location.schema | ||
const locationWithoutOneOf = new Location( | ||
schemaWithoutAnyOf, | ||
location.schemaId, | ||
location.jsonPointer | ||
) | ||
const oneOfsLocation = location.getPropertyLocation(type) | ||
let code = '' | ||
for (let index = 0; index < oneOfs.length; index++) { | ||
const optionLocation = oneOfsLocation.getPropertyLocation(index) | ||
const optionSchema = optionLocation.schema | ||
let mergedSchemaId = optionSchema[mergedSchemaRef] | ||
let mergedLocation = null | ||
if (mergedSchemaId) { | ||
mergedLocation = getMergedLocation(context, mergedSchemaId) | ||
} else { | ||
mergedSchemaId = `__fjs_merged_${schemaIdCounter++}` | ||
optionSchema[mergedSchemaRef] = mergedSchemaId | ||
mergedLocation = mergeLocations(context, mergedSchemaId, [ | ||
locationWithoutOneOf, | ||
optionLocation | ||
]) | ||
} | ||
const nestedResult = buildValue(context, mergedLocation, input) | ||
const schemaRef = optionLocation.getSchemaRef() | ||
code += ` | ||
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input})) | ||
${nestedResult} | ||
` | ||
} | ||
if (schema.if && schema.then) { | ||
return addIfThenElse(context, location, input) | ||
let schemaRef = location.getSchemaRef() | ||
if (schemaRef.startsWith(context.rootSchemaId)) { | ||
schemaRef = schemaRef.replace(context.rootSchemaId, '') | ||
} | ||
if (schema.allOf) { | ||
mergeAllOfSchema(context, location, schema, clone(schema)) | ||
schema = location.schema | ||
code += ` | ||
else throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`) | ||
` | ||
return code | ||
} | ||
function buildIfThenElse (context, location, input) { | ||
context.validatorSchemasIds.add(location.schemaId) | ||
const { | ||
if: ifSchema, | ||
then: thenSchema, | ||
else: elseSchema, | ||
...schemaWithoutIfThenElse | ||
} = location.schema | ||
const rootLocation = new Location( | ||
schemaWithoutIfThenElse, | ||
location.schemaId, | ||
location.jsonPointer | ||
) | ||
const ifLocation = location.getPropertyLocation('if') | ||
const ifSchemaRef = ifLocation.getSchemaRef() | ||
const thenLocation = location.getPropertyLocation('then') | ||
let thenMergedSchemaId = thenSchema[mergedSchemaRef] | ||
let thenMergedLocation = null | ||
if (thenMergedSchemaId) { | ||
thenMergedLocation = getMergedLocation(context, thenMergedSchemaId) | ||
} else { | ||
thenMergedSchemaId = `__fjs_merged_${schemaIdCounter++}` | ||
thenSchema[mergedSchemaRef] = thenMergedSchemaId | ||
thenMergedLocation = mergeLocations(context, thenMergedSchemaId, [ | ||
rootLocation, | ||
thenLocation | ||
]) | ||
} | ||
const type = schema.type | ||
if (!elseSchema) { | ||
return ` | ||
if (validator.validate("${ifSchemaRef}", ${input})) { | ||
${buildValue(context, thenMergedLocation, input)} | ||
} else { | ||
${buildValue(context, rootLocation, input)} | ||
} | ||
` | ||
} | ||
let code = '' | ||
const elseLocation = location.getPropertyLocation('else') | ||
let elseMergedSchemaId = elseSchema[mergedSchemaRef] | ||
let elseMergedLocation = null | ||
if (elseMergedSchemaId) { | ||
elseMergedLocation = getMergedLocation(context, elseMergedSchemaId) | ||
} else { | ||
elseMergedSchemaId = `__fjs_merged_${schemaIdCounter++}` | ||
elseSchema[mergedSchemaRef] = elseMergedSchemaId | ||
if ((type === undefined || type === 'object') && (schema.anyOf || schema.oneOf)) { | ||
context.validatorSchemasIds.add(location.getSchemaId()) | ||
elseMergedLocation = mergeLocations(context, elseMergedSchemaId, [ | ||
rootLocation, | ||
elseLocation | ||
]) | ||
} | ||
if (schema.type === 'object') { | ||
context.wrapObjects = false | ||
const funcName = buildObject(context, location) | ||
code += ` | ||
json += '{' | ||
json += ${funcName}(${input}) | ||
json += ',' | ||
` | ||
return ` | ||
if (validator.validate("${ifSchemaRef}", ${input})) { | ||
${buildValue(context, thenMergedLocation, input)} | ||
} else { | ||
${buildValue(context, elseMergedLocation, input)} | ||
} | ||
` | ||
} | ||
const type = schema.anyOf ? 'anyOf' : 'oneOf' | ||
const anyOfLocation = location.getPropertyLocation(type) | ||
function buildValue (context, location, input) { | ||
let schema = location.schema | ||
for (let index = 0; index < location.schema[type].length; index++) { | ||
const optionLocation = anyOfLocation.getPropertyLocation(index) | ||
const schemaRef = optionLocation.getSchemaRef() | ||
const nestedResult = buildValue(context, optionLocation, input) | ||
code += ` | ||
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input})) | ||
${nestedResult} | ||
` | ||
} | ||
if (typeof schema === 'boolean') { | ||
return `json += JSON.stringify(${input})` | ||
} | ||
let schemaRef = location.getSchemaRef() | ||
if (schemaRef.startsWith(context.rootSchemaId)) { | ||
schemaRef = schemaRef.replace(context.rootSchemaId, '') | ||
} | ||
if (schema.$ref) { | ||
location = resolveRef(context, location) | ||
schema = location.schema | ||
} | ||
code += ` | ||
else throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`) | ||
` | ||
if (schema.type === 'object') { | ||
code += ` | ||
json += '}' | ||
` | ||
context.wrapObjects = true | ||
if (schema.allOf) { | ||
return buildAllOf(context, location, input) | ||
} | ||
if (schema.anyOf || schema.oneOf) { | ||
return buildOneOf(context, location, input) | ||
} | ||
if (schema.if && schema.then) { | ||
return buildIfThenElse(context, location, input) | ||
} | ||
if (schema.type === undefined) { | ||
const inferredType = inferTypeByKeyword(schema) | ||
if (inferredType) { | ||
schema.type = inferredType | ||
} | ||
return code | ||
} | ||
let code = '' | ||
const type = schema.type | ||
const nullable = schema.nullable === true | ||
@@ -951,0 +980,0 @@ if (nullable) { |
@@ -8,3 +8,2 @@ 'use strict' | ||
this.jsonPointer = jsonPointer | ||
this.mergedSchemaId = null | ||
} | ||
@@ -18,38 +17,10 @@ | ||
) | ||
if (this.mergedSchemaId !== null) { | ||
propertyLocation.addMergedSchema( | ||
this.schema[propertyName], | ||
this.mergedSchemaId, | ||
this.jsonPointer + '/' + propertyName | ||
) | ||
} | ||
return propertyLocation | ||
} | ||
// Use this method to get current schema location. | ||
// Use it when you need to create reference to the current location. | ||
getSchemaId () { | ||
return this.mergedSchemaId || this.schemaId | ||
} | ||
// Use this method to get original schema id for resolving user schema $refs | ||
// Don't join it with a JSON pointer to get the current location. | ||
getOriginSchemaId () { | ||
return this.schemaId | ||
} | ||
getSchemaRef () { | ||
const schemaId = this.getSchemaId() | ||
return schemaId + this.jsonPointer | ||
return this.schemaId + this.jsonPointer | ||
} | ||
addMergedSchema (mergedSchema, schemaId, jsonPointer = '#') { | ||
this.schema = mergedSchema | ||
this.mergedSchemaId = schemaId | ||
this.jsonPointer = jsonPointer | ||
} | ||
} | ||
module.exports = Location |
{ | ||
"name": "fast-json-stringify", | ||
"version": "5.10.0", | ||
"version": "5.11.0", | ||
"description": "Stringify your JSON at max speed", | ||
@@ -49,3 +49,3 @@ "main": "index.js", | ||
"tap": "^16.0.1", | ||
"tsd": "^0.29.0", | ||
"tsd": "^0.30.3", | ||
"webpack": "^5.40.0", | ||
@@ -55,3 +55,2 @@ "fast-json-stringify": "." | ||
"dependencies": { | ||
"@fastify/deepmerge": "^1.0.0", | ||
"ajv": "^8.10.0", | ||
@@ -62,3 +61,4 @@ "ajv-formats": "^2.1.1", | ||
"rfdc": "^1.2.0", | ||
"json-schema-ref-resolver": "^1.0.1" | ||
"json-schema-ref-resolver": "^1.0.1", | ||
"@fastify/merge-json-schemas": "^0.1.0" | ||
}, | ||
@@ -65,0 +65,0 @@ "standard": { |
@@ -124,3 +124,3 @@ 'use strict' | ||
tag: 'otherString' | ||
}), '{"name":"string","tag":"otherString","id":1}') | ||
}), '{"name":"string","id":1,"tag":"otherString"}') | ||
}) | ||
@@ -557,4 +557,56 @@ | ||
test('allOf: multiple nested $ref properties', (t) => { | ||
t.plan(2) | ||
const externalSchema1 = { | ||
$id: 'externalSchema1', | ||
oneOf: [ | ||
{ $ref: '#/definitions/id1' } | ||
], | ||
definitions: { | ||
id1: { | ||
type: 'object', | ||
properties: { | ||
id1: { | ||
type: 'integer' | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
} | ||
} | ||
const externalSchema2 = { | ||
$id: 'externalSchema2', | ||
oneOf: [ | ||
{ $ref: '#/definitions/id2' } | ||
], | ||
definitions: { | ||
id2: { | ||
type: 'object', | ||
properties: { | ||
id2: { | ||
type: 'integer' | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
} | ||
} | ||
const schema = { | ||
anyOf: [ | ||
{ $ref: 'externalSchema1' }, | ||
{ $ref: 'externalSchema2' } | ||
] | ||
} | ||
const stringify = build(schema, { schema: [externalSchema1, externalSchema2] }) | ||
t.equal(stringify({ id1: 1 }), JSON.stringify({ id1: 1 })) | ||
t.equal(stringify({ id2: 2 }), JSON.stringify({ id2: 2 })) | ||
}) | ||
test('allOf: throw Error if types mismatch ', (t) => { | ||
t.plan(1) | ||
t.plan(3) | ||
@@ -567,7 +619,14 @@ const schema = { | ||
} | ||
t.throws(() => build(schema), new Error('allOf schemas have different type values')) | ||
try { | ||
build(schema) | ||
t.fail('should throw the MergeError') | ||
} catch (error) { | ||
t.ok(error instanceof Error) | ||
t.equal(error.message, 'Failed to merge "type" keyword schemas.') | ||
t.same(error.schemas, [['string'], ['number']]) | ||
} | ||
}) | ||
test('allOf: throw Error if format mismatch ', (t) => { | ||
t.plan(1) | ||
t.plan(3) | ||
@@ -580,27 +639,85 @@ const schema = { | ||
} | ||
t.throws(() => build(schema), new Error('allOf schemas have different format values')) | ||
try { | ||
build(schema) | ||
t.fail('should throw the MergeError') | ||
} catch (error) { | ||
t.ok(error instanceof Error) | ||
t.equal(error.message, 'Failed to merge "format" keyword schemas.') | ||
t.same(error.schemas, ['date', 'time']) | ||
} | ||
}) | ||
test('allOf: throw Error if nullable mismatch /1', (t) => { | ||
test('recursive nested allOfs', (t) => { | ||
t.plan(1) | ||
const schema = { | ||
allOf: [ | ||
{ nullable: true }, | ||
{ nullable: false } | ||
] | ||
type: 'object', | ||
properties: { | ||
foo: { | ||
additionalProperties: false, | ||
allOf: [{ $ref: '#' }] | ||
} | ||
} | ||
} | ||
t.throws(() => build(schema), new Error('allOf schemas have different nullable values')) | ||
const data = { foo: {} } | ||
const stringify = build(schema) | ||
t.equal(stringify(data), JSON.stringify(data)) | ||
}) | ||
test('allOf: throw Error if nullable mismatch /2', (t) => { | ||
test('recursive nested allOfs', (t) => { | ||
t.plan(1) | ||
const schema = { | ||
allOf: [ | ||
{ nullable: false }, | ||
{ nullable: true } | ||
] | ||
type: 'object', | ||
properties: { | ||
foo: { | ||
additionalProperties: false, | ||
allOf: [{ allOf: [{ $ref: '#' }] }] | ||
} | ||
} | ||
} | ||
t.throws(() => build(schema), new Error('allOf schemas have different nullable values')) | ||
const data = { foo: {} } | ||
const stringify = build(schema) | ||
t.equal(stringify(data), JSON.stringify(data)) | ||
}) | ||
test('external recursive allOfs', (t) => { | ||
t.plan(1) | ||
const externalSchema = { | ||
type: 'object', | ||
properties: { | ||
foo: { | ||
properties: { | ||
bar: { type: 'string' } | ||
}, | ||
allOf: [{ $ref: '#' }] | ||
} | ||
} | ||
} | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
a: { $ref: 'externalSchema#/properties/foo' }, | ||
b: { $ref: 'externalSchema#/properties/foo' } | ||
} | ||
} | ||
const data = { | ||
a: { | ||
foo: {}, | ||
bar: '42', | ||
baz: 42 | ||
}, | ||
b: { | ||
foo: {}, | ||
bar: '42', | ||
baz: 42 | ||
} | ||
} | ||
const stringify = build(schema, { schema: { externalSchema } }) | ||
t.equal(stringify(data), '{"a":{"bar":"42","foo":{}},"b":{"bar":"42","foo":{}}}') | ||
}) |
@@ -647,1 +647,122 @@ 'use strict' | ||
}) | ||
test('anyOf required props', (t) => { | ||
t.plan(3) | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
prop1: { type: 'string' }, | ||
prop2: { type: 'string' }, | ||
prop3: { type: 'string' } | ||
}, | ||
required: ['prop1'], | ||
anyOf: [{ required: ['prop2'] }, { required: ['prop3'] }] | ||
} | ||
const stringify = build(schema) | ||
t.equal(stringify({ prop1: 'test', prop2: 'test2' }), '{"prop1":"test","prop2":"test2"}') | ||
t.equal(stringify({ prop1: 'test', prop3: 'test3' }), '{"prop1":"test","prop3":"test3"}') | ||
t.equal(stringify({ prop1: 'test', prop2: 'test2', prop3: 'test3' }), '{"prop1":"test","prop2":"test2","prop3":"test3"}') | ||
}) | ||
test('anyOf required props', (t) => { | ||
t.plan(3) | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
prop1: { type: 'string' } | ||
}, | ||
anyOf: [ | ||
{ | ||
properties: { | ||
prop2: { type: 'string' } | ||
} | ||
}, | ||
{ | ||
properties: { | ||
prop3: { type: 'string' } | ||
} | ||
} | ||
] | ||
} | ||
const stringify = build(schema) | ||
t.equal(stringify({ prop1: 'test1' }), '{"prop1":"test1"}') | ||
t.equal(stringify({ prop2: 'test2' }), '{"prop2":"test2"}') | ||
t.equal(stringify({ prop1: 'test1', prop2: 'test2' }), '{"prop1":"test1","prop2":"test2"}') | ||
}) | ||
test('recursive nested anyOfs', (t) => { | ||
t.plan(1) | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
foo: { | ||
additionalProperties: false, | ||
anyOf: [{ $ref: '#' }] | ||
} | ||
} | ||
} | ||
const data = { foo: {} } | ||
const stringify = build(schema) | ||
t.equal(stringify(data), JSON.stringify(data)) | ||
}) | ||
test('recursive nested anyOfs', (t) => { | ||
t.plan(1) | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
foo: { | ||
additionalProperties: false, | ||
anyOf: [{ anyOf: [{ $ref: '#' }] }] | ||
} | ||
} | ||
} | ||
const data = { foo: {} } | ||
const stringify = build(schema) | ||
t.equal(stringify(data), JSON.stringify(data)) | ||
}) | ||
test('external recursive anyOfs', (t) => { | ||
t.plan(1) | ||
const externalSchema = { | ||
type: 'object', | ||
properties: { | ||
foo: { | ||
properties: { | ||
bar: { type: 'string' } | ||
}, | ||
anyOf: [{ $ref: '#' }] | ||
} | ||
} | ||
} | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
a: { $ref: 'externalSchema#/properties/foo' }, | ||
b: { $ref: 'externalSchema#/properties/foo' } | ||
} | ||
} | ||
const data = { | ||
a: { | ||
foo: {}, | ||
bar: '42', | ||
baz: 42 | ||
}, | ||
b: { | ||
foo: {}, | ||
bar: '42', | ||
baz: 42 | ||
} | ||
} | ||
const stringify = build(schema, { schema: { externalSchema } }) | ||
t.equal(stringify(data), '{"a":{"bar":"42","foo":{}},"b":{"bar":"42","foo":{}}}') | ||
}) |
@@ -252,11 +252,3 @@ 'use strict' | ||
}) | ||
const noElseGreetingOutput = JSON.stringify({ | ||
kind: 'greeting', | ||
foo: 'FOO', | ||
bar: 42, | ||
hi: 'HI', | ||
hello: 45, | ||
a: 'A', | ||
b: 35 | ||
}) | ||
const noElseGreetingOutput = JSON.stringify({}) | ||
@@ -423,1 +415,58 @@ t.test('if-then-else', t => { | ||
}) | ||
t.test('external recursive if/then/else', (t) => { | ||
t.plan(1) | ||
const externalSchema = { | ||
type: 'object', | ||
properties: { | ||
base: { type: 'string' }, | ||
self: { $ref: 'externalSchema#' } | ||
}, | ||
if: { | ||
type: 'object', | ||
properties: { | ||
foo: { type: 'string', const: '41' } | ||
} | ||
}, | ||
then: { | ||
type: 'object', | ||
properties: { | ||
bar: { type: 'string', const: '42' } | ||
} | ||
}, | ||
else: { | ||
type: 'object', | ||
properties: { | ||
baz: { type: 'string', const: '43' } | ||
} | ||
} | ||
} | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
a: { $ref: 'externalSchema#/properties/self' }, | ||
b: { $ref: 'externalSchema#/properties/self' } | ||
} | ||
} | ||
const data = { | ||
a: { | ||
base: 'a', | ||
foo: '41', | ||
bar: '42', | ||
baz: '43', | ||
ignore: 'ignored' | ||
}, | ||
b: { | ||
base: 'b', | ||
foo: 'not-41', | ||
bar: '42', | ||
baz: '43', | ||
ignore: 'ignored' | ||
} | ||
} | ||
const stringify = build(schema, { schema: { externalSchema } }) | ||
t.equal(stringify(data), '{"a":{"base":"a","bar":"42"},"b":{"base":"b","baz":"43"}}') | ||
}) |
@@ -129,3 +129,3 @@ 'use strict' | ||
} catch (e) { | ||
t.equal(e.message, '"num" is required!') | ||
t.equal(e.message, '"key1" is required!') | ||
t.pass() | ||
@@ -136,7 +136,8 @@ } | ||
stringify({ | ||
num: 42 | ||
key1: 42, | ||
key2: 42 | ||
}) | ||
t.fail() | ||
} catch (e) { | ||
t.equal(e.message, '"key1" is required!') | ||
t.equal(e.message, '"num" is required!') | ||
t.pass() | ||
@@ -143,0 +144,0 @@ } |
360602
81
13533
+ Added@fastify/merge-json-schemas@0.1.1(transitive)
+ Addedrfdc@1.3.1(transitive)
- Removed@fastify/deepmerge@^1.0.0
- Removed@fastify/deepmerge@1.3.0(transitive)
- Removedrfdc@1.4.1(transitive)