From 9dd8cb6c5da1b80856207ea08cea76e0e1964b91 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 1 Feb 2024 11:53:31 +0100 Subject: [PATCH] Revert "fix: update internal schema merging (#665)" This reverts commit d95ef87f8d02669142b4c43445c6337465e9e060. --- index.js | 397 +++++++++++++++++--------------------- lib/location.js | 31 ++- lib/merge-schemas.js | 9 - package.json | 4 +- test/allof.test.js | 149 ++------------ test/anyof.test.js | 121 ------------ test/if-then-else.test.js | 67 +------ 7 files changed, 234 insertions(+), 544 deletions(-) delete mode 100644 lib/merge-schemas.js diff --git a/index.js b/index.js index 54542411..20dd386b 100644 --- a/index.js +++ b/index.js @@ -2,13 +2,14 @@ /* eslint no-prototype-builtins: 0 */ +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') const SINGLE_TICK = /'/g @@ -31,8 +32,6 @@ const addComma = '!addComma && (addComma = true) || (json += \',\')' let schemaIdCounter = 0 -const mergedSchemaRef = Symbol('fjs-merged-schema-ref') - function isValidSchema (schema, name) { if (!validate(schema)) { if (name) { @@ -47,15 +46,13 @@ function isValidSchema (schema, name) { } } -function resolveRef (context, location) { - const ref = location.schema.$ref - +function resolveRef (context, location, ref) { let hashIndex = ref.indexOf('#') if (hashIndex === -1) { hashIndex = ref.length } - const schemaId = ref.slice(0, hashIndex) || location.schemaId + const schemaId = ref.slice(0, hashIndex) || location.getOriginSchemaId() const jsonPointer = ref.slice(hashIndex) || '#' const schema = context.refResolver.getSchema(schemaId, jsonPointer) @@ -65,17 +62,12 @@ function resolveRef (context, location) { const newLocation = new Location(schema, schemaId, jsonPointer) if (schema.$ref !== undefined) { - return resolveRef(context, newLocation) + return resolveRef(context, newLocation, schema.$ref) } return newLocation } -function getMergedLocation (context, mergedSchemaId) { - const mergedSchema = context.refResolver.getSchema(mergedSchemaId, '#') - return new Location(mergedSchema, mergedSchemaId, '#') -} - function getSchemaId (schema, rootSchemaId) { if (schema.$id && schema.$id.charAt(0) !== '#') { return schema.$id @@ -93,6 +85,7 @@ function build (schema, options) { functionsCounter: 0, functionsNamesBySchema: new Map(), options, + wrapObjects: true, refResolver: new RefResolver(), rootSchemaId: schema.$id || `__fjs_root_${schemaIdCounter++}`, validatorSchemasIds: new Set() @@ -336,7 +329,7 @@ function buildInnerObject (context, location) { } let propertyLocation = propertiesLocation.getPropertyLocation(key) if (propertyLocation.schema.$ref) { - propertyLocation = resolveRef(context, propertyLocation) + propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref) } const sanitizedKey = JSON.stringify(key) @@ -360,14 +353,16 @@ function buildInnerObject (context, location) { code += ` let addComma = false - let json = '{' + let json = '${context.wrapObjects ? '{' : ''}' ` + const wrapObjects = context.wrapObjects + context.wrapObjects = true if (schema.properties) { for (const key of Object.keys(schema.properties)) { let propertyLocation = propertiesLocation.getPropertyLocation(key) if (propertyLocation.schema.$ref) { - propertyLocation = resolveRef(context, propertyLocation) + propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref) } const sanitizedKey = JSON.stringify(key) @@ -405,65 +400,139 @@ function buildInnerObject (context, location) { code += buildExtraObjectPropertiesSerializer(context, location) } + context.wrapObjects = wrapObjects code += ` - return json + '}' + return json${context.wrapObjects ? ' + \'}\'' : ''} ` return code } -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) +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 } - } - const mergedSchemas = [] - for (const location of mergedLocations) { - const schema = cloneOriginSchema(location.schema, location.schemaId) - delete schema.$id + let allOfSchemaType = allOfSchema.type + if (allOfSchemaType === undefined) { + allOfSchemaType = inferTypeByKeyword(allOfSchema) + } - mergedSchemas.push(schema) - } + if (allOfSchemaType !== undefined) { + if ( + mergedSchema.type !== undefined && + mergedSchema.type !== allOfSchemaType + ) { + throw new Error('allOf schemas have different type values') + } + mergedSchema.type = allOfSchemaType + } - const mergedSchema = mergeSchemas(mergedSchemas) - const mergedLocation = new Location(mergedSchema, mergedSchemaId) + 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 + } - context.refResolver.addSchema(mergedSchema, mergedSchemaId) - return mergedLocation -} + 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 + } -function cloneOriginSchema (schema, schemaId) { - const clonedSchema = Array.isArray(schema) ? [] : {} + if (allOfSchema.properties !== undefined) { + if (mergedSchema.properties === undefined) { + mergedSchema.properties = {} + } + Object.assign(mergedSchema.properties, allOfSchema.properties) + } - if ( - schema.$id !== undefined && - schema.$id.charAt(0) !== '#' - ) { - schemaId = schema.$id - } + if (allOfSchema.additionalProperties !== undefined) { + if (mergedSchema.additionalProperties === undefined) { + mergedSchema.additionalProperties = {} + } + Object.assign(mergedSchema.additionalProperties, allOfSchema.additionalProperties) + } - if (schema[mergedSchemaRef]) { - clonedSchema[mergedSchemaRef] = schema[mergedSchemaRef] - } + if (allOfSchema.patternProperties !== undefined) { + if (mergedSchema.patternProperties === undefined) { + mergedSchema.patternProperties = {} + } + Object.assign(mergedSchema.patternProperties, allOfSchema.patternProperties) + } - for (const key in schema) { - let value = schema[key] + if (allOfSchema.required !== undefined) { + if (mergedSchema.required === undefined) { + mergedSchema.required = [] + } + mergedSchema.required.push(...allOfSchema.required) + } - if (key === '$ref' && value.charAt(0) === '#') { - value = schemaId + value + if (allOfSchema.oneOf !== undefined) { + if (mergedSchema.oneOf === undefined) { + mergedSchema.oneOf = [] + } + mergedSchema.oneOf.push(...allOfSchema.oneOf) } - if (typeof value === 'object' && value !== null) { - value = cloneOriginSchema(value, schemaId) + if (allOfSchema.anyOf !== undefined) { + if (mergedSchema.anyOf === undefined) { + mergedSchema.anyOf = [] + } + mergedSchema.anyOf.push(...allOfSchema.anyOf) } - clonedSchema[key] = value + if (allOfSchema.allOf !== undefined) { + mergeAllOfSchema(context, location, allOfSchema, mergedSchema) + } } + delete mergedSchema.allOf - return clonedSchema + mergedSchema.$id = `__fjs_merged_${schemaIdCounter++}` + context.refResolver.addSchema(mergedSchema) + location.addMergedSchema(mergedSchema, mergedSchema.$id) +} + +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) { @@ -513,7 +582,7 @@ function buildArray (context, location) { itemsLocation.schema = itemsLocation.schema || {} if (itemsLocation.schema.$ref) { - itemsLocation = resolveRef(context, itemsLocation) + itemsLocation = resolveRef(context, itemsLocation, itemsLocation.schema.$ref) } const itemsSchema = itemsLocation.schema @@ -791,193 +860,81 @@ function buildConstSerializer (location, input) { return code } -function buildAllOf (context, location, input) { - const schema = location.schema +function buildValue (context, location, input) { + let schema = location.schema - let mergedSchemaId = schema[mergedSchemaRef] - if (mergedSchemaId) { - const mergedLocation = getMergedLocation(context, mergedSchemaId) - return buildValue(context, mergedLocation, input) + if (typeof schema === 'boolean') { + return `json += JSON.stringify(${input})` } - 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.$ref) { + location = resolveRef(context, location, schema.$ref) + schema = location.schema } - 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 - ]) + if (schema.type === undefined) { + const inferredType = inferTypeByKeyword(schema) + if (inferredType) { + schema.type = inferredType } - - const nestedResult = buildValue(context, mergedLocation, input) - const schemaRef = optionLocation.getSchemaRef() - code += ` - ${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input})) - ${nestedResult} - ` } - let schemaRef = location.getSchemaRef() - if (schemaRef.startsWith(context.rootSchemaId)) { - schemaRef = schemaRef.replace(context.rootSchemaId, '') + if (schema.if && schema.then) { + return addIfThenElse(context, location, input) } - 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 - ]) + if (schema.allOf) { + mergeAllOfSchema(context, location, schema, clone(schema)) + schema = location.schema } - if (!elseSchema) { - return ` - if (validator.validate("${ifSchemaRef}", ${input})) { - ${buildValue(context, thenMergedLocation, input)} - } else { - ${buildValue(context, rootLocation, input)} - } - ` - } + const type = schema.type - 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 + let code = '' - elseMergedLocation = mergeLocations(context, elseMergedSchemaId, [ - rootLocation, - elseLocation - ]) - } + if ((type === undefined || type === 'object') && (schema.anyOf || schema.oneOf)) { + context.validatorSchemasIds.add(location.getSchemaId()) - return ` - if (validator.validate("${ifSchemaRef}", ${input})) { - ${buildValue(context, thenMergedLocation, input)} - } else { - ${buildValue(context, elseMergedLocation, input)} + if (schema.type === 'object') { + context.wrapObjects = false + const funcName = buildObject(context, location) + code += ` + json += '{' + json += ${funcName}(${input}) + json += ',' + ` } - ` -} -function buildValue (context, location, input) { - let schema = location.schema - - if (typeof schema === 'boolean') { - return `json += JSON.stringify(${input})` - } - - if (schema.$ref) { - location = resolveRef(context, location) - schema = location.schema - } - - if (schema.allOf) { - return buildAllOf(context, location, input) - } + const type = schema.anyOf ? 'anyOf' : 'oneOf' + const anyOfLocation = location.getPropertyLocation(type) - if (schema.anyOf || schema.oneOf) { - return buildOneOf(context, location, input) - } + 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 (schema.if && schema.then) { - return buildIfThenElse(context, location, input) - } + let schemaRef = location.getSchemaRef() + if (schemaRef.startsWith(context.rootSchemaId)) { + schemaRef = schemaRef.replace(context.rootSchemaId, '') + } - if (schema.type === undefined) { - const inferredType = inferTypeByKeyword(schema) - if (inferredType) { - schema.type = inferredType + code += ` + else throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`) + ` + if (schema.type === 'object') { + code += ` + json += '}' + ` + context.wrapObjects = true } + return code } - let code = '' - - const type = schema.type const nullable = schema.nullable === true if (nullable) { code += ` diff --git a/lib/location.js b/lib/location.js index 0d9acb2d..1311de81 100644 --- a/lib/location.js +++ b/lib/location.js @@ -5,6 +5,7 @@ class Location { this.schema = schema this.schemaId = schemaId this.jsonPointer = jsonPointer + this.mergedSchemaId = null } getPropertyLocation (propertyName) { @@ -13,11 +14,39 @@ class Location { this.schemaId, this.jsonPointer + '/' + propertyName ) + + 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 () { - return this.schemaId + this.jsonPointer + const schemaId = this.getSchemaId() + return schemaId + this.jsonPointer + } + + addMergedSchema (mergedSchema, schemaId, jsonPointer = '#') { + this.schema = mergedSchema + this.mergedSchemaId = schemaId + this.jsonPointer = jsonPointer } } diff --git a/lib/merge-schemas.js b/lib/merge-schemas.js deleted file mode 100644 index bb27a8bf..00000000 --- a/lib/merge-schemas.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict' - -const { mergeSchemas: _mergeSchemas } = require('@fastify/merge-json-schemas') - -function mergeSchemas (schemas) { - return _mergeSchemas(schemas, { onConflict: 'skip' }) -} - -module.exports = mergeSchemas diff --git a/package.json b/package.json index c7bef5e3..6f85aa12 100644 --- a/package.json +++ b/package.json @@ -51,13 +51,13 @@ "fast-json-stringify": "." }, "dependencies": { + "@fastify/deepmerge": "^1.0.0", "ajv": "^8.10.0", "ajv-formats": "^2.1.1", "fast-deep-equal": "^3.1.3", "fast-uri": "^2.1.0", "rfdc": "^1.2.0", - "json-schema-ref-resolver": "^1.0.1", - "@fastify/merge-json-schemas": "^0.1.0" + "json-schema-ref-resolver": "^1.0.1" }, "standard": { "ignore": [ diff --git a/test/allof.test.js b/test/allof.test.js index 7ec2d0c8..d0b8b74f 100644 --- a/test/allof.test.js +++ b/test/allof.test.js @@ -553,60 +553,8 @@ test('allof with local anchor reference', (t) => { t.equal(stringify(data), JSON.stringify(data)) }) -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(3) + t.plan(1) const schema = { allOf: [ @@ -614,18 +562,11 @@ test('allOf: throw Error if types mismatch ', (t) => { { type: 'number' } ] } - 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']]) - } + t.throws(() => build(schema), new Error('allOf schemas have different type values')) }) test('allOf: throw Error if format mismatch ', (t) => { - t.plan(3) + t.plan(1) const schema = { allOf: [ @@ -633,87 +574,29 @@ test('allOf: throw Error if format mismatch ', (t) => { { format: 'time' } ] } - 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']) - } + t.throws(() => build(schema), new Error('allOf schemas have different format values')) }) -test('recursive nested allOfs', (t) => { +test('allOf: throw Error if nullable mismatch /1', (t) => { t.plan(1) const schema = { - type: 'object', - properties: { - foo: { - additionalProperties: false, - allOf: [{ $ref: '#' }] - } - } + allOf: [ + { nullable: true }, + { nullable: false } + ] } - - const data = { foo: {} } - const stringify = build(schema) - t.equal(stringify(data), JSON.stringify(data)) + t.throws(() => build(schema), new Error('allOf schemas have different nullable values')) }) -test('recursive nested allOfs', (t) => { +test('allOf: throw Error if nullable mismatch /2', (t) => { t.plan(1) const schema = { - type: 'object', - properties: { - foo: { - additionalProperties: false, - allOf: [{ allOf: [{ $ref: '#' }] }] - } - } - } - - 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 - } + allOf: [ + { nullable: false }, + { nullable: true } + ] } - const stringify = build(schema, { schema: { externalSchema } }) - t.equal(stringify(data), '{"a":{"bar":"42","foo":{}},"b":{"bar":"42","foo":{}}}') + t.throws(() => build(schema), new Error('allOf schemas have different nullable values')) }) diff --git a/test/anyof.test.js b/test/anyof.test.js index b0d21af6..03483329 100644 --- a/test/anyof.test.js +++ b/test/anyof.test.js @@ -644,124 +644,3 @@ test('object with ref and validated properties', (t) => { const stringify = build(schema, { schema: externalSchemas }) t.equal(stringify({ id: 1, reference: 'hi' }), '{"id":1,"reference":"hi"}') }) - -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":{}}}') -}) diff --git a/test/if-then-else.test.js b/test/if-then-else.test.js index 1e93be00..bab3be7c 100644 --- a/test/if-then-else.test.js +++ b/test/if-then-else.test.js @@ -249,7 +249,15 @@ const alphabetOutput = JSON.stringify({ const deepFoobarOutput = JSON.stringify({ foobar: JSON.parse(foobarOutput) }) -const noElseGreetingOutput = JSON.stringify({}) +const noElseGreetingOutput = JSON.stringify({ + kind: 'greeting', + foo: 'FOO', + bar: 42, + hi: 'HI', + hello: 45, + a: 'A', + b: 35 +}) t.test('if-then-else', t => { const tests = [ @@ -411,60 +419,3 @@ t.test('if/else with array', (t) => { t.equal(stringify(['1']), JSON.stringify(['1'])) t.equal(stringify(['1', '2']), JSON.stringify([1, 2])) }) - -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"}}') -})