Skip to content

Commit

Permalink
Merge pull request #1 from asc11cat/feature/def-support
Browse files Browse the repository at this point in the history
Feature/def support
  • Loading branch information
asc11cat authored Oct 7, 2022
2 parents d8a57a4 + 04cd48a commit 2c4f510
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 8 deletions.
37 changes: 29 additions & 8 deletions lib/spec/openapi/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ function transformDefsToComponents (jsonSchema) {
jsonSchema[key][prop] = transformDefsToComponents(jsonSchema[key][prop])
})
} else if (key === '$ref') {
// replace the top-lvl path
jsonSchema[key] = jsonSchema[key].replace('definitions', 'components/schemas')
// replace the path for nested defs
jsonSchema[key] = jsonSchema[key].replaceAll('definitions', 'properties')
} else if (key === 'examples' && Array.isArray(jsonSchema[key]) && (jsonSchema[key].length > 1)) {
jsonSchema.examples = convertExamplesArrayToObject(jsonSchema.examples)
} else if (key === 'examples' && Array.isArray(jsonSchema[key]) && (jsonSchema[key].length === 1)) {
Expand Down Expand Up @@ -402,16 +405,34 @@ function prepareOpenapiSchemas (schemas, ref) {
return Object.entries(schemas)
.reduce((res, [name, schema]) => {
const _ = { ...schema }
const resolved = transformDefsToComponents(ref.resolve(_, { externalSchemas: [schemas] }))

// 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
// definitions are added by resolve but they are replace by components.schemas
delete resolved.$id
delete resolved.definitions
// 'definitions' keyword is not supported by openapi in schema item
// but we can receive it from json-schema input
if (_.definitions) {
_.properties = {
..._.definitions,
..._.properties
}
}

// ref.resolve call does 3 things:
// modifies underlying cache of ref
// adds 'definitions' with resolved schema(which we don't need here anymore)
// mutates $ref to point to the resolved schema
// ($ref will be mutated again by transformDefsToComponents)
const resolvedRef = ref.resolve(_, { externalSchemas: [schemas] })

// swagger doesn't accept $id on components schemas
// $ids are needed by ref.resolve to check the URI
// definitions are added by resolve, but they are not needed, as we resolve
// the $ref to already existing schemas in components.schemas using method below
// therefore, we delete both $id and definitions at the end of the process
delete resolvedRef.$id
delete resolvedRef.definitions

const components = transformDefsToComponents(resolvedRef)

res[name] = resolved
res[name] = components
return res
}, {})
}
Expand Down
41 changes: 41 additions & 0 deletions test/spec/openapi/refs.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,44 @@ test('uses examples if has property required in body', async (t) => {
t.ok(schema.parameters)
t.same(schema.parameters[0].in, 'query')
})

test('support schema $ref inside the json-schema definitions', async (t) => {
const fastify = Fastify()

await fastify.register(fastifySwagger, openapiOption)
fastify.register(async (instance) => {
instance.addSchema({
$id: 'NestedSchema',
properties: {
id: { type: 'string' }
},
definitions: {
SchemaA: {
$id: 'SchemaA',
type: 'object',
properties: {
id: { type: 'string' }
}
},
SchemaB: {
$id: 'SchemaB',
type: 'object',
properties: {
example: { $ref: 'NestedSchema#/definitions/SchemaA' }
}
}
}
})
instance.post('/url1', { schema: { body: { $ref: 'NestedSchema#/definitions/SchemaB' }, response: { 200: { $ref: 'NestedSchema#/definitions/SchemaA' } } } }, () => {})
})

await fastify.ready()

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

// ref must be prefixed by '#/components/schemas/'
t.equal(openapiObject.components.schemas.NestedSchema.properties.SchemaB.properties.example.$ref, '#/components/schemas/NestedSchema/properties/SchemaA')

await Swagger.validate(openapiObject)
})
106 changes: 106 additions & 0 deletions test/spec/openapi/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ const {
schemaAllOf
} = require('../../../examples/options')

const openapiOptionWithResolver = {
openapi: {},
refResolver: {
buildLocalReference: (json, baseUri, fragment, i) => {
return json.$id || `def-${i}`
}
}
}

test('support - oneOf, anyOf, allOf', async (t) => {
t.plan(2)
const fastify = Fastify()
Expand Down Expand Up @@ -785,3 +794,100 @@ test('support query serialization params', async t => {
t.equal(api.paths['/'].get.parameters[0].style, 'deepObject')
t.equal(api.paths['/'].get.parameters[0].explode, false)
})

test('support json-schema definitions keyword in schema(merge)', async t => {
const fastify = Fastify()
await fastify.register(fastifySwagger, openapiOptionWithResolver)

const schema = {
$id: 'NestedSchema',
properties: {
id: { type: 'string' }
},
definitions: {
SchemaA: {
type: 'object',
properties: {
id: { type: 'string' }
}
},
SchemaB: {
type: 'object',
properties: {
id: { type: 'string' }
}
}
}
}

fastify.register(async (instance) => {
instance.addSchema(schema)
})

await fastify.ready()

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

// definitions are getting merged to properties
t.match(Object.keys(openapiObject.components.schemas), ['NestedSchema'])
t.match(Object.keys(openapiObject.components.schemas.NestedSchema), ['properties'])
t.match(Object.keys(openapiObject.components.schemas.NestedSchema.properties), ['SchemaA', 'SchemaB', 'id'])

await Swagger.validate(openapiObject)
})

test('prefer properties when merging json-schema definitions with properties', async t => {
const fastify = Fastify()
await fastify.register(fastifySwagger, openapiOptionWithResolver)

const schema = {
$id: 'NestedSchema',
properties: {
SchemaA: {
$id: 'SchemaA',
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
description: { type: 'string' }
}
}
},
definitions: {
SchemaA: {
$id: 'SchemaA',
type: 'object',
properties: {
id: { type: 'string' }
}
},
SchemaB: {
$id: 'SchemaB',
type: 'object',
properties: {
id: { type: 'string' }
}
}
}
}

fastify.register(async (instance) => {
instance.addSchema(schema)
})

await fastify.ready()

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

t.same(openapiObject.components.schemas.NestedSchema.properties.SchemaA, {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
description: { type: 'string' }
}
})
await Swagger.validate(openapiObject)
})

0 comments on commit 2c4f510

Please sign in to comment.