Skip to content

Commit

Permalink
feat(openapi): add support for callbacks (#829)
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasFlorelli authored Oct 17, 2024
1 parent 53a71eb commit 2628bdb
Show file tree
Hide file tree
Showing 4 changed files with 579 additions and 0 deletions.
53 changes: 53 additions & 0 deletions examples/dynamic-openapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,59 @@ fastify.register(async function (fastify) {
}, (req, reply) => { reply.send({ hello: `Hello ${req.body.hello}` }) })
})

fastify.post('/subscribe', {
schema: {
description: 'subscribe for webhooks',
summary: 'webhook example',
security: [],
response: {
201: {
description: 'Succesful response'
}
},
body: {
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'
}
}
}
}
}
}
}
})

fastify.listen({ port: 3000 }, err => {
if (err) throw err
})
46 changes: 46 additions & 0 deletions lib/spec/openapi/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,51 @@ function resolveResponse (fastifyResponseJson, produces, ref) {
return responsesContainer
}

function resolveCallbacks (schema, ref) {
const callbacksContainer = {}

for (const eventName in schema) {
if (!schema[eventName]) {
continue
}

const eventSchema = schema[eventName]
const [callbackUrl] = Object.keys(eventSchema)

if (!callbackUrl || !eventSchema[callbackUrl]) {
continue
}

const callbackSchema = schema[eventName][callbackUrl]
const [httpMethodName] = Object.keys(callbackSchema)

if (!httpMethodName || !callbackSchema[httpMethodName]) {
continue
}

const httpMethodSchema = callbackSchema[httpMethodName]
const httpMethodContainer = {}

if (httpMethodSchema.requestBody) {
httpMethodContainer.requestBody = convertJsonSchemaToOpenapi3(
ref.resolve(httpMethodSchema.requestBody)
)
}

httpMethodContainer.responses = httpMethodSchema.responses
? convertJsonSchemaToOpenapi3(ref.resolve(httpMethodSchema.responses))
: { '2XX': { description: 'Default Response' } }

callbacksContainer[eventName] = {
[callbackUrl]: {
[httpMethodName]: httpMethodContainer
}
}
}

return callbacksContainer
}

function prepareOpenapiMethod (schema, ref, openapiObject, url) {
const openapiMethod = {}
const parameters = []
Expand Down Expand Up @@ -432,6 +477,7 @@ function prepareOpenapiMethod (schema, ref, openapiObject, url) {
if (schema.deprecated) openapiMethod.deprecated = schema.deprecated
if (schema.security) openapiMethod.security = schema.security
if (schema.servers) openapiMethod.servers = schema.servers
if (schema.callbacks) openapiMethod.callbacks = resolveCallbacks(schema.callbacks, ref)
for (const key of Object.keys(schema)) {
if (key.startsWith('x-')) {
openapiMethod[key] = schema[key]
Expand Down
53 changes: 53 additions & 0 deletions test/spec/openapi/refs.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,56 @@ test('renders $ref schema with additional keywords', async (t) => {
t.match(res.statusCode, 400)
t.match(openapiObject.paths['/url1'].get.parameters[0].schema, cookie)
})

test('support $ref in callbacks', async (t) => {
const fastify = Fastify()

await fastify.register(fastifySwagger, openapiOption)
fastify.register(async (instance) => {
instance.addSchema({ $id: 'Subscription', type: 'object', properties: { callbackUrl: { type: 'string', examples: ['https://example.com'] } } })
instance.addSchema({ $id: 'Event', type: 'object', properties: { message: { type: 'string', examples: ['Some event happened'] } } })
instance.post('/subscribe', {
schema: {
body: {
$ref: 'Subscription#'
},
response: {
200: {
$ref: 'Subscription#'
}
},
callbacks: {
myEvent: {
'{$request.body#/callbackUrl}': {
post: {
requestBody: {
content: {
'application/json': {
schema: { $ref: 'Event#' }
}
}
},
responses: {
200: {
description: 'Success'
}
}
}
}
}
}
}
}, () => {})
})

await fastify.ready()

const openapiObject = fastify.swagger()

t.equal(typeof openapiObject, 'object')
t.match(Object.keys(openapiObject.components.schemas), ['Subscription', 'Event'])
t.equal(openapiObject.components.schemas.Subscription.properties.callbackUrl.example, 'https://example.com')
t.equal(openapiObject.components.schemas.Event.properties.message.example, 'Some event happened')

await Swagger.validate(openapiObject)
})
Loading

0 comments on commit 2628bdb

Please sign in to comment.