Skip to content

Commit

Permalink
refactor: json schema draft 7 to openapi schema converting (#797)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-tymoshenko authored Apr 19, 2024
1 parent 22d1e7c commit e01cd7b
Showing 1 changed file with 78 additions and 51 deletions.
129 changes: 78 additions & 51 deletions lib/spec/openapi/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,36 +115,6 @@ function resolveServerUrls (servers) {
return resolvedUrls
}

function transformDefsToComponents (jsonSchema) {
if (typeof jsonSchema === 'object' && jsonSchema !== null) {
// Handle patternProperties, that is not part of OpenAPI definitions
if (jsonSchema.patternProperties) {
jsonSchema.additionalProperties = Object.values(jsonSchema.patternProperties)[0]
delete jsonSchema.patternProperties
} else if (jsonSchema.const !== undefined) {
// OAS 3.1 supports `const` but it is not supported by `swagger-ui`
// https://swagger.io/docs/specification/data-models/keywords/
jsonSchema.enum = [jsonSchema.const]
delete jsonSchema.const
}

Object.keys(jsonSchema).forEach(function (key) {
if (key === 'properties') {
Object.keys(jsonSchema[key]).forEach(function (prop) {
jsonSchema[key][prop] = transformDefsToComponents(jsonSchema[key][prop])
})
} else if (key === '$ref') {
jsonSchema[key] = jsonSchema[key].replace('definitions', 'components/schemas')
} else if (key === '$id' || key === '$schema') {
delete jsonSchema[key]
} else {
jsonSchema[key] = transformDefsToComponents(jsonSchema[key])
}
})
}
return jsonSchema
}

function convertExamplesArrayToObject (examples) {
return examples.reduce((examplesObject, example, index) => {
if (typeof example === 'object') {
Expand All @@ -160,7 +130,7 @@ function convertExamplesArrayToObject (examples) {
// For supported keys read:
// https://swagger.io/docs/specification/describing-parameters/
function plainJsonObjectToOpenapi3 (container, jsonSchema, externalSchemas, securityIgnores = []) {
const obj = transformDefsToComponents(resolveLocalRef(jsonSchema, externalSchemas))
const obj = convertJsonSchemaToOpenapi3(resolveLocalRef(jsonSchema, externalSchemas))
let toOpenapiProp
switch (container) {
case 'cookie':
Expand Down Expand Up @@ -292,7 +262,7 @@ function schemaToMediaRecursive (schema) {
}

function resolveBodyParams (body, schema, consumes, ref) {
const resolved = transformDefsToComponents(ref.resolve(schema))
const resolved = convertJsonSchemaToOpenapi3(ref.resolve(schema))
if ((Array.isArray(consumes) && consumes.length === 0) || consumes === undefined) {
consumes = ['application/json']
}
Expand All @@ -313,7 +283,7 @@ function resolveBodyParams (body, schema, consumes, ref) {

function resolveCommonParams (container, parameters, schema, ref, sharedSchemas, securityIgnores) {
const schemasPath = '#/components/schemas/'
let resolved = transformDefsToComponents(ref.resolve(schema))
let resolved = convertJsonSchemaToOpenapi3(ref.resolve(schema))

// if the resolved definition is in global schema
if (resolved.$ref && resolved.$ref.startsWith(schemasPath)) {
Expand All @@ -339,7 +309,7 @@ function resolveResponse (fastifyResponseJson, produces, ref) {

statusCodes.forEach(statusCode => {
const rawJsonSchema = fastifyResponseJson[statusCode]
const resolved = transformDefsToComponents(ref.resolve(rawJsonSchema))
const resolved = convertJsonSchemaToOpenapi3(ref.resolve(rawJsonSchema))

/**
* 2xx require to be all upper-case
Expand Down Expand Up @@ -465,23 +435,80 @@ function prepareOpenapiMethod (schema, ref, openapiObject, url) {
return openapiMethod
}

function prepareOpenapiSchemas (schemas, ref) {
return Object.entries(schemas)
.reduce((res, [name, schema]) => {
const _ = { ...schema }
const resolved = transformDefsToComponents(ref.resolve(_, { externalSchemas: [schemas] }))
resolveSchemaExamplesRecursive(resolved)

// 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

res[name] = resolved
return res
}, {})
function convertJsonSchemaToOpenapi3 (jsonSchema) {
if (typeof jsonSchema !== 'object' || jsonSchema === null) {
return jsonSchema
}

if (Array.isArray(jsonSchema)) {
return jsonSchema.map(convertJsonSchemaToOpenapi3)
}

const openapiSchema = { ...jsonSchema }

for (const key of Object.keys(openapiSchema)) {
const value = openapiSchema[key]

if (key === '$id' || key === '$schema' || key === 'definitions') {
// TODO: this breaks references to the definition properties
delete openapiSchema[key]
continue
}

if (key === '$ref') {
openapiSchema.$ref = value.replace('definitions', 'components/schemas')
continue
}

if (key === 'const') {
// OAS 3.1 supports `const` but it is not supported by `swagger-ui`
// https://swagger.io/docs/specification/data-models/keywords/
// TODO: check if enum property already exists
// TODO: this breaks references to the const property
openapiSchema.enum = [openapiSchema.const]
delete openapiSchema.const
continue
}

if (key === 'patternProperties') {
// TODO: check if additionalProperties property already exists
// TODO: this breaks references to the additionalProperties properties
// TODO: patternProperties actually allowed in the openapi schema, but should
// always start with "x-" prefix
openapiSchema.additionalProperties = Object.values(openapiSchema.patternProperties)[0]
delete openapiSchema.patternProperties
continue
}

if (key === 'properties') {
openapiSchema[key] = {}
for (const propertyName of Object.keys(value)) {
const propertyJsonSchema = value[propertyName]
const propertyOpenapiSchema = convertJsonSchemaToOpenapi3(propertyJsonSchema)
openapiSchema[key][propertyName] = propertyOpenapiSchema
}
continue
}

openapiSchema[key] = convertJsonSchemaToOpenapi3(value)
}

return openapiSchema
}

function prepareOpenapiSchemas (jsonSchemas, ref) {
const openapiSchemas = {}

for (const schemaName of Object.keys(jsonSchemas)) {
const jsonSchema = { ...jsonSchemas[schemaName] }

const resolvedJsonSchema = ref.resolve(jsonSchema, { externalSchemas: [jsonSchemas] })
const openapiSchema = convertJsonSchemaToOpenapi3(resolvedJsonSchema)
resolveSchemaExamplesRecursive(openapiSchema)

openapiSchemas[schemaName] = openapiSchema
}
return openapiSchemas
}

module.exports = {
Expand Down

0 comments on commit e01cd7b

Please sign in to comment.