From ac990395364e4b03971173e30b6e8247f06efd06 Mon Sep 17 00:00:00 2001 From: Nelson Oliveira Date: Fri, 11 Oct 2024 16:30:14 +0200 Subject: [PATCH] docs: Fallback to reference description for responses (#826) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fallback to reference description for responses * Update lib/util/resolve-schema-reference.js Signed-off-by: Gürgün Dayıoğlu * Adjust README entry to mention strict matching * Update README.md Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> Signed-off-by: Gürgün Dayıoğlu --------- Signed-off-by: Gürgün Dayıoğlu Co-authored-by: Gürgün Dayıoğlu Co-authored-by: KaKa <23028015+climba03003@users.noreply.github.com> --- README.md | 3 +++ lib/spec/openapi/utils.js | 11 ++++++++- lib/spec/swagger/utils.js | 11 ++++++++- lib/util/resolve-schema-reference.js | 18 ++++++++++++++ test/spec/openapi/schema.js | 35 ++++++++++++++++++++++++++++ test/spec/swagger/schema.js | 30 ++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 lib/util/resolve-schema-reference.js diff --git a/README.md b/README.md index cdb34bae..90b7c0db 100644 --- a/README.md +++ b/README.md @@ -468,6 +468,9 @@ fastify.get('/responseDescription', { } }, () => {}) ``` + +Additionally, if you provide a `$ref` in your response schema but no description, the reference's description will be used as a fallback. Note that at the moment, `$ref` will only be resolved by matching with `$id` and not through complex paths. + ##### Status code 2xx Fastify supports both the `2xx` and `3xx` status codes, however Swagger (OpenAPI v2) itself does not. diff --git a/lib/spec/openapi/utils.js b/lib/spec/openapi/utils.js index a8cd584b..477d2c8e 100644 --- a/lib/spec/openapi/utils.js +++ b/lib/spec/openapi/utils.js @@ -3,6 +3,7 @@ const { readPackageJson } = require('../../util/read-package-json') const { formatParamUrl } = require('../../util/format-param-url') const { resolveLocalRef } = require('../../util/resolve-local-ref') +const { resolveSchemaReference } = require('../../util/resolve-schema-reference') const { xResponseDescription, xConsume, xExamples } = require('../../constants') const { rawRequired } = require('../../symbols') const { generateParamsSchema } = require('../../util/generate-params-schema') @@ -303,6 +304,11 @@ function resolveCommonParams (container, parameters, schema, ref, sharedSchemas, arr.forEach(swaggerSchema => parameters.push(swaggerSchema)) } +function findReferenceDescription (rawSchema, ref) { + const resolved = resolveSchemaReference(rawSchema, ref) + return resolved?.description +} + // https://swagger.io/docs/specification/describing-responses/ function resolveResponse (fastifyResponseJson, produces, ref) { // if the user does not provided an out schema @@ -327,7 +333,10 @@ function resolveResponse (fastifyResponseJson, produces, ref) { } const response = { - description: resolved[xResponseDescription] || rawJsonSchema.description || 'Default Response' + description: resolved[xResponseDescription] || + rawJsonSchema.description || + findReferenceDescription(rawJsonSchema, ref) || + 'Default Response' } // add headers when there are any. diff --git a/lib/spec/swagger/utils.js b/lib/spec/swagger/utils.js index 18719137..66a5b59b 100644 --- a/lib/spec/swagger/utils.js +++ b/lib/spec/swagger/utils.js @@ -3,6 +3,7 @@ const { readPackageJson } = require('../../util/read-package-json') const { formatParamUrl } = require('../../util/format-param-url') const { resolveLocalRef } = require('../../util/resolve-local-ref') +const { resolveSchemaReference } = require('../../util/resolve-schema-reference') const { xResponseDescription, xConsume } = require('../../constants') const { generateParamsSchema } = require('../../util/generate-params-schema') const { hasParams } = require('../../util/match-params') @@ -205,6 +206,11 @@ function resolveCommonParams (container, parameters, schema, ref, sharedSchemas, arr.forEach(swaggerSchema => parameters.push(swaggerSchema)) } +function findReferenceDescription (rawSchema, ref) { + const resolved = resolveSchemaReference(rawSchema, ref) + return resolved?.description +} + // https://swagger.io/docs/specification/2-0/describing-responses/ function resolveResponse (fastifyResponseJson, ref) { // if the user does not provided an out schema @@ -235,7 +241,10 @@ function resolveResponse (fastifyResponseJson, ref) { } const response = { - description: rawJsonSchema[xResponseDescription] || rawJsonSchema.description || 'Default Response' + description: rawJsonSchema[xResponseDescription] || + rawJsonSchema.description || + findReferenceDescription(rawJsonSchema, ref) || + 'Default Response' } // add headers when there are any. diff --git a/lib/util/resolve-schema-reference.js b/lib/util/resolve-schema-reference.js new file mode 100644 index 00000000..06bf7757 --- /dev/null +++ b/lib/util/resolve-schema-reference.js @@ -0,0 +1,18 @@ +'use strict' + +function resolveSchemaReference (rawSchema, ref) { + const resolvedReference = ref.resolve(rawSchema, { externalSchemas: [ref.definitions().definitions] }) + + // Ref has format `#/definitions/id` + const schemaId = resolvedReference?.$ref?.split('/', 3)[2] + + if (schemaId === undefined) { + return undefined + } + + return resolvedReference.definitions[schemaId] +} + +module.exports = { + resolveSchemaReference +} diff --git a/test/spec/openapi/schema.js b/test/spec/openapi/schema.js index a2fcc88c..0d388722 100644 --- a/test/spec/openapi/schema.js +++ b/test/spec/openapi/schema.js @@ -387,6 +387,41 @@ test('response: description and x-response-description', async () => { t.equal(schemaObject.description, description) t.equal(schemaObject.responseDescription, undefined) }) + + test('retrieve the response description from its given $ref schema', async t => { + // Given a /description endpoint that also has a |description| field in its response referenced schema + const fastify = Fastify() + fastify.addSchema({ + $id: 'my-ref', + description, + type: 'string' + }) + + await fastify.register(fastifySwagger, openapiOption) + fastify.get('/description', { + schema: { + response: { + 200: { + $ref: 'my-ref#' + } + } + } + }, () => {}) + await fastify.ready() + + // When the Swagger schema is generated + const swaggerObject = fastify.swagger() + const api = await Swagger.validate(swaggerObject) + + const responseObject = api.paths['/description'].get.responses['200'] + t.ok(responseObject) + t.equal(responseObject.description, description) + + const schemaObject = responseObject.content['application/json'].schema + t.ok(schemaObject) + t.equal(schemaObject.description, description) + t.equal(schemaObject.responseDescription, undefined) + }) }) test('support default=null', async t => { diff --git a/test/spec/swagger/schema.js b/test/spec/swagger/schema.js index 37be7be1..e6f736ce 100644 --- a/test/spec/swagger/schema.js +++ b/test/spec/swagger/schema.js @@ -69,6 +69,36 @@ test('support response description', async t => { t.same(definedPath.responses['200'].description, 'Response OK!') }) +test('support response description fallback to its $ref', async t => { + const opts = { + schema: { + response: { + 200: { + $ref: 'my-ref#' + } + } + } + } + + const fastify = Fastify() + fastify.addSchema({ + $id: 'my-ref', + description: 'Response OK!', + type: 'string' + }) + + await fastify.register(fastifySwagger) + fastify.get('/', opts, () => {}) + + await fastify.ready() + + const swaggerObject = fastify.swagger() + const api = await Swagger.validate(swaggerObject) + + const definedPath = api.paths['/'].get + t.same(definedPath.responses['200'].description, 'Response OK!') +}) + test('response default description', async t => { const opts9 = { schema: {