Skip to content

Commit 2628bdb

Browse files
feat(openapi): add support for callbacks (#829)
1 parent 53a71eb commit 2628bdb

File tree

4 files changed

+579
-0
lines changed

4 files changed

+579
-0
lines changed

examples/dynamic-openapi.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,59 @@ fastify.register(async function (fastify) {
112112
}, (req, reply) => { reply.send({ hello: `Hello ${req.body.hello}` }) })
113113
})
114114

115+
fastify.post('/subscribe', {
116+
schema: {
117+
description: 'subscribe for webhooks',
118+
summary: 'webhook example',
119+
security: [],
120+
response: {
121+
201: {
122+
description: 'Succesful response'
123+
}
124+
},
125+
body: {
126+
type: 'object',
127+
properties: {
128+
callbackUrl: {
129+
type: 'string',
130+
examples: ['https://example.com']
131+
}
132+
}
133+
},
134+
callbacks: {
135+
myEvent: {
136+
'{$request.body#/callbackUrl}': {
137+
post: {
138+
requestBody: {
139+
content: {
140+
'application/json': {
141+
schema: {
142+
type: 'object',
143+
properties: {
144+
message: {
145+
type: 'string',
146+
example: 'Some event happened'
147+
}
148+
},
149+
required: [
150+
'message'
151+
]
152+
}
153+
}
154+
}
155+
},
156+
responses: {
157+
200: {
158+
description: 'Success'
159+
}
160+
}
161+
}
162+
}
163+
}
164+
}
165+
}
166+
})
167+
115168
fastify.listen({ port: 3000 }, err => {
116169
if (err) throw err
117170
})

lib/spec/openapi/utils.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,51 @@ function resolveResponse (fastifyResponseJson, produces, ref) {
388388
return responsesContainer
389389
}
390390

391+
function resolveCallbacks (schema, ref) {
392+
const callbacksContainer = {}
393+
394+
for (const eventName in schema) {
395+
if (!schema[eventName]) {
396+
continue
397+
}
398+
399+
const eventSchema = schema[eventName]
400+
const [callbackUrl] = Object.keys(eventSchema)
401+
402+
if (!callbackUrl || !eventSchema[callbackUrl]) {
403+
continue
404+
}
405+
406+
const callbackSchema = schema[eventName][callbackUrl]
407+
const [httpMethodName] = Object.keys(callbackSchema)
408+
409+
if (!httpMethodName || !callbackSchema[httpMethodName]) {
410+
continue
411+
}
412+
413+
const httpMethodSchema = callbackSchema[httpMethodName]
414+
const httpMethodContainer = {}
415+
416+
if (httpMethodSchema.requestBody) {
417+
httpMethodContainer.requestBody = convertJsonSchemaToOpenapi3(
418+
ref.resolve(httpMethodSchema.requestBody)
419+
)
420+
}
421+
422+
httpMethodContainer.responses = httpMethodSchema.responses
423+
? convertJsonSchemaToOpenapi3(ref.resolve(httpMethodSchema.responses))
424+
: { '2XX': { description: 'Default Response' } }
425+
426+
callbacksContainer[eventName] = {
427+
[callbackUrl]: {
428+
[httpMethodName]: httpMethodContainer
429+
}
430+
}
431+
}
432+
433+
return callbacksContainer
434+
}
435+
391436
function prepareOpenapiMethod (schema, ref, openapiObject, url) {
392437
const openapiMethod = {}
393438
const parameters = []
@@ -432,6 +477,7 @@ function prepareOpenapiMethod (schema, ref, openapiObject, url) {
432477
if (schema.deprecated) openapiMethod.deprecated = schema.deprecated
433478
if (schema.security) openapiMethod.security = schema.security
434479
if (schema.servers) openapiMethod.servers = schema.servers
480+
if (schema.callbacks) openapiMethod.callbacks = resolveCallbacks(schema.callbacks, ref)
435481
for (const key of Object.keys(schema)) {
436482
if (key.startsWith('x-')) {
437483
openapiMethod[key] = schema[key]

test/spec/openapi/refs.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,3 +377,56 @@ test('renders $ref schema with additional keywords', async (t) => {
377377
t.match(res.statusCode, 400)
378378
t.match(openapiObject.paths['/url1'].get.parameters[0].schema, cookie)
379379
})
380+
381+
test('support $ref in callbacks', async (t) => {
382+
const fastify = Fastify()
383+
384+
await fastify.register(fastifySwagger, openapiOption)
385+
fastify.register(async (instance) => {
386+
instance.addSchema({ $id: 'Subscription', type: 'object', properties: { callbackUrl: { type: 'string', examples: ['https://example.com'] } } })
387+
instance.addSchema({ $id: 'Event', type: 'object', properties: { message: { type: 'string', examples: ['Some event happened'] } } })
388+
instance.post('/subscribe', {
389+
schema: {
390+
body: {
391+
$ref: 'Subscription#'
392+
},
393+
response: {
394+
200: {
395+
$ref: 'Subscription#'
396+
}
397+
},
398+
callbacks: {
399+
myEvent: {
400+
'{$request.body#/callbackUrl}': {
401+
post: {
402+
requestBody: {
403+
content: {
404+
'application/json': {
405+
schema: { $ref: 'Event#' }
406+
}
407+
}
408+
},
409+
responses: {
410+
200: {
411+
description: 'Success'
412+
}
413+
}
414+
}
415+
}
416+
}
417+
}
418+
}
419+
}, () => {})
420+
})
421+
422+
await fastify.ready()
423+
424+
const openapiObject = fastify.swagger()
425+
426+
t.equal(typeof openapiObject, 'object')
427+
t.match(Object.keys(openapiObject.components.schemas), ['Subscription', 'Event'])
428+
t.equal(openapiObject.components.schemas.Subscription.properties.callbackUrl.example, 'https://example.com')
429+
t.equal(openapiObject.components.schemas.Event.properties.message.example, 'Some event happened')
430+
431+
await Swagger.validate(openapiObject)
432+
})

0 commit comments

Comments
 (0)