From b92b020d86bac7c95f83c8a2862b0b428158489e Mon Sep 17 00:00:00 2001 From: flcrom <161213233+flcrom@users.noreply.github.com> Date: Mon, 5 Oct 2026 21:17:29 +0530 Subject: [PATCH] fix: preserve allOf property constraints with additionalProperties false --- index.js | 4 ++++ test/allof.test.js | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/index.js b/index.js index 63372eaa..3ec3ae20 100644 --- a/index.js +++ b/index.js @@ -1358,6 +1358,9 @@ function buildAllOf (context, location, input) { context.mergedSchemasIds.set(schema, mergedSchemaId) const { allOf, ...schemaWithoutAllOf } = location.schema + const dropAdditionalProperties = schemaWithoutAllOf.additionalProperties === false + if (dropAdditionalProperties) delete schemaWithoutAllOf.additionalProperties + const locations = [ new Location( schemaWithoutAllOf, @@ -1372,6 +1375,7 @@ function buildAllOf (context, location, input) { } const mergedLocation = mergeLocations(context, mergedSchemaId, locations) + if (dropAdditionalProperties) mergedLocation.schema.additionalProperties = false return buildValue(context, mergedLocation, input) } diff --git a/test/allof.test.js b/test/allof.test.js index fe3d2c20..66154847 100644 --- a/test/allof.test.js +++ b/test/allof.test.js @@ -749,3 +749,54 @@ test('do not crash with $ref prop', (t) => { }) t.assert.equal(value, '{"outside":{"$ref":"true"}}') }) + +test('allOf keeps property constraints when additional properties are disabled', (t) => { + const schema = { + additionalProperties: false, + allOf: [{ + type: 'object', + properties: { + entries: { + type: 'array', + items: { + type: 'object', + properties: { enabled: { type: 'boolean' }, domain: { type: 'string' } }, + required: ['domain'] + } + } + } + }] + } + const stringify = build(schema) + t.assert.deepEqual(JSON.parse(stringify({ entries: [{ enabled: 1, domain: 'example.com' }], extra: true })), + { entries: [{ enabled: true, domain: 'example.com' }] }) + t.assert.throws(() => stringify({ entries: [{ enabled: 1 }] }), /"domain" is required/) +}) + +test('allOf keeps root and referenced property constraints with additionalProperties false', (t) => { + const schema = { + type: 'object', + additionalProperties: false, + properties: { name: { type: 'string' } }, + required: ['name'], + allOf: [{ $ref: 'entry' }] + } + const stringify = build(schema, { + schema: { entry: { type: 'object', properties: { enabled: { type: 'boolean' } }, required: ['enabled'] } } + }) + t.assert.deepEqual(JSON.parse(stringify({ name: 'one', enabled: 1, extra: 'drop' })), { name: 'one', enabled: true }) + t.assert.throws(() => stringify({ name: 'one' }), /"enabled" is required/) + t.assert.throws(() => stringify({ enabled: true }), /"name" is required/) + t.assert.equal(schema.additionalProperties, false) +}) + +test('allOf preserves explicit additional property schemas', (t) => { + for (const additionalProperties of [true, { type: 'boolean' }]) { + const stringify = build({ + type: 'object', + additionalProperties, + allOf: [{ type: 'object', properties: { enabled: { type: 'boolean' } } }] + }) + t.assert.deepEqual(JSON.parse(stringify({ enabled: 1, extra: true })), { enabled: true, extra: true }) + } +})