From c36da03f3ddda6c27946840835118ec955ce36ae Mon Sep 17 00:00:00 2001 From: Rob Campbell Date: Thu, 7 Nov 2024 22:02:42 -0500 Subject: [PATCH] support full callback spec --- lib/spec/openapi/utils.js | 51 ++++++---- test/spec/openapi/schema.js | 194 +++++++++++++++++++++++++++++++++++- 2 files changed, 219 insertions(+), 26 deletions(-) diff --git a/lib/spec/openapi/utils.js b/lib/spec/openapi/utils.js index a448138f..01b0e8a7 100644 --- a/lib/spec/openapi/utils.js +++ b/lib/spec/openapi/utils.js @@ -391,41 +391,48 @@ function resolveResponse (fastifyResponseJson, produces, ref) { function resolveCallbacks (schema, ref) { const callbacksContainer = {} + // Iterate over each callback event for (const eventName in schema) { if (!schema[eventName]) { continue } + // Create an empty object to house the future iterations + callbacksContainer[eventName] = {} const eventSchema = schema[eventName] - const [callbackUrl] = Object.keys(eventSchema) - if (!callbackUrl || !eventSchema[callbackUrl]) { - continue - } + // Iterate over each callbackUrl for the event + for (const callbackUrl in eventSchema) { + if (!callbackUrl || !eventSchema[callbackUrl]) { + continue + } - const callbackSchema = schema[eventName][callbackUrl] - const [httpMethodName] = Object.keys(callbackSchema) + // Create an empty object to house the future iterations + callbacksContainer[eventName][callbackUrl] = {} + const callbackSchema = eventSchema[callbackUrl] - if (!httpMethodName || !callbackSchema[httpMethodName]) { - continue - } + // Iterate over each httpMethod for the callbackUrl + for (const httpMethodName in callbackSchema) { + if (!httpMethodName || !callbackSchema[httpMethodName]) { + continue + } - const httpMethodSchema = callbackSchema[httpMethodName] - const httpMethodContainer = {} + const httpMethodSchema = callbackSchema[httpMethodName] + const httpMethodContainer = {} - if (httpMethodSchema.requestBody) { - httpMethodContainer.requestBody = convertJsonSchemaToOpenapi3( - ref.resolve(httpMethodSchema.requestBody) - ) - } + if (httpMethodSchema.requestBody) { + httpMethodContainer.requestBody = convertJsonSchemaToOpenapi3( + ref.resolve(httpMethodSchema.requestBody) + ) + } - httpMethodContainer.responses = httpMethodSchema.responses - ? convertJsonSchemaToOpenapi3(ref.resolve(httpMethodSchema.responses)) - : { '2XX': { description: 'Default Response' } } + // If a response is not provided, set a 2XX default response + httpMethodContainer.responses = httpMethodSchema.responses + ? convertJsonSchemaToOpenapi3(ref.resolve(httpMethodSchema.responses)) + : { '2XX': { description: 'Default Response' } } - callbacksContainer[eventName] = { - [callbackUrl]: { - [httpMethodName]: httpMethodContainer + // Set the schema at the appropriate location in the response object + callbacksContainer[eventName][callbackUrl][httpMethodName] = httpMethodContainer } } } diff --git a/test/spec/openapi/schema.js b/test/spec/openapi/schema.js index 53c463b9..03431f1c 100644 --- a/test/spec/openapi/schema.js +++ b/test/spec/openapi/schema.js @@ -1382,7 +1382,7 @@ test('support callbacks', async () => { await Swagger.validate(openapiObject) }) - test('skips callbacks if badly formatted', async t => { + test('skips callbacks if event is badly formatted', async t => { const fastify = Fastify() await fastify.register(fastifySwagger, openapiOption) @@ -1432,7 +1432,7 @@ test('support callbacks', async () => { await Swagger.validate(openapiObject) }) - test('skips callback if event is badly formatted', async t => { + test('skips callback if callbackUrl is badly formatted', async t => { const fastify = Fastify() await fastify.register(fastifySwagger, openapiOption) @@ -1492,7 +1492,7 @@ test('support callbacks', async () => { } }, myOtherEvent: { - '{$request.body#/callbackUrl}': {} + '{$request.body#/callbackUrl}': null } } } @@ -1586,7 +1586,11 @@ test('support callbacks', async () => { } } }, - myOtherEvent: {} + myOtherEvent: { + '{$request.body#/callbackUrl}': { + post: null + } + } } } }, @@ -1619,4 +1623,186 @@ test('support callbacks', async () => { await Swagger.validate(openapiObject) }) + + test('supports multiple callbackUrls and httpMethods in openapiObject', async t => { + const fastify = Fastify() + + await fastify.register(fastifySwagger, openapiOption) + fastify.register(async (instance) => { + instance.post( + '/subscribe', + { + schema: { + body: { + $id: 'Subscription', + type: 'object', + properties: { + callbackUrl: { + type: 'string', + examples: ['https://example.com'] + } + } + }, + response: { + 200: { + $id: 'Subscription', + type: 'object', + properties: { + callbackUrl: { + type: 'string', + examples: ['https://example.com'] + } + } + } + }, + callbacks: { + myEvent: { + '{$request.body#/callbackUrl}': { + post: { + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + example: 'Some event happened' + } + }, + required: ['message'] + } + } + } + }, + responses: { + 200: { + description: 'Success' + } + } + } + }, + '{$request.body#/anotherUrl}': { + post: { + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + example: 'Another event happened' + } + }, + required: ['message'] + } + } + } + }, + responses: { + 200: { + description: 'Success' + } + } + }, + put: { + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + example: 'PUT event happened' + } + }, + required: ['message'] + } + } + } + }, + responses: { + 200: { + description: 'Success' + } + } + } + } + }, + myOtherEvent: { + '{$request.body#/callbackUrl}': { + post: { + responses: { + 200: { + description: 'Success' + }, + 500: { + description: 'Error' + } + } + } + } + } + } + } + }, + () => {} + ) + }) + + await fastify.ready() + + const openapiObject = fastify.swagger() + + t.equal(typeof openapiObject, 'object') + t.equal(typeof openapiObject.paths['/subscribe'].post.callbacks, 'object') + + const definedPath = openapiObject.paths['/subscribe'].post.callbacks + + // First Event->First URL->First Method + t.strictSame( + definedPath.myEvent['{$request.body#/callbackUrl}'].post.requestBody + .content['application/json'].schema.properties, + { + message: { + type: 'string', + example: 'Some event happened' + } + } + ) + + // First Event->Second URL->First Method + t.strictSame( + definedPath.myEvent['{$request.body#/anotherUrl}'].post.requestBody + .content['application/json'].schema.properties, + { + message: { + type: 'string', + example: 'Another event happened' + } + } + ) + + // First Event->Second URL->Second Method + t.strictSame( + definedPath.myEvent['{$request.body#/anotherUrl}'].put.requestBody + .content['application/json'].schema.properties, + { + message: { + type: 'string', + example: 'PUT event happened' + } + } + ) + + // Second Event + t.same( + definedPath.myOtherEvent['{$request.body#/callbackUrl}'].post.requestBody, + null + ) + + await Swagger.validate(openapiObject) + }) })