UNPKG

apibake

Version:
425 lines (424 loc) 16.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OpenApiParser = exports.DataField = exports.SchemaRef = void 0; const logger_1 = require("./utils/logger"); const string_utils_1 = require("./utils/string-utils"); class SchemaRef { constructor(text, schemaName, anchor, isArray = false) { this.text = text; this.schemaName = schemaName; this.anchor = anchor; this.isArray = isArray; } static undefined() { return new SchemaRef('undefined', 'undefined'); } isUndefined() { return this.text == 'undefined'; } toString() { return JSON.stringify(this); } } exports.SchemaRef = SchemaRef; class DataField { constructor(name, type, description, required) { this.name = name; this.type = type; this.required = required; if (description) { this.description = (0, string_utils_1.sanitizeDescription)(description); } } } exports.DataField = DataField; const allAnyOne = { 'allOf': { name: 'All of', connectWord: 'and' }, 'anyOf': { name: 'Any of', connectWord: 'or' }, 'oneOf': { name: 'One of', connectWord: 'or' }, }; function getFirstOf(spec, children) { for (const child of children) { if (spec[child]) { return { key: child, value: spec[child] }; } } return undefined; } class OpenApiParser { constructor(doc, mergeSchemasInOneSection = false) { this.doc = doc; this.mergeSchemasInOneSection = mergeSchemasInOneSection; this.firstHeaderLevel = 0; this.spec = {}; // open api spec being processed this.sectionName = ''; this.schemas = {}; } parse(apiSpec, sectionName) { var _a, _b, _c; this.spec = apiSpec; const openapiVer = (_a = this.spec['openapi']) !== null && _a !== void 0 ? _a : this.spec['swagger']; ; if (openapiVer == null) { throw Error('Invalid OpenAPI specification.'); } if (openapiVer.startsWith('1') || openapiVer.startsWith('2')) { throw Error(`Not supported OpenAPI version: ${openapiVer}, supported: 3.0.0+.`); } this.sectionName = (_b = this.spec['info']['title']) !== null && _b !== void 0 ? _b : sectionName; this.doc.newSection(this.sectionName); this.doc.header(this.firstHeaderLevel, this.sectionName); this.parseInfo(this.spec); const paths = this.spec['paths']; if (paths && Object.entries(paths).length > 0) { (0, logger_1.log)('Endpoints:'); Object.entries(paths).forEach(([key, value]) => { this.parsePath(key, value); }); } const schemas = (_c = this.spec['components']) === null || _c === void 0 ? void 0 : _c['schemas']; if (schemas && Object.entries(schemas).length > 0) { if (this.mergeSchemasInOneSection) { this.saveSchemasToParseLater(schemas); } else { this.parseSchemas(schemas); } } } // if we parse multiple api spec files - done() shoud be called only once in the end done() { if (this.mergeSchemasInOneSection && Object.entries(this.schemas).length > 0) { this.parseSchemas(this.schemas); } this.doc.finish(); } parseInfo(infoSpec) { const objectContainsOrString = (obj, fields) => { return typeof obj === 'string' || typeof obj === 'object' && fields.some(field => field in obj); }; if (infoSpec['info']) { (0, logger_1.log)(`File info:`); Object.entries(infoSpec['info']).forEach(([key, value]) => (0, logger_1.log)(` - ${key}: ${value}`)); this.doc.header(1, 'Overview'); if (infoSpec['info']['description']) { this.doc.indentStart(); this.doc.para((0, string_utils_1.sanitizeDescription)(infoSpec['info']['description'])); this.doc.lineBreak(); } if ("termsOfService" in infoSpec['info']) { this.doc.subHeader('Terms of service'); this.doc.indentStart(); this.doc.para(infoSpec['info']['termsOfService']); this.doc.indentEnd(); this.doc.lineBreak(); } if ("version" in infoSpec['info']) { this.doc.subHeader('Version'); this.doc.indentStart(); this.doc.para(infoSpec['info']['version']); this.doc.indentEnd(); this.doc.lineBreak(); } if ("license" in infoSpec['info']) { this.doc.subHeader('License'); this.doc.indentStart(); if (typeof infoSpec['info']['license'] === 'object') { this.doc.para(infoSpec['info']['license']['name']); this.doc.para(infoSpec['info']['license']['version']); } else { this.doc.para(infoSpec['info']['license']); } this.doc.indentEnd(); this.doc.lineBreak(); } if (objectContainsOrString(infoSpec['info']['contact'], ['name', 'url', 'email'])) { this.doc.subHeader('Contact'); this.doc.indentStart(); if (typeof infoSpec['info']['contact'] === 'object') { this.doc.para(infoSpec['info']['contact']['name']); this.doc.para(infoSpec['info']['contact']['url']); this.doc.para(infoSpec['info']['contact']['email']); } else if (typeof infoSpec['info']['contact'] === 'string') { this.doc.para(infoSpec['info']['contact']); } this.doc.indentEnd(); this.doc.lineBreak(); } // add other info fields if any const processedEntries = ["title", "description", "termsOfService", "version", "license", "contact"]; Object.entries(infoSpec['info']).forEach(([key, value]) => { if (processedEntries.includes(key) || value === null || typeof value !== 'string') { return; } this.doc.subHeader((0, string_utils_1.capitalizeFirst)(key)); this.doc.indentStart(); this.doc.para(value); this.doc.indentEnd(); this.doc.lineBreak(); }); this.doc.lineBreak(2); } } parsePath(path, pathSpec) { Object.entries(pathSpec).forEach(([methodName, methodSpec]) => { (0, logger_1.log)(` - ${methodName.toUpperCase()} ${path}`); this.doc.apiHeader(methodName.toUpperCase(), path, this.firstHeaderLevel + 1); this.parseMethod(methodSpec); this.doc.lineBreak(2); }); } parseMethod(methodSpec) { const summary = methodSpec['summary']; const descr = methodSpec['description']; if (summary) { this.doc.description((0, string_utils_1.sanitizeDescription)(summary)); } if (descr) { this.doc.description((0, string_utils_1.sanitizeDescription)(descr)); } const parameters = methodSpec['parameters']; if (parameters && Object.keys(parameters).length > 0) { this.doc.subHeader('Request Parameters:'); // TODO: for some reason sometimes there are duplicated parameters this.doc.indentStart(); const uniqParams = parameters; uniqParams.forEach((param) => { this.parseParameter(param); }); this.doc.indentEnd(); this.doc.lineBreak(); } const body = methodSpec['requestBody']; if (body && Object.keys(body).length > 0) { this.doc.subHeader('Request Body:'); this.doc.indentStart(); this.parseBody(body); this.doc.indentEnd(); } const responses = methodSpec['responses']; if (responses) { Object.entries(responses).forEach(([key, value]) => { this.doc.subHeader(`Response ${key}:`); this.doc.indentStart(); this.parseBody(value); this.doc.indentEnd(); }); } } parseBody(bodySpec) { const descr = bodySpec['description']; if (descr) { this.doc.description((0, string_utils_1.sanitizeDescription)(descr)); } const contentSpec = bodySpec['content']; let emptyBody = true; if (contentSpec) { Object.entries(contentSpec).forEach(([contentType, contentSpec]) => { const schema = contentSpec['schema']; if (schema) { this.doc.contentType(contentType, { lineContinues: true }); this.parseSchema(schema, { alwaysShowType: true, lineContinues: true }); emptyBody = false; } this.parseExamples(contentSpec['examples']); }); } if (emptyBody) { // this.doc.para('Empty body.'); } this.doc.paraBreak(); } parseExamples(spec) { if (!spec) { return; } Object.entries(spec).forEach(([name, spec]) => { if ((spec === null || spec === void 0 ? void 0 : spec.value) && Object.keys(spec.value).length > 0) { const exampleBody = typeof spec.value === 'object' ? JSON.stringify(spec.value, null, 2) : spec.value; if (exampleBody) { this.doc.example(name, exampleBody); } } }); } saveSchemasToParseLater(schemas) { Object.entries(schemas).forEach(([key, value]) => { if (!this.schemas[key]) { this.schemas[key] = value; } else { (0, logger_1.log)(`!Duplicated schema: ${key}`); } }); } parseSchemas(schemas) { // Example: // { // "Pet": { // "allOf": [ // { // "$ref": "#/components/schemas/NewPet" // }, // { // "type": "object", // "required": [ // "id" // ], // "properties": { // "id": { // "type": "integer", // "format": "int64" // } // } // } // ] // }, // "NewPet": { // "type": "object", // "required": [ // "name" // ], // "properties": { // "name": { // "type": "string" // }, // "tag": { // "type": "string" // } // } // }, // } (0, logger_1.log)('Schemas:'); const headerLevel = this.mergeSchemasInOneSection ? this.firstHeaderLevel : this.firstHeaderLevel + 1; this.doc.newSection('Schemas'); this.doc.header(headerLevel, 'Schemas'); Object.entries(schemas).forEach(([key, value]) => { (0, logger_1.log)(` - ${key}`); this.doc.header(headerLevel + 1, key, this.schemaAnchor(key)); this.parseSchema(value); this.doc.lineBreak(2); }); } parseSchema(schemaSpec, options) { var _a, _b; if (!schemaSpec) { return; } // If schema is aggregate: allOf, anyOf, oneOf const found = getFirstOf(schemaSpec, Object.keys(allAnyOne)); if ((found === null || found === void 0 ? void 0 : found.value) && Array.isArray(found.value)) { if (options === null || options === void 0 ? void 0 : options.lineContinues) { this.doc.nextLine(); this.doc.paraBreak(); } const aggregate = allAnyOne[found.key]; this.doc.description(`${aggregate.name}:`); // TODO: for allOf combine schemas into a single object found.value.forEach((it, index, arr) => { this.doc.indentStart(); this.parseSchema(it); this.doc.indentEnd(); if (index < arr.length - 1) { this.doc.description(aggregate.connectWord); } }); return; } // If schema is a reference if (options === null || options === void 0 ? void 0 : options.lineContinues) { this.doc.text(' | ', {}, { continued: true }); } const ref = this.parseSchemaRef(schemaSpec); if (ref.schemaName && ref.anchor) { const foundRef = (_b = (_a = this.spec['components']) === null || _a === void 0 ? void 0 : _a['schemas']) === null || _b === void 0 ? void 0 : _b[ref.schemaName]; if (foundRef) { this.doc.textRef(ref.text, ref.anchor); if (options === null || options === void 0 ? void 0 : options.lineContinues) { this.doc.paraBreak(); } this.parseSchema(foundRef); } else { this.doc.textRef(ref.text, ref.anchor); } return; } else { if (!ref.isUndefined() && ref.text !== 'object') { this.doc.text(ref.text); } } // If schema defined explicetly const properties = schemaSpec['properties']; if (properties) { const required = schemaSpec['required']; const dataFields = []; Object.entries(properties).forEach(([key, value]) => { const typeRef = this.parseSchemaRef(value); dataFields.push(new DataField(key, typeRef, value['description'], required === null || required === void 0 ? void 0 : required.includes(key))); }); this.doc.objectSchema(dataFields); } else if (schemaSpec['enum']) { this.doc.enumValues(schemaSpec['enum']); } this.doc.paraBreak(); } schemaNameByRef(ref) { const refPath = '#/components/schemas/'; const start = ref.indexOf(refPath); if (start >= 0) { return ref.substring(ref.indexOf(refPath) + refPath.length); } return ref; } schemaDefinitionExists(name) { var _a, _b; return ((_b = (_a = this.spec['components']) === null || _a === void 0 ? void 0 : _a['schemas']) === null || _b === void 0 ? void 0 : _b[name]) ? true : false; } schemaAnchor(schemaName) { return this.mergeSchemasInOneSection ? `schemas:${schemaName}` : `${this.sectionName}:${schemaName}`; } parseSchemaRef(schemaRef) { if (schemaRef['type']) { if (schemaRef['type'] === 'array' && schemaRef['items']) { const typeRef = this.parseSchemaRef(schemaRef['items']); return new SchemaRef(`Array<${typeRef.text}>`, typeRef.schemaName, typeRef.anchor, true); } else { return new SchemaRef(schemaRef['type'], schemaRef['type']); } } else if (schemaRef['\$ref']) { const schemaName = this.schemaNameByRef(schemaRef['\$ref']); const anchor = this.schemaDefinitionExists(schemaName) ? this.schemaAnchor(schemaName) : undefined; if (!anchor) { (0, logger_1.log)(`! No definition for ref: ${schemaRef['\$ref']}`); } return new SchemaRef(schemaName, schemaName, anchor); } return SchemaRef.undefined(); } parseParameter(paramSpec) { if (paramSpec['name']) { let typeRef = undefined; if (paramSpec['schema'] != null) { typeRef = this.parseSchemaRef(paramSpec['schema']); } if (typeRef === undefined || typeRef.isUndefined()) { const leaf = getFirstOf(paramSpec, Object.keys(allAnyOne)); // TODO: parse entire array of possible schemas if (leaf === null || leaf === void 0 ? void 0 : leaf.value[0]) { typeRef = this.parseSchemaRef(leaf === null || leaf === void 0 ? void 0 : leaf.value[0]); } } this.doc.dataFields([ new DataField(paramSpec['name'], typeRef, paramSpec['description'], paramSpec['required']) ]); } } } exports.OpenApiParser = OpenApiParser;