From 7b69860e6d3071f623c8366a17f1fe77717d7a92 Mon Sep 17 00:00:00 2001 From: m-mitsuhide Date: Sun, 7 Jun 2020 14:44:41 +0900 Subject: [PATCH] fix: support local file refs --- package.json | 1 + src/resolveExternalRefs.ts | 51 ++++++++++++++++++++++---------------- yarn.lock | 5 ++++ 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 9afa5606..ba90161c 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@commitlint/cli": "^8.3.5", "@commitlint/config-conventional": "^8.3.4", "@types/jest": "^25.2.3", + "@types/js-yaml": "^3.12.4", "@types/minimist": "^1.2.0", "@typescript-eslint/eslint-plugin": "2.34.0", "@typescript-eslint/parser": "^2.34.0", diff --git a/src/resolveExternalRefs.ts b/src/resolveExternalRefs.ts index 7c3b8a33..b49f0c56 100644 --- a/src/resolveExternalRefs.ts +++ b/src/resolveExternalRefs.ts @@ -2,6 +2,7 @@ import https from 'https' import http from 'http' import fs from 'fs' import path from 'path' +import yaml from 'js-yaml' import { OpenAPIV3 } from 'openapi-types' const getText = (url: string) => @@ -24,31 +25,32 @@ const getText = (url: string) => }) }) -type DocInfo = { url: string; doc: OpenAPIV3.Document } +type DocType = Record +type DocInfo = { url: string; doc: DocType } const hasExternalRegExp = /"\$ref":"[^#].+?"/g -const fetchExternalDocs = async (docs: OpenAPIV3.Document, inputDir: string) => { +const fetchExternalDocs = async (docs: DocType, inputDir: string) => { const docList: DocInfo[] = [] const fetchingUrls: string[] = [] - const fetchDocs = (d: OpenAPIV3.Document, input: string) => + const fetchDocs = (d: DocType, input: string) => Promise.all( (JSON.stringify(d).match(hasExternalRegExp) ?? []).map(async ref => { - const [, url] = ref.match(/"\$ref":"(.+?)#/)! + const [, url] = ref.match(/"\$ref":"(.+?)[#"]/)! if (fetchingUrls.includes(url)) return - fetchingUrls.push(url) - const doc: OpenAPIV3.Document = JSON.parse( - await (url.startsWith('http') - ? getText(url) - : input.startsWith('http') - ? getText(path.join(input, url)) - : fs.promises.readFile(path.join(input, url), 'utf8')) - ) + + const filePath = url.startsWith('http') + ? url + : path.posix.join(input.split('/').slice(0, -1).join('/'), url) + const text = await (filePath.startsWith('http') + ? getText(filePath) + : fs.promises.readFile(filePath, 'utf8')) + const doc: DocType = filePath.endsWith('.json') ? JSON.parse(text) : yaml.safeLoad(text) docList[fetchingUrls.indexOf(url)] = { url, doc } - await fetchDocs(doc, url.startsWith('http') ? url : path.join(input, url)) + await fetchDocs(doc, filePath) }) ) @@ -58,11 +60,15 @@ const fetchExternalDocs = async (docs: OpenAPIV3.Document, inputDir: string) => const getComponentInfo = (docList: DocInfo[], url: string, prop: string) => { const data = docList.find(d => d.url === url)!.doc - const target = prop.split('/').reduce((prev, current) => prev[current], data as any) - if (target.name) return { type: 'parameters' as const, data: target } - return { type: 'schemas' as const, data: target } + const target = prop ? prop.split('/').reduce((prev, current) => prev[current], data) : data + + if (target.name) return { type: 'parameters', data: target } + return { type: 'schemas', data: target } } +const genExternalTypeName = (docList: DocInfo[], url: string, prop: string) => + `External${docList.findIndex(d => d.url === url)}${prop ? `_${prop.split('/').pop()}` : ''}` + const resolveExternalDocs = async (docs: OpenAPIV3.Document, inputDir: string) => { const externalDocs = await fetchExternalDocs(docs, inputDir) const componentsInfoList: { url: string; prop: string; name: string }[] = [] @@ -70,9 +76,11 @@ const resolveExternalDocs = async (docs: OpenAPIV3.Document, inputDir: string) = let docsString = JSON.stringify(selfDoc.doc) ;(docsString.match(/"\$ref":".+?"/g) ?? []).forEach(refs => { const targetText = refs.replace('"$ref":"', '').slice(0, -1) - const [, url = selfDoc.url, prop] = targetText.match(/(.+?)?#\/(.+)/)! + const [urlBase, propBase = '/'] = targetText.split('#') + const url = urlBase || selfDoc.url + const prop = propBase.slice(1) const info = getComponentInfo(externalDocs, url, prop) - const name = `External${externalDocs.findIndex(d => d.url === url)}_${prop.split('/').pop()}` + const name = genExternalTypeName(externalDocs, url, prop) docsString = docsString.replace(targetText, `#/components/${info.type}/${name}`) componentsInfoList.push({ url, prop, name }) }) @@ -85,7 +93,7 @@ const resolveExternalDocs = async (docs: OpenAPIV3.Document, inputDir: string) = components: componentsInfoList.reduce((prev, { url, prop, name }) => { const info = getComponentInfo(replacedExternalDocs, url, prop) return { ...prev, [info.type]: { ...prev[info.type], [name]: info.data } } - }, {} as Record) + }, {} as DocType) } } @@ -95,11 +103,12 @@ export default async (docs: OpenAPIV3.Document, inputDir: string): Promise { const targetText = refs.replace('"$ref":"', '').slice(0, -1) - const [, url, prop] = targetText.match(/(.+?)#\/(.+)/)! + const [url, propBase = '/'] = targetText.split('#') + const prop = propBase.slice(1) const info = getComponentInfo(externalDocs, url, prop) components[info.type] = components[info.type] || {} - const name = `External${externalDocs.findIndex(d => d.url === url)}_${prop.split('/').pop()}` + const name = genExternalTypeName(externalDocs, url, prop) Object.assign(components[info.type], { [name]: info.data }) docsString = docsString.replace(targetText, `#/components/${info.type}/${name}`) }) diff --git a/yarn.lock b/yarn.lock index f41bb03c..b824eaf2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -724,6 +724,11 @@ jest-diff "^25.2.1" pretty-format "^25.2.1" +"@types/js-yaml@^3.12.4": + version "3.12.4" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.4.tgz#7d3b534ec35a0585128e2d332db1403ebe057e25" + integrity sha512-fYMgzN+9e28R81weVN49inn/u798ruU91En1ZnGvSZzCRc5jXx9B2EDhlRaWmcO1RIxFHL8AajRXzxDuJu93+A== + "@types/json-schema@^7.0.3": version "7.0.4" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"