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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy