Skip to content

Commit

Permalink
fix: nested swagger ref
Browse files Browse the repository at this point in the history
  • Loading branch information
peter-mouland committed Sep 27, 2021
1 parent 7cf7d6d commit 1407e32
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 5 deletions.
11 changes: 7 additions & 4 deletions lib/spec/swagger/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const yaml = require('js-yaml')
const { shouldRouteHide } = require('../../util/common')
const { prepareDefaultOptions, prepareSwaggerObject, prepareSwaggerMethod, normalizeUrl } = require('./utils')
const { prepareDefaultOptions, prepareSwaggerObject, prepareSwaggerMethod, normalizeUrl, prepareSwaggerDefinitions } = require('./utils')

module.exports = function (opts, cache, routes, Ref, done) {
let ref
Expand All @@ -19,16 +19,19 @@ module.exports = function (opts, cache, routes, Ref, done) {
const swaggerObject = prepareSwaggerObject(defOpts, done)

ref = Ref()
swaggerObject.definitions = {
swaggerObject.definitions = prepareSwaggerDefinitions({
...swaggerObject.definitions,
...(ref.definitions().definitions)
}
}, ref)

// Swagger doesn't accept $id on /definitions schemas.
// The $ids are needed by Ref() to check the URI so we need
// to remove them at the end of the process
Object.values(swaggerObject.definitions)
.forEach(_ => { delete _.$id })
.forEach(_ => {
delete _.$id
delete _.definitions
})

swaggerObject.paths = {}
for (const route of routes) {
Expand Down
15 changes: 14 additions & 1 deletion lib/spec/swagger/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,22 @@ function prepareSwaggerMethod (schema, ref, swaggerObject) {
return swaggerMethod
}

function prepareSwaggerDefinitions (definitions, ref) {
return Object.entries(definitions)
.reduce((res, [name, definition]) => {
const _ = { ...definition }
const resolved = ref.resolve(_, { externalSchemas: [definitions] })
return {
...res,
[name]: resolved
}
}, {})
}

module.exports = {
prepareDefaultOptions,
prepareSwaggerObject,
prepareSwaggerMethod,
normalizeUrl
normalizeUrl,
prepareSwaggerDefinitions
}
28 changes: 28 additions & 0 deletions test/spec/openapi/refs.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,31 @@ test('support nested $ref schema : complex case', async (t) => {

await Swagger.validate(openapiObject)
})

test('support nested $ref schema : complex case without modifying buildLocalReference', async (t) => {
const fastify = Fastify()
fastify.register(fastifySwagger, { openapi: {} })
fastify.register(async (instance) => {
instance.addSchema({ $id: 'schemaA', type: 'object', properties: { id: { type: 'integer' } } })
instance.addSchema({ $id: 'schemaB', type: 'object', properties: { id: { type: 'string' } } })
instance.addSchema({ $id: 'schemaC', type: 'object', properties: { a: { type: 'array', items: { $ref: 'schemaA' } } } })
instance.addSchema({ $id: 'schemaD', type: 'object', properties: { b: { $ref: 'schemaB' }, c: { $ref: 'schemaC' } } })
instance.post('/url1', { schema: { body: { $ref: 'schemaD' }, response: { 200: { $ref: 'schemaB' } } } }, () => {})
instance.post('/url2', { schema: { body: { $ref: 'schemaC' }, response: { 200: { $ref: 'schemaA' } } } }, () => {})
})

await fastify.ready()

const openapiObject = fastify.swagger()
t.equal(typeof openapiObject, 'object')

const schemas = openapiObject.components.schemas
t.match(Object.keys(schemas), ['def-0', 'def-1', 'def-2', 'def-3'])

// ref must be prefixed by '#/components/schemas/'
t.equal(schemas['def-2'].properties.a.items.$ref, '#/components/schemas/def-0')
t.equal(schemas['def-3'].properties.b.$ref, '#/components/schemas/def-1')
t.equal(schemas['def-3'].properties.c.$ref, '#/components/schemas/def-2')

await Swagger.validate(openapiObject)
})
66 changes: 66 additions & 0 deletions test/spec/swagger/refs.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,69 @@ test('support $ref schema', async t => {
await Swagger.validate(res.json())
t.pass('valid swagger object')
})

test('support nested $ref schema : complex case', async (t) => {
const options = {
swagger: {},
refResolver: {
buildLocalReference: (json, baseUri, fragment, i) => {
return json.$id || `def-${i}`
}
}
}
const fastify = Fastify()
fastify.register(fastifySwagger, options)
fastify.register(async (instance) => {
instance.addSchema({ $id: 'schemaA', type: 'object', properties: { id: { type: 'integer' } } })
instance.addSchema({ $id: 'schemaB', type: 'object', properties: { id: { type: 'string' } } })
instance.addSchema({ $id: 'schemaC', type: 'object', properties: { a: { type: 'array', items: { $ref: 'schemaA' } } } })
instance.addSchema({ $id: 'schemaD', type: 'object', properties: { b: { $ref: 'schemaB' }, c: { $ref: 'schemaC' } } })
instance.post('/url1', { schema: { body: { $ref: 'schemaD' }, response: { 200: { $ref: 'schemaB' } } } }, () => {})
instance.post('/url2', { schema: { body: { $ref: 'schemaC' }, response: { 200: { $ref: 'schemaA' } } } }, () => {})
})

await fastify.ready()

const swaggerObject = fastify.swagger()
t.equal(typeof swaggerObject, 'object')
const definitions = swaggerObject.definitions
t.match(Object.keys(definitions), ['schemaA', 'schemaB', 'schemaC', 'schemaD'])

// ref must be prefixed by '#/definitions/'
t.equal(definitions.schemaC.properties.a.items.$ref, '#/definitions/schemaA')
t.equal(definitions.schemaD.properties.b.$ref, '#/definitions/schemaB')
t.equal(definitions.schemaD.properties.c.$ref, '#/definitions/schemaC')

await Swagger.validate(swaggerObject)
})

// test('support nested $ref schema : complex case without modifying buildLocalReference', async (t) => {
// const fastify = Fastify()
// fastify.register(fastifySwagger, {
// routePrefix: '/docs',
// exposeRoute: true
// })
// fastify.register(async (instance) => {
// instance.addSchema({ $id: 'schemaA', type: 'object', properties: { id: { type: 'integer' } } })
// instance.addSchema({ $id: 'schemaB', type: 'object', properties: { id: { type: 'string' } } })
// instance.addSchema({ $id: 'schemaC', type: 'object', properties: { a: { type: 'array', items: { $ref: 'schemaA' } } } })
// instance.addSchema({ $id: 'schemaD', type: 'object', properties: { b: { $ref: 'schemaB' }, c: { $ref: 'schemaC' } } })
// instance.post('/url1', { schema: { body: { $ref: 'schemaD' }, response: { 200: { $ref: 'schemaB' } } } }, () => {})
// instance.post('/url2', { schema: { body: { $ref: 'schemaC' }, response: { 200: { $ref: 'schemaA' } } } }, () => {})
// })
//
// await fastify.ready()
//
// const swaggerObject = fastify.swagger()
// t.equal(typeof swaggerObject, 'object')
//
// const definitions = swaggerObject.definitions
// t.match(Object.keys(definitions), ['def-0', 'def-1', 'def-2', 'def-3'])
//
// // ref must be prefixed by '#/definitions/'
// t.equal(definitions['def-2'].properties.a.items.$ref, '#/definitions/def-0')
// t.equal(definitions['def-3'].properties.b.$ref, '#/definitions/def-1')
// t.equal(definitions['def-3'].properties.c.$ref, '#/definitions/def-2')
//
// await Swagger.validate(swaggerObject)
// })

0 comments on commit 1407e32

Please sign in to comment.