UNPKG

@opendevise/antora-swagger-ui-extension

Version:

An Antora extension that integrates the Swagger UI (interactive API explorer) into an Antora site through AsciiDoc syntax extensions.

100 lines (92 loc) 4.27 kB
'use strict' const fs = require('node:fs') const { inspect } = require('node:util') const { posix: path } = require('node:path') const toProc = require('./util/to-proc') const yaml = require('js-yaml') const INSPECT_OPTIONS = { depth: Infinity, maxArrayLength: Infinity, maxStringLength: Infinity, compact: false } const SPEC_FAMILIES = ['attachment', 'example', 'partial'] function register (registry, context) { if (!(registry && context)) return // NOTE only works as a scoped extension registry.$groups().$store('swagger-ui/block-macro', toProc(createExtensionGroup(context))) return registry } function createExtensionGroup ({ contentCatalog, file }) { return function () { this.blockMacro(function () { this.named('swagger-ui') this.process((parent, target, attrs) => { let specFile, topicPath if (/^https?:[/]{2}/.test(target)) { const specUrl = target const { hostname, pathname } = new URL(specUrl) const extname = path.extname(pathname) const pathnameWithoutExtname = extname ? pathname.slice(0, -extname.length) : pathname const relativeWithoutExtname = hostname.replace(/\./g, '-') + pathnameWithoutExtname.replace(/[/.]/g, '-') specFile = { src: { component: file.src.component, version: file.src.version, module: file.src.module, family: 'attachment', relative: relativeWithoutExtname + extname, stem: relativeWithoutExtname, url: specUrl, }, } topicPath = '' } else if (!(specFile = contentCatalog.resolveResource(target, file.src, undefined, SPEC_FAMILIES))) { const message = `OpenAPI specification to visualize could not be resolved: ${target}` return log(parent.getDocument(), 'warn', message) } else if ((topicPath = path.dirname(specFile.src.relative)) && ~topicPath.indexOf(path.sep + '_')) { topicPath = topicPath .split(path.sep) .filter((it) => !it.startsWith('_')) .join(path.sep) } const autoId = `swagger-ui-${parent.getDocument().counter('swagger-ui-number')}` const id = attrs.id || autoId const docExpansion = { full: 'full', list: 'list', none: 'none' }[attrs.docExpansion] || 'list' const specLoaderSrc = { component: specFile.src.component, version: specFile.src.version, module: specFile.src.module, family: 'attachment', relative: path.join(topicPath, specFile.src.stem.replace(/^_+/, '') + '-spec-loader.js'), } let specLoaderFile = contentCatalog.getById(specLoaderSrc) if (!specLoaderFile) { const { source: specLoaderFn } = require('./get-spec-loader') let specSource if (!(specSource = specFile.src.url)) { try { specSource = parseContents(specFile) } catch (ex) { specSource = {} const message = `OpenAPI specification to visualize could not be parsed: ${target}\n${ex.message}` log(parent.getDocument(), 'error', message) } } specLoaderFile = contentCatalog.addFile({ contents: Buffer.from(`${specLoaderFn}(${inspect(specSource, INSPECT_OPTIONS)})\n`), path: path.join('modules', specLoaderSrc.module, specLoaderSrc.family, specLoaderSrc.relative), src: specLoaderSrc, }) } const specLoaderInfo = { url: specLoaderFile.pub.url, config: { target: id, 'doc-expansion': docExpansion } } ;(file.asciidoc.attributes['page-openapi-spec-loaders'] ||= []).push(specLoaderInfo) return this.createBlock(parent, 'pass', `<div id="${id}"></div>`) }) }) } } function parseContents (file) { return file.mediaType === 'text/yaml' ? yaml.load(file.contents, { schema: yaml.JSON_SCHEMA, json: true }) : JSON.parse(file.contents) } function log (doc, level, message) { const cursor = doc.getReader().$cursor_at_mark() doc.getLogger()[level](doc.createLogMessage(message, { source_location: cursor })) } module.exports = { register, createExtensionGroup }