Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: json schema draft 7 to openapi schema converting #797

Merged
merged 1 commit into from
Apr 19, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading