From 6378a6558ba1251c0f568e9c542b0e22e7f28cfb Mon Sep 17 00:00:00 2001 From: bas0N Date: Sat, 22 Nov 2025 14:15:03 +0100 Subject: [PATCH] fix(openapi): always set requestBody.required to true when schema.body exists Fastify requires a body when schema.body is defined, even if all properties are optional. This ensures the OpenAPI spec correctly reflects Fastify's runtime behavior. --- lib/spec/openapi/utils.js | 8 +++----- test/spec/openapi/option.test.js | 32 ++++++++++++++++++++++++++++++++ test/spec/openapi/schema.test.js | 6 ++++++ 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/lib/spec/openapi/utils.js b/lib/spec/openapi/utils.js index b6fa281e..d3a62a75 100644 --- a/lib/spec/openapi/utils.js +++ b/lib/spec/openapi/utils.js @@ -281,10 +281,6 @@ function resolveBodyParams (opts, body, schema, consumes, ref) { body.content[consume] = media }) - if (resolved?.required?.length) { - body.required = true - } - if (resolved?.description) { body.description = resolved.description } @@ -475,7 +471,9 @@ function prepareOpenapiMethod (opts, schema, ref, openapiObject, url) { if (schema.externalDocs) openapiMethod.externalDocs = schema.externalDocs if (schema.querystring) resolveCommonParams(opts, 'query', parameters, schema.querystring, ref, openapiObject.definitions, securityIgnores.query) if (schema.body) { - openapiMethod.requestBody = { content: {} } + // Fastify requires a body (at least {}) when schema.body is defined, + // so requestBody.required should always be true + openapiMethod.requestBody = { required: true, content: {} } resolveBodyParams(opts, openapiMethod.requestBody, schema.body, schema.consumes, ref) } if (schema.params) resolveCommonParams(opts, 'path', parameters, schema.params, ref, openapiObject.definitions) diff --git a/test/spec/openapi/option.test.js b/test/spec/openapi/option.test.js index ab5ade64..9bf48261 100644 --- a/test/spec/openapi/option.test.js +++ b/test/spec/openapi/option.test.js @@ -1660,6 +1660,38 @@ test('marks request body as required', async (t) => { t.assert.deepStrictEqual(requestBody.required, true) }) +test('marks request body as required even when all properties are optional', async (t) => { + t.plan(3) + const fastify = Fastify() + + await fastify.register(fastifySwagger, openapiOption) + + const body = { + type: 'object', + properties: { + hello: { + type: 'string' + } + } + } + + const opts = { + schema: { + body + } + } + + fastify.put('/', opts, () => {}) + + await fastify.ready() + const openapiObject = fastify.swagger() + const requestBody = openapiObject.paths['/'].put.requestBody + + t.assert.ok(requestBody) + t.assert.strictEqual(requestBody.required, true) + t.assert.ok(requestBody.content['application/json'].schema) +}) + test('openapi webhooks properties', async (t) => { t.plan(1) const fastify = Fastify() diff --git a/test/spec/openapi/schema.test.js b/test/spec/openapi/schema.test.js index 54f7f94b..c1c5a61e 100644 --- a/test/spec/openapi/schema.test.js +++ b/test/spec/openapi/schema.test.js @@ -726,6 +726,7 @@ test('support "const" keyword', async t => { const definedPath = api.paths['/'].post t.assert.deepStrictEqual(JSON.parse(JSON.stringify(definedPath.requestBody)), { + required: true, content: { 'application/json': { schema: { @@ -793,6 +794,7 @@ test('convert "const" to "enum"', async t => { const definedPath = api.paths['/'].post t.assert.deepStrictEqual(JSON.parse(JSON.stringify(definedPath.requestBody)), { + required: true, content: { 'application/json': { schema: { @@ -855,6 +857,7 @@ test('support object properties named "const"', async t => { const definedPath = api.paths['/'].post t.assert.deepStrictEqual(JSON.parse(JSON.stringify(definedPath.requestBody)), { + required: true, content: { 'application/json': { schema: { @@ -914,6 +917,7 @@ test('support object properties with special names', async t => { const definedPath = api.paths['/'].post t.assert.deepStrictEqual(JSON.parse(JSON.stringify(definedPath.requestBody)), { + required: true, content: { 'application/json': { schema: { @@ -968,6 +972,7 @@ test('support "description" keyword', async t => { const definedPath = api.paths['/'].post t.assert.deepStrictEqual(JSON.parse(JSON.stringify(definedPath.requestBody)), { + required: true, description: 'Body description', content: { 'application/json': { @@ -1239,6 +1244,7 @@ test('support multiple content types as request', async t => { const definedPath = api.paths['/'].post t.assert.deepStrictEqual(definedPath.requestBody, { + required: true, content: { 'application/json': { schema: {