From efb987359a20a700b4644d21bf8eb677950b955c Mon Sep 17 00:00:00 2001 From: Garber Date: Mon, 18 May 2020 14:55:31 +0300 Subject: [PATCH 1/4] Added support for injecting MD files. Any extension starts with 'x-md-*' will be treated as md file path, and loaded into 'md_*' property. The content of the MD file can be accessed in the templates. --- lib/openapi3.js | 177 +++++++++++++++++++++++++++++------------------- 1 file changed, 107 insertions(+), 70 deletions(-) diff --git a/lib/openapi3.js b/lib/openapi3.js index d673b05e..a1cf682a 100644 --- a/lib/openapi3.js +++ b/lib/openapi3.js @@ -18,22 +18,35 @@ const dereference = require('reftools/lib/dereference.js').dereference; const clone = require('reftools/lib/clone.js').circularClone; const swagger2openapi = require('swagger2openapi'); +const fs = require('fs').promises; + const common = require('./common.js'); let templates; -function convertToToc(source, data) { +async function loadMD(sourceSection, destSection) { + for (let field in sourceSection) { + if (field.startsWith("x-md-")) { + //Replacing '-' so that field can be used in dot templates + let newFieldName = field.replace("x-md-", "md_"); + destSection[newFieldName] = await fs.readFile(sourceSection[field], 'utf8'); + } + } +} + +async function convertToToc(source, data) { let resources = {}; - resources[data.translations.defaultTag] = { count: 0, methods: {} }; + resources[data.translations.defaultTag] = {count: 0, methods: {}}; if (source.tags) { for (let tag of source.tags) { - resources[tag.name] = { count: 0, methods: {}, description: tag.description, externalDocs: tag.externalDocs }; + resources[tag.name] = {count: 0, methods: {}, description: tag.description, externalDocs: tag.externalDocs}; } } for (var p in source.paths) { if (!p.startsWith('x-')) { for (var m in source.paths[p]) { if ((m !== 'parameters') && (m !== 'summary') && (m !== 'description') && (!m.startsWith('x-'))) { + var method = {}; method.operation = source.paths[p][m]; method.pathItem = source.paths[p]; @@ -53,14 +66,21 @@ function convertToToc(source, data) { tagDescription = tagData.description; } if (!resources[tagName]) { - resources[tagName] = { count: 0, methods: {}, description: tagDescription }; + resources[tagName] = {count: 0, methods: {}, description: tagDescription}; } resources[tagName].count++; resources[tagName].methods[sMethodUniqueName] = method; + await loadMD(source.paths[p], resources[tagName]); } } } } + + + for (let tag of source.tags) { + //Load MD for tags + await loadMD(tag, resources[tag.name]); + } for (let r in resources) { if (resources[r].count <= 0) delete resources[r]; } @@ -71,11 +91,11 @@ function getTagGroup(tag, tagGroups) { if (tagGroups) { for (let group of tagGroups) { if (group.tags.indexOf(tag) > -1) { - return { name: group.title, description: group.description }; + return {name: group.title, description: group.description}; } } } - return { name: tag, description: '' }; + return {name: tag, description: ''}; } function fakeProdCons(data) { @@ -104,8 +124,7 @@ function fakeProdCons(data) { let schema = op.requestBody.content[rb].schema; if (schema['x-widdershins-oldRef']) { data.bodyParameter.refName = schema['x-widdershins-oldRef'].replace('#/components/schemas/', ''); - } - else { + } else { if ((schema.type === 'array') && (schema.items) && (schema.items['x-widdershins-oldRef'])) { data.bodyParameter.refName = schema.items['x-widdershins-oldRef'].replace('#/components/schemas/', ''); } @@ -117,14 +136,15 @@ function fakeProdCons(data) { let key = Object.keys(op.requestBody.content[rb].examples)[0]; data.bodyParameter.exampleValues.object = op.requestBody.content[rb].examples[key].value; data.bodyParameter.exampleValues.description = op.requestBody.content[rb].examples[key].description; - } - else { - data.bodyParameter.exampleValues.object = common.getSample(op.requestBody.content[rb].schema, data.options, { skipReadOnly: true, quiet: true }, data.api); + } else { + data.bodyParameter.exampleValues.object = common.getSample(op.requestBody.content[rb].schema, data.options, { + skipReadOnly: true, + quiet: true + }, data.api); } if (typeof data.bodyParameter.exampleValues.object === 'object') { data.bodyParameter.exampleValues.json = safejson(data.bodyParameter.exampleValues.object, null, 2); - } - else { + } else { data.bodyParameter.exampleValues.json = data.bodyParameter.exampleValues.object; } } @@ -201,11 +221,13 @@ function getParameters(data) { if (param.refName) param.safeType = '[' + param.refName + '](#schema' + param.refName.toLowerCase() + ')'; } if (pSchema) { - param.exampleValues.object = param.example || param.default || common.getSample(pSchema, data.options, { skipReadOnly: true, quiet: true }, data.api); + param.exampleValues.object = param.example || param.default || common.getSample(pSchema, data.options, { + skipReadOnly: true, + quiet: true + }, data.api); if (typeof param.exampleValues.object === 'object') { param.exampleValues.json = safejson(param.exampleValues.object, null, 2); - } - else { + } else { param.exampleValues.json = "'" + param.exampleValues.object + "'"; } } @@ -266,8 +288,7 @@ function getParameters(data) { if (data.operation.security.length) { effSecurity = Object.keys(data.operation.security[0]); } - } - else if (data.api.security && data.api.security.length) { + } else if (data.api.security && data.api.security.length) { effSecurity = Object.keys(data.api.security[0]); } if (effSecurity && effSecurity.length && data.api.components && data.api.components.securitySchemes) { @@ -285,8 +306,7 @@ function getParameters(data) { authHeader.exampleValues.object = 'Bearer {access-token}'; authHeader.exampleValues.json = "'" + authHeader.exampleValues.object + "'"; data.allHeaders.push(authHeader); - } - else if ((secScheme.type === 'apiKey') && (secScheme.in === 'header')) { + } else if ((secScheme.type === 'apiKey') && (secScheme.in === 'header')) { let authHeader = {}; authHeader.name = secScheme.name; authHeader.type = 'string'; @@ -325,8 +345,7 @@ function getBodyParameterExamples(data) { let xmlWrap = false; if (data.bodyParameter.schema && data.bodyParameter.schema.xml) { xmlWrap = data.bodyParameter.schema.xml.name; - } - else if (data.bodyParameter.schema && data.bodyParameter.schema["x-widdershins-oldRef"]) { + } else if (data.bodyParameter.schema && data.bodyParameter.schema["x-widdershins-oldRef"]) { xmlWrap = data.bodyParameter.schema["x-widdershins-oldRef"].split('/').pop(); } if (common.doContentType(data.consumes, 'json')) { @@ -384,7 +403,7 @@ function fakeBodyParameter(data) { if ((param.schema.type === 'object') && (data.options.expandBody || (!param.schema["x-widdershins-oldRef"]))) { let offset = (data.options.omitBody ? -1 : 0); - let props = common.schemaToArray(data.bodyParameter.schema, offset, { trim: true }, data); + let props = common.schemaToArray(data.bodyParameter.schema, offset, {trim: true}, data); for (let block of props) { for (let prop of block.rows) { @@ -409,7 +428,9 @@ function fakeBodyParameter(data) { function mergePathParameters(data) { if (!data.parameters || !Array.isArray(data.parameters)) data.parameters = []; data.parameters = data.parameters.concat(data.method.pathParameters || []); - data.parameters = data.parameters.filter((param, index, self) => self.findIndex((p) => { return p.name === param.name && p.in === param.in; }) === index || param.in === 'body'); + data.parameters = data.parameters.filter((param, index, self) => self.findIndex((p) => { + return p.name === param.name && p.in === param.in; + }) === index || param.in === 'body'); } function getResponses(data) { @@ -441,8 +462,7 @@ function getResponses(data) { let schemaName = contentType.schema["x-widdershins-oldRef"].replace('#/components/schemas/', ''); entry.schema = '[' + schemaName + '](#schema' + schemaName.toLowerCase() + ')'; entry.$ref = true; - } - else { + } else { if (contentType.schema && contentType.schema.type && (contentType.schema.type !== 'object') && (contentType.schema.type !== 'array')) { entry.schema = contentType.schema.type; } @@ -460,12 +480,10 @@ function convertExample(ex) { if (typeof ex === 'string') { try { return yaml.parse(ex); - } - catch (e) { + } catch (e) { return ex; } - } - else return ex; + } else return ex; } function getResponseExamples(data) { @@ -482,13 +500,19 @@ function getResponseExamples(data) { if (contentType.examples) { for (let ctei in contentType.examples) { let example = contentType.examples[ctei]; - examples.push({ description: example.description || response.description, value: common.clean(convertExample(example.value)), cta: cta }); + examples.push({ + description: example.description || response.description, + value: common.clean(convertExample(example.value)), + cta: cta + }); } - } - else if (contentType.example) { - examples.push({ description: resp + ' ' + data.translations.response, value: common.clean(convertExample(contentType.example)), cta: cta }); - } - else if (contentType.schema) { + } else if (contentType.example) { + examples.push({ + description: resp + ' ' + data.translations.response, + value: common.clean(convertExample(contentType.example)), + cta: cta + }); + } else if (contentType.schema) { let obj = contentType.schema; let autoCT = ''; if (common.doContentType(cta, 'json')) autoCT = 'json'; @@ -501,11 +525,15 @@ function getResponseExamples(data) { let xmlWrap = false; if (obj && obj.xml && obj.xml.name) { xmlWrap = obj.xml.name; - } - else if (obj["x-widdershins-oldRef"]) { + } else if (obj["x-widdershins-oldRef"]) { xmlWrap = obj["x-widdershins-oldRef"].split('/').pop(); } - examples.push({ description: resp + ' ' + data.translations.response, value: common.getSample(obj, data.options, { skipWriteOnly: true, quiet: true }, data.api), cta: cta, xmlWrap: xmlWrap }); + examples.push({ + description: resp + ' ' + data.translations.response, + value: common.getSample(obj, data.options, {skipWriteOnly: true, quiet: true}, data.api), + cta: cta, + xmlWrap: xmlWrap + }); } } } @@ -599,16 +627,18 @@ function getAuthenticationStr(data) { } function convertInner(api, options) { - return new Promise(function (resolve, reject) { + return new Promise(async function (resolve, reject) { let defaults = {}; defaults.title = 'API'; - defaults.language_tabs = [{ 'shell': 'Shell' }, { 'http': 'HTTP' }, { 'javascript': 'JavaScript' }, { 'ruby': 'Ruby' }, { 'python': 'Python' }, { 'php': 'PHP' }, { 'java': 'Java' }, { 'go': 'Go' }]; + defaults.language_tabs = [{'shell': 'Shell'}, {'http': 'HTTP'}, {'javascript': 'JavaScript'}, {'ruby': 'Ruby'}, {'python': 'Python'}, {'php': 'PHP'}, {'java': 'Java'}, {'go': 'Go'}]; defaults.toc_footers = []; defaults.includes = []; defaults.search = true; defaults.theme = 'darkula'; defaults.headings = 2; - defaults.templateCallback = function (template, stage, data) { return data; }; + defaults.templateCallback = function (template, stage, data) { + return data; + }; options = Object.assign({}, defaults, options); @@ -616,22 +646,23 @@ function convertInner(api, options) { if (options.verbose) console.warn('starting deref', api.info.title); if (api.components) { data.components = clone(api.components); - } - else { + } else { data.components = {}; } - data.api = dereference(api, api, { verbose: options.verbose, $ref: 'x-widdershins-oldRef' }); + data.api = dereference(api, api, {verbose: options.verbose, $ref: 'x-widdershins-oldRef'}); if (options.verbose) console.warn('finished deref'); if (data.api.components && data.api.components.schemas && data.api.components.schemas["x-widdershins-oldRef"]) { delete data.api.components.schemas["x-widdershins-oldRef"]; } + //Load root level MD + await loadMD(api, data); if (typeof templates === 'undefined') { - templates = dot.process({ path: path.join(__dirname, '..', 'templates', 'openapi3') }); + templates = dot.process({path: path.join(__dirname, '..', 'templates', 'openapi3')}); } if (options.user_templates) { - templates = Object.assign(templates, dot.process({ path: options.user_templates })); + templates = Object.assign(templates, dot.process({path: options.user_templates})); } data.options = options; data.translations = {}; @@ -662,16 +693,14 @@ function convertInner(api, options) { data.header = header; data.title_prefix = (data.api.info && data.api.info.version ? common.slugify((data.api.info.title || '').trim() || 'API') : ''); data.templates = templates; - data.resources = convertToToc(api, data); + data.resources = await convertToToc(api, data); if (data.api.servers && data.api.servers.length) { data.servers = data.api.servers; - } - else if (options.loadedFrom) { - data.servers = [{ url: options.loadedFrom }]; - } - else { - data.servers = [{ url: '//' }]; + } else if (options.loadedFrom) { + data.servers = [{url: options.loadedFrom}]; + } else { + data.servers = [{url: '//'}]; } data.host = up.parse(data.servers[0].url).host; data.protocol = up.parse(data.servers[0].url).protocol; @@ -684,14 +713,18 @@ function convertInner(api, options) { data.utils.yaml = yaml; data.utils.inspect = util.inspect; data.utils.safejson = safejson; - data.utils.isPrimitive = function (t) { return (t && (t !== 'object') && (t !== 'array')) }; + data.utils.isPrimitive = function (t) { + return (t && (t !== 'object') && (t !== 'array')) + }; data.utils.toPrimitive = common.toPrimitive; - data.utils.slashes = function (s) { return s.replace(/\/+/g, '/').replace(':/', '://'); }; + data.utils.slashes = function (s) { + return s.replace(/\/+/g, '/').replace(':/', '://'); + }; data.utils.slugify = common.slugify; data.utils.getSample = common.getSample; data.utils.schemaToArray = common.schemaToArray; data.utils.fakeProdCons = fakeProdCons; - data.utils.getParameters = getParameters; + data.utils.getParameters = getParameters; data.utils.getCodeSamples = common.getCodeSamples; data.utils.getBodyParameterExamples = getBodyParameterExamples; data.utils.fakeBodyParameter = fakeBodyParameter; @@ -708,15 +741,20 @@ function convertInner(api, options) { let content = ''; if (!options.omitHeader) content += '---\n' + yaml.stringify(header) + '\n---\n\n'; data = options.templateCallback('main', 'pre', data); - if (data.append) { content += data.append; delete data.append; } + if (data.append) { + content += data.append; + delete data.append; + } try { content += templates.main(data); - } - catch (ex) { + } catch (ex) { throw ex; } data = options.templateCallback('main', 'post', data); - if (data.append) { content += data.append; delete data.append; } + if (data.append) { + content += data.append; + delete data.append; + } content = common.removeDupeBlankLines(content); if (options.html) content = common.html(content, header, options); @@ -727,15 +765,14 @@ function convertInner(api, options) { function convert(api, options) { if (options.resolve) { - return swagger2openapi.convertObj(api, { resolve: true, source: options.source, verbose: options.verbose }) - .then(sOptions => { - return convertInner(sOptions.openapi, options); - }) - .catch(err => { - console.error(err.message); - }); - } - else { + return swagger2openapi.convertObj(api, {resolve: true, source: options.source, verbose: options.verbose}) + .then(sOptions => { + return convertInner(sOptions.openapi, options); + }) + .catch(err => { + console.error(err.message); + }); + } else { return convertInner(api, options); } } From ba5249281f8beb18e8a5bde1fadca1f6d8b2b059 Mon Sep 17 00:00:00 2001 From: Garber Date: Tue, 2 Jun 2020 20:51:06 +0300 Subject: [PATCH 2/4] Moved loadMD to common.js, changed it to be sync instead of async, and allowing the MD field prefix to be configured --- lib/common.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/common.js b/lib/common.js index e7a421d0..2ef1853e 100644 --- a/lib/common.js +++ b/lib/common.js @@ -1,7 +1,6 @@ 'use strict'; const fs = require('fs'); - const jptr = require('reftools/lib/jptr.js').jptr; const sampler = require('openapi-sampler'); const safejson = require('fast-safe-stringify'); @@ -526,6 +525,21 @@ function html(markdown,header,options) { return preface+md.render(markdown); } +function loadMD(sourceSection, destSection, mdFieldPrefix) { + for (let field in sourceSection) { + if (field.startsWith(mdFieldPrefix)) { + //Replacing '-' so that field can be used in dot templates + let newFieldName = field.split("-").join("_"); + try { + destSection[newFieldName] = md.render(fs.readFileSync(sourceSection[field],'utf8')); + } + catch (ex) { + throw ex; + } + } + } +} + module.exports = { statusCodes : statusCodes, doContentType : doContentType, @@ -541,6 +555,7 @@ module.exports = { schemaToArray : schemaToArray, removeDupeBlankLines: removeDupeBlankLines, toPrimitive: toPrimitive, - html : html + html : html, + loadMD: loadMD }; From ac895476415e9dac334f7db602c8a42cac6066da Mon Sep 17 00:00:00 2001 From: Garber Date: Tue, 2 Jun 2020 20:54:46 +0300 Subject: [PATCH 3/4] Added support to loadMD anywhere OpenAPISpec allows extensions, added to README --- README.md | 1 + lib/openapi3.js | 191 ++++++++++++++++++++++-------------------------- widdershins.js | 3 + 3 files changed, 90 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index c883f137..ca917240 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ node widdershins --search false --language_tabs 'ruby:Ruby' 'python:Python' --su | --omitHeader | options.omitHeader | `boolean` | `false` | Omit the header / YAML front-matter in the generated Markdown file. | | --resolve | options.resolve | `boolean` | `false` | Resolve external $refs, using the `source` parameter or the input file as the base location. | | --shallowSchemas | options.shallowSchemas | `boolean` | `false` | When referring to a schema with a $ref, don't show the full contents of the schema. | +| --loadMdPrefix | options.loadMdPrefix | `string` | `x-md-`| Prefix of OpenAPI extension that indicates MD files paths to load and can be injected anywhere in the output MD | N/A | options.source | `string` | None | The absolute location or URL of the source file to use as the base to resolve relative references ($refs) from; required if options.resolve is set to true. For CLI commands, Widdershins uses the input file as the base for the $refs. | | --summary | options.tocSummary | `boolean` | `false` | Use the operation summary as the TOC entry instead of the ID. | | --useBodyName | options.useBodyName | `boolean` | Use original param name for OpenAPI 2.0 body parameter. | diff --git a/lib/openapi3.js b/lib/openapi3.js index a1cf682a..7629dffa 100644 --- a/lib/openapi3.js +++ b/lib/openapi3.js @@ -18,28 +18,19 @@ const dereference = require('reftools/lib/dereference.js').dereference; const clone = require('reftools/lib/clone.js').circularClone; const swagger2openapi = require('swagger2openapi'); -const fs = require('fs').promises; - const common = require('./common.js'); let templates; +let loadMdPrefix; -async function loadMD(sourceSection, destSection) { - for (let field in sourceSection) { - if (field.startsWith("x-md-")) { - //Replacing '-' so that field can be used in dot templates - let newFieldName = field.replace("x-md-", "md_"); - destSection[newFieldName] = await fs.readFile(sourceSection[field], 'utf8'); - } - } -} - -async function convertToToc(source, data) { +function convertToToc(source, data) { let resources = {}; - resources[data.translations.defaultTag] = {count: 0, methods: {}}; + resources[data.translations.defaultTag] = { count: 0, methods: {} }; if (source.tags) { for (let tag of source.tags) { - resources[tag.name] = {count: 0, methods: {}, description: tag.description, externalDocs: tag.externalDocs}; + resources[tag.name] = { count: 0, methods: {}, description: tag.description, externalDocs: tag.externalDocs }; + //Load MD for tags + common.loadMD(tag, resources[tag.name], loadMdPrefix); } } for (var p in source.paths) { @@ -49,6 +40,8 @@ async function convertToToc(source, data) { var method = {}; method.operation = source.paths[p][m]; + ///load operation level MD + common.loadMD(method.operation, method.operation, loadMdPrefix); method.pathItem = source.paths[p]; method.verb = m; method.path = p; @@ -66,21 +59,18 @@ async function convertToToc(source, data) { tagDescription = tagData.description; } if (!resources[tagName]) { - resources[tagName] = {count: 0, methods: {}, description: tagDescription}; + resources[tagName] = { count: 0, methods: {}, description: tagDescription }; } resources[tagName].count++; resources[tagName].methods[sMethodUniqueName] = method; - await loadMD(source.paths[p], resources[tagName]); + //load MD for paths + common.loadMD(source.paths[p], resources[tagName], loadMdPrefix); } } } } - for (let tag of source.tags) { - //Load MD for tags - await loadMD(tag, resources[tag.name]); - } for (let r in resources) { if (resources[r].count <= 0) delete resources[r]; } @@ -91,11 +81,11 @@ function getTagGroup(tag, tagGroups) { if (tagGroups) { for (let group of tagGroups) { if (group.tags.indexOf(tag) > -1) { - return {name: group.title, description: group.description}; + return { name: group.title, description: group.description }; } } } - return {name: tag, description: ''}; + return { name: tag, description: '' }; } function fakeProdCons(data) { @@ -124,7 +114,8 @@ function fakeProdCons(data) { let schema = op.requestBody.content[rb].schema; if (schema['x-widdershins-oldRef']) { data.bodyParameter.refName = schema['x-widdershins-oldRef'].replace('#/components/schemas/', ''); - } else { + } + else { if ((schema.type === 'array') && (schema.items) && (schema.items['x-widdershins-oldRef'])) { data.bodyParameter.refName = schema.items['x-widdershins-oldRef'].replace('#/components/schemas/', ''); } @@ -136,15 +127,14 @@ function fakeProdCons(data) { let key = Object.keys(op.requestBody.content[rb].examples)[0]; data.bodyParameter.exampleValues.object = op.requestBody.content[rb].examples[key].value; data.bodyParameter.exampleValues.description = op.requestBody.content[rb].examples[key].description; - } else { - data.bodyParameter.exampleValues.object = common.getSample(op.requestBody.content[rb].schema, data.options, { - skipReadOnly: true, - quiet: true - }, data.api); + } + else { + data.bodyParameter.exampleValues.object = common.getSample(op.requestBody.content[rb].schema, data.options, { skipReadOnly: true, quiet: true }, data.api); } if (typeof data.bodyParameter.exampleValues.object === 'object') { data.bodyParameter.exampleValues.json = safejson(data.bodyParameter.exampleValues.object, null, 2); - } else { + } + else { data.bodyParameter.exampleValues.json = data.bodyParameter.exampleValues.object; } } @@ -221,13 +211,11 @@ function getParameters(data) { if (param.refName) param.safeType = '[' + param.refName + '](#schema' + param.refName.toLowerCase() + ')'; } if (pSchema) { - param.exampleValues.object = param.example || param.default || common.getSample(pSchema, data.options, { - skipReadOnly: true, - quiet: true - }, data.api); + param.exampleValues.object = param.example || param.default || common.getSample(pSchema, data.options, { skipReadOnly: true, quiet: true }, data.api); if (typeof param.exampleValues.object === 'object') { param.exampleValues.json = safejson(param.exampleValues.object, null, 2); - } else { + } + else { param.exampleValues.json = "'" + param.exampleValues.object + "'"; } } @@ -288,16 +276,19 @@ function getParameters(data) { if (data.operation.security.length) { effSecurity = Object.keys(data.operation.security[0]); } - } else if (data.api.security && data.api.security.length) { + } + else if (data.api.security && data.api.security.length) { effSecurity = Object.keys(data.api.security[0]); } if (effSecurity && effSecurity.length && data.api.components && data.api.components.securitySchemes) { for (let ess of effSecurity) { if (data.api.components.securitySchemes[ess]) { let secScheme = data.api.components.securitySchemes[ess]; + let authHeader = {}; + //load Security Scheme level MD + common.loadMD(secScheme, authHeader, loadMdPrefix); if (!existingAuth && ((secScheme.type === 'oauth2') || (secScheme.type === 'openIdConnect') || ((secScheme.type === 'http') && (secScheme.scheme === 'bearer')))) { - let authHeader = {}; authHeader.name = 'Authorization'; authHeader.type = 'string'; authHeader.in = 'header'; @@ -306,8 +297,8 @@ function getParameters(data) { authHeader.exampleValues.object = 'Bearer {access-token}'; authHeader.exampleValues.json = "'" + authHeader.exampleValues.object + "'"; data.allHeaders.push(authHeader); - } else if ((secScheme.type === 'apiKey') && (secScheme.in === 'header')) { - let authHeader = {}; + } + else if ((secScheme.type === 'apiKey') && (secScheme.in === 'header')) { authHeader.name = secScheme.name; authHeader.type = 'string'; authHeader.in = 'header'; @@ -345,7 +336,8 @@ function getBodyParameterExamples(data) { let xmlWrap = false; if (data.bodyParameter.schema && data.bodyParameter.schema.xml) { xmlWrap = data.bodyParameter.schema.xml.name; - } else if (data.bodyParameter.schema && data.bodyParameter.schema["x-widdershins-oldRef"]) { + } + else if (data.bodyParameter.schema && data.bodyParameter.schema["x-widdershins-oldRef"]) { xmlWrap = data.bodyParameter.schema["x-widdershins-oldRef"].split('/').pop(); } if (common.doContentType(data.consumes, 'json')) { @@ -403,7 +395,7 @@ function fakeBodyParameter(data) { if ((param.schema.type === 'object') && (data.options.expandBody || (!param.schema["x-widdershins-oldRef"]))) { let offset = (data.options.omitBody ? -1 : 0); - let props = common.schemaToArray(data.bodyParameter.schema, offset, {trim: true}, data); + let props = common.schemaToArray(data.bodyParameter.schema, offset, { trim: true }, data); for (let block of props) { for (let prop of block.rows) { @@ -428,9 +420,7 @@ function fakeBodyParameter(data) { function mergePathParameters(data) { if (!data.parameters || !Array.isArray(data.parameters)) data.parameters = []; data.parameters = data.parameters.concat(data.method.pathParameters || []); - data.parameters = data.parameters.filter((param, index, self) => self.findIndex((p) => { - return p.name === param.name && p.in === param.in; - }) === index || param.in === 'body'); + data.parameters = data.parameters.filter((param, index, self) => self.findIndex((p) => { return p.name === param.name && p.in === param.in; }) === index || param.in === 'body'); } function getResponses(data) { @@ -462,7 +452,8 @@ function getResponses(data) { let schemaName = contentType.schema["x-widdershins-oldRef"].replace('#/components/schemas/', ''); entry.schema = '[' + schemaName + '](#schema' + schemaName.toLowerCase() + ')'; entry.$ref = true; - } else { + } + else { if (contentType.schema && contentType.schema.type && (contentType.schema.type !== 'object') && (contentType.schema.type !== 'array')) { entry.schema = contentType.schema.type; } @@ -470,6 +461,8 @@ function getResponses(data) { } entry.content = response.content; entry.links = response.links; + //load response level MD + common.loadMD(response, entry, loadMdPrefix); responses.push(entry); } } @@ -480,10 +473,12 @@ function convertExample(ex) { if (typeof ex === 'string') { try { return yaml.parse(ex); - } catch (e) { + } + catch (e) { return ex; } - } else return ex; + } + else return ex; } function getResponseExamples(data) { @@ -500,19 +495,13 @@ function getResponseExamples(data) { if (contentType.examples) { for (let ctei in contentType.examples) { let example = contentType.examples[ctei]; - examples.push({ - description: example.description || response.description, - value: common.clean(convertExample(example.value)), - cta: cta - }); + examples.push({ description: example.description || response.description, value: common.clean(convertExample(example.value)), cta: cta }); } - } else if (contentType.example) { - examples.push({ - description: resp + ' ' + data.translations.response, - value: common.clean(convertExample(contentType.example)), - cta: cta - }); - } else if (contentType.schema) { + } + else if (contentType.example) { + examples.push({ description: resp + ' ' + data.translations.response, value: common.clean(convertExample(contentType.example)), cta: cta }); + } + else if (contentType.schema) { let obj = contentType.schema; let autoCT = ''; if (common.doContentType(cta, 'json')) autoCT = 'json'; @@ -525,15 +514,11 @@ function getResponseExamples(data) { let xmlWrap = false; if (obj && obj.xml && obj.xml.name) { xmlWrap = obj.xml.name; - } else if (obj["x-widdershins-oldRef"]) { + } + else if (obj["x-widdershins-oldRef"]) { xmlWrap = obj["x-widdershins-oldRef"].split('/').pop(); } - examples.push({ - description: resp + ' ' + data.translations.response, - value: common.getSample(obj, data.options, {skipWriteOnly: true, quiet: true}, data.api), - cta: cta, - xmlWrap: xmlWrap - }); + examples.push({ description: resp + ' ' + data.translations.response, value: common.getSample(obj, data.options, {skipWriteOnly: true, quiet: true}, data.api), cta: cta, xmlWrap: xmlWrap }); } } } @@ -627,42 +612,41 @@ function getAuthenticationStr(data) { } function convertInner(api, options) { - return new Promise(async function (resolve, reject) { + return new Promise(function (resolve, reject) { let defaults = {}; defaults.title = 'API'; - defaults.language_tabs = [{'shell': 'Shell'}, {'http': 'HTTP'}, {'javascript': 'JavaScript'}, {'ruby': 'Ruby'}, {'python': 'Python'}, {'php': 'PHP'}, {'java': 'Java'}, {'go': 'Go'}]; + defaults.language_tabs = [{ 'shell': 'Shell' }, { 'http': 'HTTP' }, { 'javascript': 'JavaScript' }, { 'ruby': 'Ruby' }, { 'python': 'Python' }, { 'php': 'PHP' }, { 'java': 'Java' }, { 'go': 'Go' }]; defaults.toc_footers = []; defaults.includes = []; defaults.search = true; defaults.theme = 'darkula'; defaults.headings = 2; - defaults.templateCallback = function (template, stage, data) { - return data; - }; - + defaults.templateCallback = function (template, stage, data) { return data; }; + defaults.loadMdPrefix = 'x-md-'; options = Object.assign({}, defaults, options); - + loadMdPrefix = options.loadMdPrefix; let data = {}; if (options.verbose) console.warn('starting deref', api.info.title); if (api.components) { data.components = clone(api.components); - } else { + } + else { data.components = {}; } - data.api = dereference(api, api, {verbose: options.verbose, $ref: 'x-widdershins-oldRef'}); + data.api = dereference(api, api, { verbose: options.verbose, $ref: 'x-widdershins-oldRef' }); if (options.verbose) console.warn('finished deref'); if (data.api.components && data.api.components.schemas && data.api.components.schemas["x-widdershins-oldRef"]) { delete data.api.components.schemas["x-widdershins-oldRef"]; } //Load root level MD - await loadMD(api, data); + common.loadMD(api, data, loadMdPrefix); if (typeof templates === 'undefined') { - templates = dot.process({path: path.join(__dirname, '..', 'templates', 'openapi3')}); + templates = dot.process({ path: path.join(__dirname, '..', 'templates', 'openapi3') }); } if (options.user_templates) { - templates = Object.assign(templates, dot.process({path: options.user_templates})); + templates = Object.assign(templates, dot.process({ path: options.user_templates })); } data.options = options; data.translations = {}; @@ -693,13 +677,15 @@ function convertInner(api, options) { data.header = header; data.title_prefix = (data.api.info && data.api.info.version ? common.slugify((data.api.info.title || '').trim() || 'API') : ''); data.templates = templates; - data.resources = await convertToToc(api, data); + data.resources = convertToToc(api, data); if (data.api.servers && data.api.servers.length) { data.servers = data.api.servers; - } else if (options.loadedFrom) { + } + else if (options.loadedFrom) { data.servers = [{url: options.loadedFrom}]; - } else { + } + else { data.servers = [{url: '//'}]; } data.host = up.parse(data.servers[0].url).host; @@ -713,18 +699,14 @@ function convertInner(api, options) { data.utils.yaml = yaml; data.utils.inspect = util.inspect; data.utils.safejson = safejson; - data.utils.isPrimitive = function (t) { - return (t && (t !== 'object') && (t !== 'array')) - }; + data.utils.isPrimitive = function (t) { return (t && (t !== 'object') && (t !== 'array')) }; data.utils.toPrimitive = common.toPrimitive; - data.utils.slashes = function (s) { - return s.replace(/\/+/g, '/').replace(':/', '://'); - }; + data.utils.slashes = function (s) { return s.replace(/\/+/g, '/').replace(':/', '://'); }; data.utils.slugify = common.slugify; data.utils.getSample = common.getSample; data.utils.schemaToArray = common.schemaToArray; data.utils.fakeProdCons = fakeProdCons; - data.utils.getParameters = getParameters; + data.utils.getParameters = getParameters; data.utils.getCodeSamples = common.getCodeSamples; data.utils.getBodyParameterExamples = getBodyParameterExamples; data.utils.fakeBodyParameter = fakeBodyParameter; @@ -741,20 +723,15 @@ function convertInner(api, options) { let content = ''; if (!options.omitHeader) content += '---\n' + yaml.stringify(header) + '\n---\n\n'; data = options.templateCallback('main', 'pre', data); - if (data.append) { - content += data.append; - delete data.append; - } + if (data.append) { content += data.append; delete data.append; } try { content += templates.main(data); - } catch (ex) { + } + catch (ex) { throw ex; } data = options.templateCallback('main', 'post', data); - if (data.append) { - content += data.append; - delete data.append; - } + if (data.append) { content += data.append; delete data.append; } content = common.removeDupeBlankLines(content); if (options.html) content = common.html(content, header, options); @@ -765,14 +742,15 @@ function convertInner(api, options) { function convert(api, options) { if (options.resolve) { - return swagger2openapi.convertObj(api, {resolve: true, source: options.source, verbose: options.verbose}) - .then(sOptions => { - return convertInner(sOptions.openapi, options); - }) - .catch(err => { - console.error(err.message); - }); - } else { + return swagger2openapi.convertObj(api, { resolve: true, source: options.source, verbose: options.verbose }) + .then(sOptions => { + return convertInner(sOptions.openapi, options); + }) + .catch(err => { + console.error(err.message); + }); + } + else { return convertInner(api, options); } } @@ -781,3 +759,6 @@ module.exports = { convert: convert, fakeBodyParameter: fakeBodyParameter }; + + + diff --git a/widdershins.js b/widdershins.js index b967d4e6..5bbed4a4 100755 --- a/widdershins.js +++ b/widdershins.js @@ -47,6 +47,8 @@ var argv = require('yargs') .describe('lang','Generate the list of languages for code samples based on the languages used in the source file\'s `x-code-samples` examples.') .array('language_tabs') .describe('language_tabs', 'List of language tabs for code samples using "language[:label[:client]]" format, such as `javascript:JavaScript:request`.') + .string('loadMdPrefix') + .describe('loadMdPrefix', 'Prefix of OpenAPI extension that indicates MD files paths to load and can be injected anywhere in the output MD') .number('maxLevel') .alias('m','maxDepth') .describe('maxDepth','Maximum depth to show for schema examples.') @@ -166,6 +168,7 @@ options.customApiKeyValue = argv.customApiKeyValue; options.html = argv.html; options.respec = argv.respec; options.useBodyName = argv.useBodyName; +options.loadMdPrefix = argv.loadMdPrefix; if (argv.search === false) options.search = false; if (argv.includes) options.includes = argv.includes.split(','); if (argv.respec) { From 3b45427dc9edce9927ded44aab0f337c29878da3 Mon Sep 17 00:00:00 2001 From: Garber Date: Tue, 30 Jun 2020 00:56:27 +0300 Subject: [PATCH 4/4] Added default value for loadMdPrefix and changed loadMD to log exceptions --- lib/common.js | 2 +- widdershins.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/common.js b/lib/common.js index 07c6afe0..e3f77074 100644 --- a/lib/common.js +++ b/lib/common.js @@ -534,7 +534,7 @@ function loadMD(sourceSection, destSection, mdFieldPrefix) { destSection[newFieldName] = md.render(fs.readFileSync(sourceSection[field],'utf8')); } catch (ex) { - throw ex; + console.log(ex.message); } } } diff --git a/widdershins.js b/widdershins.js index 5bbed4a4..db6dad20 100755 --- a/widdershins.js +++ b/widdershins.js @@ -49,6 +49,7 @@ var argv = require('yargs') .describe('language_tabs', 'List of language tabs for code samples using "language[:label[:client]]" format, such as `javascript:JavaScript:request`.') .string('loadMdPrefix') .describe('loadMdPrefix', 'Prefix of OpenAPI extension that indicates MD files paths to load and can be injected anywhere in the output MD') + .default('loadMdPrefix', 'x-md') .number('maxLevel') .alias('m','maxDepth') .describe('maxDepth','Maximum depth to show for schema examples.')