Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(openapi): support full callbacks spec #838

Merged
merged 1 commit into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 29 additions & 22 deletions lib/spec/openapi/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand Down
194 changes: 190 additions & 4 deletions test/spec/openapi/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1492,7 +1492,7 @@ test('support callbacks', async () => {
}
},
myOtherEvent: {
'{$request.body#/callbackUrl}': {}
'{$request.body#/callbackUrl}': null
}
}
}
Expand Down Expand Up @@ -1586,7 +1586,11 @@ test('support callbacks', async () => {
}
}
},
myOtherEvent: {}
myOtherEvent: {
'{$request.body#/callbackUrl}': {
post: null
}
}
}
}
},
Expand Down Expand Up @@ -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)
})
})