@walecloud/fastify-openapi-typescript-generator
Version:
Contains utilities to generate fastify types from openapi definition for the fastify framework.
183 lines (182 loc) • 6.2 kB
JavaScript
import { ParserBase } from './ParserBase.js';
const HttpOperations = new Set([
'delete',
'get',
'head',
'patch',
'post',
'put',
'options',
]);
function isExploding(item) {
const explode = item.explode ?? item.style === 'form' ? true : false;
return explode !== false && item.schema?.type === 'object';
}
export class ParserV3 extends ParserBase {
constructor() {
super(); // Now 'this' is initialized by calling the parent constructor.
}
parseQueryString(data) {
if (data.length === 1 && isExploding(data[0])) {
return data[0].schema;
}
return this.parseParams(data);
}
parseParams(data) {
const params = {
type: 'object',
properties: {},
};
const required = [];
data.forEach(item => {
params.properties[item.name] = item.schema;
this.copyProps(item, params.properties[item.name], ['description']);
// ajv wants "required" to be an array, which seems to be too strict
// see https://github.com/json-schema/json-schema/wiki/Properties-and-required
if (item.required) {
required.push(item.name);
}
});
if (required.length > 0) {
params.required = required;
}
return params;
}
parseParameters(schema, data) {
const params = [];
const querystring = [];
const headers = [];
// const formData = [];
data.forEach(item => {
switch (item.in) {
// case "body":
// schema.body = item.schema;
// break;
// case "formData":
// formData.push(item);
// break;
case 'path': {
item.style = item.style || 'simple';
params.push(item);
break;
}
case 'query': {
item.style = item.style || 'form';
querystring.push(item);
break;
}
case 'header': {
item.style = item.style || 'simple';
headers.push(item);
break;
}
}
});
if (params.length > 0) {
schema.params = this.parseParams(params);
}
if (querystring.length > 0) {
schema.querystring = this.parseQueryString(querystring);
}
if (headers.length > 0) {
schema.headers = this.parseParams(headers);
}
}
parseBody(data) {
if (data?.content) {
const mimeTypes = Object.keys(data.content);
if (mimeTypes.length === 0) {
return undefined;
}
mimeTypes.forEach((mimeType) => this.config.contentTypes.add(mimeType));
// fastify only supports one mimeType per path, pick the last
return data.content[mimeTypes.pop()].schema;
}
return undefined;
}
parseResponses(responses) {
const result = {};
for (const httpCode in responses) {
const body = this.parseBody(responses[httpCode]);
if (body !== undefined) {
result[httpCode] = body;
}
}
return result;
}
makeSchema(genericSchema, data) {
const schema = Object.assign({}, genericSchema);
const copyItems = [
'tags',
'summary',
'description',
'operationId',
];
this.copyProps(data, schema, copyItems, true);
if (data.parameters) {
this.parseParameters(schema, data.parameters);
}
const body = this.parseBody(data.requestBody);
if (body) {
schema.body = body;
}
const response = this.parseResponses(data.responses);
if (Object.keys(response).length > 0) {
schema.response = response;
}
this.removeRecursion(schema);
return schema;
}
processOperation(path, operation, operationSpec, genericSchema) {
const route = {
method: operation.toUpperCase(),
url: this.makeURL(path),
schema: this.makeSchema(genericSchema, operationSpec),
openapiPath: path,
operationId: operationSpec.operationId ||
this.makeOperationId(operation, path),
openapiSource: operationSpec,
security: this.parseSecurity(operationSpec.security || this.spec.security),
};
if (operationSpec['x-fastify-config']) {
route.config = operationSpec['x-fastify-config'];
}
this.config.routes.push(route);
}
processPaths(paths) {
const copyItems = ['summary', 'description'];
for (const path in paths) {
const genericSchema = {};
const pathSpec = paths[path];
this.copyProps(pathSpec, genericSchema, copyItems, true);
if (typeof pathSpec.parameters === 'object') {
this.parseParameters(genericSchema, pathSpec.parameters);
}
for (const pathItem in pathSpec) {
if (HttpOperations.has(pathItem)) {
this.processOperation(path, pathItem, pathSpec[pathItem], genericSchema);
}
}
}
}
parse(spec) {
this.spec = spec;
for (const item in spec) {
switch (item) {
case 'paths': {
this.processPaths(spec.paths);
break;
}
case 'components':
if (spec.components.securitySchemes) {
this.config.securitySchemes =
spec.components.securitySchemes;
} // the missing break is on purpose !
// eslint-disable-next-line no-fallthrough
default:
this.config.generic[item] = spec[item];
}
}
return this.config;
}
}