Skip to content

Commit a65d96a

Browse files
authored
fix(openapi): always set requestBody.required to true when schema.body exists (#903)
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.
1 parent d35da58 commit a65d96a

3 files changed

Lines changed: 41 additions & 5 deletions

File tree

lib/spec/openapi/utils.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,6 @@ function resolveBodyParams (opts, body, schema, consumes, ref) {
282282
body.content[consume] = media
283283
})
284284

285-
if (resolved?.required?.length) {
286-
body.required = true
287-
}
288-
289285
if (resolved?.description) {
290286
body.description = resolved.description
291287
}
@@ -476,7 +472,9 @@ function prepareOpenapiMethod (opts, schema, ref, openapiObject, url) {
476472
if (schema.externalDocs) openapiMethod.externalDocs = schema.externalDocs
477473
if (schema.querystring) resolveCommonParams(opts, 'query', parameters, schema.querystring, ref, openapiObject.definitions, securityIgnores.query)
478474
if (schema.body) {
479-
openapiMethod.requestBody = { content: {} }
475+
// Fastify requires a body (at least {}) when schema.body is defined,
476+
// so requestBody.required should always be true
477+
openapiMethod.requestBody = { required: true, content: {} }
480478
resolveBodyParams(opts, openapiMethod.requestBody, schema.body, schema.consumes, ref)
481479
}
482480
if (schema.params) resolveCommonParams(opts, 'path', parameters, schema.params, ref, openapiObject.definitions)

test/spec/openapi/option.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,6 +1660,38 @@ test('marks request body as required', async (t) => {
16601660
t.assert.deepStrictEqual(requestBody.required, true)
16611661
})
16621662

1663+
test('marks request body as required even when all properties are optional', async (t) => {
1664+
t.plan(3)
1665+
const fastify = Fastify()
1666+
1667+
await fastify.register(fastifySwagger, openapiOption)
1668+
1669+
const body = {
1670+
type: 'object',
1671+
properties: {
1672+
hello: {
1673+
type: 'string'
1674+
}
1675+
}
1676+
}
1677+
1678+
const opts = {
1679+
schema: {
1680+
body
1681+
}
1682+
}
1683+
1684+
fastify.put('/', opts, () => {})
1685+
1686+
await fastify.ready()
1687+
const openapiObject = fastify.swagger()
1688+
const requestBody = openapiObject.paths['/'].put.requestBody
1689+
1690+
t.assert.ok(requestBody)
1691+
t.assert.strictEqual(requestBody.required, true)
1692+
t.assert.ok(requestBody.content['application/json'].schema)
1693+
})
1694+
16631695
test('openapi webhooks properties', async (t) => {
16641696
t.plan(1)
16651697
const fastify = Fastify()

test/spec/openapi/schema.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,7 @@ test('support "const" keyword', async t => {
726726

727727
const definedPath = api.paths['/'].post
728728
t.assert.deepStrictEqual(JSON.parse(JSON.stringify(definedPath.requestBody)), {
729+
required: true,
729730
content: {
730731
'application/json': {
731732
schema: {
@@ -793,6 +794,7 @@ test('convert "const" to "enum"', async t => {
793794

794795
const definedPath = api.paths['/'].post
795796
t.assert.deepStrictEqual(JSON.parse(JSON.stringify(definedPath.requestBody)), {
797+
required: true,
796798
content: {
797799
'application/json': {
798800
schema: {
@@ -855,6 +857,7 @@ test('support object properties named "const"', async t => {
855857

856858
const definedPath = api.paths['/'].post
857859
t.assert.deepStrictEqual(JSON.parse(JSON.stringify(definedPath.requestBody)), {
860+
required: true,
858861
content: {
859862
'application/json': {
860863
schema: {
@@ -914,6 +917,7 @@ test('support object properties with special names', async t => {
914917

915918
const definedPath = api.paths['/'].post
916919
t.assert.deepStrictEqual(JSON.parse(JSON.stringify(definedPath.requestBody)), {
920+
required: true,
917921
content: {
918922
'application/json': {
919923
schema: {
@@ -968,6 +972,7 @@ test('support "description" keyword', async t => {
968972

969973
const definedPath = api.paths['/'].post
970974
t.assert.deepStrictEqual(JSON.parse(JSON.stringify(definedPath.requestBody)), {
975+
required: true,
971976
description: 'Body description',
972977
content: {
973978
'application/json': {
@@ -1239,6 +1244,7 @@ test('support multiple content types as request', async t => {
12391244

12401245
const definedPath = api.paths['/'].post
12411246
t.assert.deepStrictEqual(definedPath.requestBody, {
1247+
required: true,
12421248
content: {
12431249
'application/json': {
12441250
schema: {

0 commit comments

Comments
 (0)