@redocly/openapi-core
Version:
See https://github.com/Redocly/openapi-cli
289 lines (288 loc) • 12.4 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.mapTypeToComponent = exports.bundleDocument = exports.bundle = exports.OasVersion = void 0;
const isEqual = require("lodash.isequal");
const resolve_1 = require("./resolve");
const visitors_1 = require("./visitors");
const oas3_1 = require("./types/oas3");
const oas2_1 = require("./types/oas2");
const oas3_1_1 = require("./types/oas3_1");
const types_1 = require("./types");
const walk_1 = require("./walk");
const oas_types_1 = require("./oas-types");
const ref_utils_1 = require("./ref-utils");
const rules_1 = require("./config/rules");
const no_unresolved_refs_1 = require("./rules/no-unresolved-refs");
const utils_1 = require("./utils");
const redocly_1 = require("./redocly");
const remove_unused_components_1 = require("./rules/oas2/remove-unused-components");
const remove_unused_components_2 = require("./rules/oas3/remove-unused-components");
var OasVersion;
(function (OasVersion) {
OasVersion["Version2"] = "oas2";
OasVersion["Version3_0"] = "oas3_0";
OasVersion["Version3_1"] = "oas3_1";
})(OasVersion = exports.OasVersion || (exports.OasVersion = {}));
function bundle(opts) {
return __awaiter(this, void 0, void 0, function* () {
const { ref, doc, externalRefResolver = new resolve_1.BaseResolver(opts.config.resolve), base = null, } = opts;
if (!(ref || doc)) {
throw new Error('Document or reference is required.\n');
}
const document = doc !== undefined ? doc : yield externalRefResolver.resolveDocument(base, ref, true);
if (document instanceof Error) {
throw document;
}
return bundleDocument(Object.assign(Object.assign({ document }, opts), { config: opts.config.lint, externalRefResolver }));
});
}
exports.bundle = bundle;
function bundleDocument(opts) {
return __awaiter(this, void 0, void 0, function* () {
const { document, config, customTypes, externalRefResolver, dereference = false, skipRedoclyRegistryRefs = false, removeUnusedComponents = false, } = opts;
const oasVersion = oas_types_1.detectOpenAPI(document.parsed);
const oasMajorVersion = oas_types_1.openAPIMajor(oasVersion);
const rules = config.getRulesForOasVersion(oasMajorVersion);
const types = types_1.normalizeTypes(config.extendTypes((customTypes !== null && customTypes !== void 0 ? customTypes : oasMajorVersion === oas_types_1.OasMajorVersion.Version3)
? oasVersion === OasVersion.Version3_1
? oas3_1_1.Oas3_1Types
: oas3_1.Oas3Types
: oas2_1.Oas2Types, oasVersion), config);
const preprocessors = rules_1.initRules(rules, config, 'preprocessors', oasVersion);
const decorators = rules_1.initRules(rules, config, 'decorators', oasVersion);
const ctx = {
problems: [],
oasVersion: oasVersion,
refTypes: new Map(),
visitorsData: {},
};
if (removeUnusedComponents) {
decorators.push({
severity: 'error',
ruleId: 'remove-unused-components',
visitor: oasMajorVersion === oas_types_1.OasMajorVersion.Version2
? remove_unused_components_1.RemoveUnusedComponents({})
: remove_unused_components_2.RemoveUnusedComponents({})
});
}
const resolvedRefMap = yield resolve_1.resolveDocument({
rootDocument: document,
rootType: types.DefinitionRoot,
externalRefResolver,
});
const bundleVisitor = visitors_1.normalizeVisitors([
...preprocessors,
{
severity: 'error',
ruleId: 'bundler',
visitor: makeBundleVisitor(oasMajorVersion, dereference, skipRedoclyRegistryRefs, document, resolvedRefMap),
},
...decorators,
], types);
walk_1.walkDocument({
document,
rootType: types.DefinitionRoot,
normalizedVisitors: bundleVisitor,
resolvedRefMap,
ctx,
});
return {
bundle: document,
problems: ctx.problems.map((problem) => config.addProblemToIgnore(problem)),
fileDependencies: externalRefResolver.getFiles(),
rootType: types.DefinitionRoot,
refTypes: ctx.refTypes,
visitorsData: ctx.visitorsData,
};
});
}
exports.bundleDocument = bundleDocument;
function mapTypeToComponent(typeName, version) {
switch (version) {
case oas_types_1.OasMajorVersion.Version3:
switch (typeName) {
case 'Schema':
return 'schemas';
case 'Parameter':
return 'parameters';
case 'Response':
return 'responses';
case 'Example':
return 'examples';
case 'RequestBody':
return 'requestBodies';
case 'Header':
return 'headers';
case 'SecuritySchema':
return 'securitySchemes';
case 'Link':
return 'links';
case 'Callback':
return 'callbacks';
default:
return null;
}
case oas_types_1.OasMajorVersion.Version2:
switch (typeName) {
case 'Schema':
return 'definitions';
case 'Parameter':
return 'parameters';
case 'Response':
return 'responses';
default:
return null;
}
}
}
exports.mapTypeToComponent = mapTypeToComponent;
// function oas3Move
function makeBundleVisitor(version, dereference, skipRedoclyRegistryRefs, rootDocument, resolvedRefMap) {
let components;
const visitor = {
ref: {
leave(node, ctx, resolved) {
if (!resolved.location || resolved.node === undefined) {
no_unresolved_refs_1.reportUnresolvedRef(resolved, ctx.report, ctx.location);
return;
}
if (resolved.location.source === rootDocument.source &&
resolved.location.source === ctx.location.source &&
ctx.type.name !== 'scalar' &&
!dereference) {
return;
}
// do not bundle registry URL before push, otherwise we can't record dependencies
if (skipRedoclyRegistryRefs && redocly_1.isRedoclyRegistryURL(node.$ref)) {
return;
}
const componentType = mapTypeToComponent(ctx.type.name, version);
if (!componentType) {
replaceRef(node, resolved, ctx);
}
else {
if (dereference) {
saveComponent(componentType, resolved, ctx);
replaceRef(node, resolved, ctx);
}
else {
node.$ref = saveComponent(componentType, resolved, ctx);
resolveBundledComponent(node, resolved, ctx);
}
}
},
},
DefinitionRoot: {
enter(root) {
if (version === oas_types_1.OasMajorVersion.Version3) {
components = root.components = root.components || {};
}
else if (version === oas_types_1.OasMajorVersion.Version2) {
components = root;
}
},
},
};
if (version === oas_types_1.OasMajorVersion.Version3) {
visitor.DiscriminatorMapping = {
leave(mapping, ctx) {
for (const name of Object.keys(mapping)) {
const $ref = mapping[name];
const resolved = ctx.resolve({ $ref });
if (!resolved.location || resolved.node === undefined) {
no_unresolved_refs_1.reportUnresolvedRef(resolved, ctx.report, ctx.location.child(name));
return;
}
const componentType = mapTypeToComponent('Schema', version);
if (dereference) {
saveComponent(componentType, resolved, ctx);
}
else {
mapping[name] = saveComponent(componentType, resolved, ctx);
}
}
},
};
}
function resolveBundledComponent(node, resolved, ctx) {
const newRefId = resolve_1.makeRefId(ctx.location.source.absoluteRef, node.$ref);
resolvedRefMap.set(newRefId, {
document: rootDocument,
isRemote: false,
node: resolved.node,
nodePointer: node.$ref,
resolved: true,
});
}
function replaceRef(ref, resolved, ctx) {
if (!utils_1.isPlainObject(resolved.node)) {
ctx.parent[ctx.key] = resolved.node;
}
else {
// @ts-ignore
delete ref.$ref;
Object.assign(ref, resolved.node);
}
}
function saveComponent(componentType, target, ctx) {
components[componentType] = components[componentType] || {};
const name = getComponentName(target, componentType, ctx);
components[componentType][name] = target.node;
if (version === oas_types_1.OasMajorVersion.Version3) {
return `#/components/${componentType}/${name}`;
}
else {
return `#/${componentType}/${name}`;
}
}
function isEqualOrEqualRef(node, target, ctx) {
var _a;
if (ref_utils_1.isRef(node) &&
((_a = ctx.resolve(node).location) === null || _a === void 0 ? void 0 : _a.absolutePointer) === target.location.absolutePointer) {
return true;
}
return isEqual(node, target.node);
}
function getComponentName(target, componentType, ctx) {
const [fileRef, pointer] = [target.location.source.absoluteRef, target.location.pointer];
const componentsGroup = components[componentType];
let name = '';
const refParts = pointer.slice(2).split('/').filter(Boolean); // slice(2) removes "#/"
while (refParts.length > 0) {
name = refParts.pop() + (name ? `-${name}` : '');
if (!componentsGroup ||
!componentsGroup[name] ||
isEqualOrEqualRef(componentsGroup[name], target, ctx)) {
return name;
}
}
name = ref_utils_1.refBaseName(fileRef) + (name ? `_${name}` : '');
if (!componentsGroup[name] || isEqualOrEqualRef(componentsGroup[name], target, ctx)) {
return name;
}
const prevName = name;
let serialId = 2;
while (componentsGroup[name] && !isEqualOrEqualRef(componentsGroup[name], target, ctx)) {
name = `${prevName}-${serialId}`;
serialId++;
}
if (!componentsGroup[name]) {
ctx.report({
message: `Two schemas are referenced with the same name but different content. Renamed ${prevName} to ${name}.`,
location: ctx.location,
forceSeverity: 'warn',
});
}
return name;
}
return visitor;
}