UNPKG

fastify-oas

Version:

Fastify OpenAPI specification generator plugin

249 lines (229 loc) 6.49 kB
const ALLOWED_SCHEMA_KEYS = [ '$ref', 'additionalProperties', 'allOf', 'anyOf', 'default', 'description', 'enum', 'example', 'examples', 'exclusiveMaximum', 'exclusiveMinimum', 'format', 'items', 'maximum', 'maxItems', 'maxLength', 'maxProperties', 'minimum', 'minItems', 'minLength', 'minProperties', 'multipleOf', 'not', 'nullable', 'oneOf', 'pattern', 'properties', 'required', 'title', 'type', 'uniqueItems', ]; const allowedProps = (obj) => { if (typeof obj !== 'object' || obj === null) { return obj; } return Object.keys(obj).reduce((newObj, key) => { const val = obj[key]; if (Array.isArray(val)) { if (obj.type === 'array' && key === 'items') { // Fix json-schema tuples, as they're not supported in OpenAPI 3: https://github.com/OAI/OpenAPI-Specification/issues/1026 newObj.items = { oneOf: obj.items.map(allowedProps) }; newObj.minItems = obj.minItems === undefined ? obj.items.length : obj.minItems; newObj.maxItems = obj.maxItems === undefined ? obj.items.length : obj.maxItems; } else { if ( !obj.type && key === 'enum' && obj.enum.every((v) => typeof v === typeof obj.enum[0]) ) { // Attempt to fix enum schemas without a type (only possible if all values have the same type) newObj.type = typeof obj.enum[0]; } newObj[key] = val.map(allowedProps); } } else if ( obj.type === 'array' && key === 'items' && typeof val === 'object' ) { newObj[key] = allowedProps(val); } else if ( obj.type === 'object' && key === 'properties' && typeof val === 'object' ) { // Process nested schemas in object properties newObj.properties = Object.keys(obj.properties).reduce( (processed, prop) => { return Object.assign(processed, { [prop]: allowedProps(obj.properties[prop]), }); }, {} ); } else if (ALLOWED_SCHEMA_KEYS.includes(key)) { newObj[key] = obj[key]; } return newObj; }, {}); }; exports.genBody = (dst, src, consumes) => { convertSchemaTypes(src); const body = src; const mediaTypes = consumes; dst.content = {}; if (body.description) { dst.description = body.description; delete body.description; } if (body.required) { dst.required = true; } for (const mediaType of mediaTypes) { dst.content[mediaType] = {}; if (body.examples) { dst.content[mediaType].examples = body.examples.reduce( (res, { name, ...rest }) => { res[name] = rest; return res; }, {} ); delete body.examples; } else if (body.example) { dst.content[mediaType].example = body.example; delete body.example; } dst.content[mediaType].schema = allowedProps(body); } }; const gentShorthandParams = (inWhat) => { const genFn = (dst, src) => { convertSchemaTypes(src); const params = src; if ((params.type || params.oneOf) && params.properties) { const paramProperties = Object.keys(params.properties).reduce( (acc, h) => { const required = (params.required && params.required.indexOf(h) >= 0) || false; const newProps = Object.assign({}, params.properties[h], { required, }); return Object.assign({}, acc, { [h]: newProps }); }, {} ); return genFn(dst, paramProperties); } Object.keys(params).forEach((name) => { // if (name === '$ref') { // const param = { // name: name, // in: inWhat, // schema: params[name], // }; // const param = params[name]; // dst.push(param); // return; // } const val = Object.assign({}, params[name]); const param = { name: name, in: inWhat, }; if (val.in) param.in = params[name].in; if (val.required || param.in === 'path') param.required = true; if (typeof val.description !== 'undefined') { param.description = val.description; } if (typeof val.style === 'string') param.style = val.style; if (typeof val.explode === 'boolean') param.explode = val.explode; delete val.required; param.schema = allowedProps(val); dst.push(param); }); }; return genFn; }; exports.genHeaders = gentShorthandParams('header'); exports.genQuery = gentShorthandParams('query'); exports.genCookies = gentShorthandParams('cookie'); exports.genPath = gentShorthandParams('path'); exports.genResponse = ( dst, src = { 200: { description: 'Default Response' } }, produces = ['*/*'] ) => { Object.keys(src).forEach((key) => { convertSchemaTypes(src[key]); const description = src[key].description; const headers = src[key].headers; const links = src[key].links; dst[key] = {}; if (src[key].type || src[key].oneOf || src[key].$ref) { const rsp = src[key]; const mediaTypes = produces; for (const mediaType of mediaTypes) { Object.assign(dst[key], { content: { [mediaType]: { schema: allowedProps(rsp) }, }, }); } } if (description) { Object.assign(dst[key], { description }); } if (headers) { Object.assign(dst[key], { headers }); } if (links) { Object.assign(dst[key], { links }); } }); }; exports.formatParamUrl = (url) => { let start = url.indexOf('/:'); if (start === -1) return url; const end = url.indexOf('/', ++start); if (end === -1) { return url.slice(0, start) + '{' + url.slice(++start) + '}'; } else { return exports.formatParamUrl( url.slice(0, start) + '{' + url.slice(++start, end) + '}' + url.slice(end) ); } }; exports.clone = (obj) => JSON.parse(JSON.stringify(obj)); function convertSchemaTypes(schema) { const obj = schema; if (Array.isArray(obj.type)) { if (obj.type.includes('null')) obj.nullable = true; obj.type = obj.type.filter((type) => type !== 'null'); if (obj.type.length > 1) { obj.oneOf = []; obj.type.forEach((type) => obj.oneOf.push({ type })); delete obj.type; } else { obj.type = obj.type[0]; } } if (obj.properties) { Object.values(obj.properties).forEach((prop) => convertSchemaTypes(prop)); } }