390 lines
15 KiB
Plaintext
390 lines
15 KiB
Plaintext
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.OasVersion = void 0;
|
|
exports.bundleConfig = bundleConfig;
|
|
exports.bundle = bundle;
|
|
exports.bundleFromString = bundleFromString;
|
|
exports.bundleDocument = bundleDocument;
|
|
exports.mapTypeToComponent = mapTypeToComponent;
|
|
const resolve_1 = require("./resolve");
|
|
const visitors_1 = require("./visitors");
|
|
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 domains_1 = require("./redocly/domains");
|
|
const remove_unused_components_1 = require("./decorators/oas2/remove-unused-components");
|
|
const remove_unused_components_2 = require("./decorators/oas3/remove-unused-components");
|
|
const redocly_yaml_1 = require("./types/redocly-yaml");
|
|
var OasVersion;
|
|
(function (OasVersion) {
|
|
OasVersion["Version2"] = "oas2";
|
|
OasVersion["Version3_0"] = "oas3_0";
|
|
OasVersion["Version3_1"] = "oas3_1";
|
|
})(OasVersion || (exports.OasVersion = OasVersion = {}));
|
|
const bundleVisitor = (0, visitors_1.normalizeVisitors)([
|
|
{
|
|
severity: 'error',
|
|
ruleId: 'configBundler',
|
|
visitor: {
|
|
ref: {
|
|
leave(node, ctx, resolved) {
|
|
replaceRef(node, resolved, ctx);
|
|
},
|
|
},
|
|
},
|
|
},
|
|
], redocly_yaml_1.NormalizedConfigTypes);
|
|
async function bundleConfig(document, resolvedRefMap) {
|
|
const ctx = {
|
|
problems: [],
|
|
oasVersion: oas_types_1.SpecVersion.OAS3_0,
|
|
refTypes: new Map(),
|
|
visitorsData: {},
|
|
};
|
|
(0, walk_1.walkDocument)({
|
|
document,
|
|
rootType: redocly_yaml_1.NormalizedConfigTypes.ConfigRoot,
|
|
normalizedVisitors: bundleVisitor,
|
|
resolvedRefMap,
|
|
ctx,
|
|
});
|
|
return document.parsed ?? {};
|
|
}
|
|
async function bundle(opts) {
|
|
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 ? await externalRefResolver.resolveDocument(base, ref, true) : doc;
|
|
if (document instanceof Error) {
|
|
throw document;
|
|
}
|
|
opts.collectSpecData?.(document.parsed);
|
|
return bundleDocument({
|
|
document,
|
|
...opts,
|
|
config: opts.config.styleguide,
|
|
externalRefResolver,
|
|
});
|
|
}
|
|
async function bundleFromString(opts) {
|
|
const { source, absoluteRef, externalRefResolver = new resolve_1.BaseResolver(opts.config.resolve) } = opts;
|
|
const document = (0, resolve_1.makeDocumentFromString)(source, absoluteRef || '/');
|
|
return bundleDocument({
|
|
document,
|
|
...opts,
|
|
externalRefResolver,
|
|
config: opts.config.styleguide,
|
|
});
|
|
}
|
|
async function bundleDocument(opts) {
|
|
const { document, config, customTypes, externalRefResolver, dereference = false, skipRedoclyRegistryRefs = false, removeUnusedComponents = false, keepUrlRefs = false, } = opts;
|
|
const specVersion = (0, oas_types_1.detectSpec)(document.parsed);
|
|
const specMajorVersion = (0, oas_types_1.getMajorSpecVersion)(specVersion);
|
|
const rules = config.getRulesForSpecVersion(specMajorVersion);
|
|
const types = (0, types_1.normalizeTypes)(config.extendTypes(customTypes ?? (0, oas_types_1.getTypes)(specVersion), specVersion), config);
|
|
const preprocessors = (0, rules_1.initRules)(rules, config, 'preprocessors', specVersion);
|
|
const decorators = (0, rules_1.initRules)(rules, config, 'decorators', specVersion);
|
|
const ctx = {
|
|
problems: [],
|
|
oasVersion: specVersion,
|
|
refTypes: new Map(),
|
|
visitorsData: {},
|
|
};
|
|
if (removeUnusedComponents) {
|
|
decorators.push({
|
|
severity: 'error',
|
|
ruleId: 'remove-unused-components',
|
|
visitor: specMajorVersion === oas_types_1.SpecMajorVersion.OAS2
|
|
? (0, remove_unused_components_1.RemoveUnusedComponents)({})
|
|
: (0, remove_unused_components_2.RemoveUnusedComponents)({}),
|
|
});
|
|
}
|
|
let resolvedRefMap = await (0, resolve_1.resolveDocument)({
|
|
rootDocument: document,
|
|
rootType: types.Root,
|
|
externalRefResolver,
|
|
});
|
|
if (preprocessors.length > 0) {
|
|
// Make additional pass to resolve refs defined in preprocessors.
|
|
(0, walk_1.walkDocument)({
|
|
document,
|
|
rootType: types.Root,
|
|
normalizedVisitors: (0, visitors_1.normalizeVisitors)(preprocessors, types),
|
|
resolvedRefMap,
|
|
ctx,
|
|
});
|
|
resolvedRefMap = await (0, resolve_1.resolveDocument)({
|
|
rootDocument: document,
|
|
rootType: types.Root,
|
|
externalRefResolver,
|
|
});
|
|
}
|
|
const bundleVisitor = (0, visitors_1.normalizeVisitors)([
|
|
{
|
|
severity: 'error',
|
|
ruleId: 'bundler',
|
|
visitor: makeBundleVisitor(specMajorVersion, dereference, skipRedoclyRegistryRefs, document, resolvedRefMap, keepUrlRefs),
|
|
},
|
|
...decorators,
|
|
], types);
|
|
(0, walk_1.walkDocument)({
|
|
document,
|
|
rootType: types.Root,
|
|
normalizedVisitors: bundleVisitor,
|
|
resolvedRefMap,
|
|
ctx,
|
|
});
|
|
return {
|
|
bundle: document,
|
|
problems: ctx.problems.map((problem) => config.addProblemToIgnore(problem)),
|
|
fileDependencies: externalRefResolver.getFiles(),
|
|
rootType: types.Root,
|
|
refTypes: ctx.refTypes,
|
|
visitorsData: ctx.visitorsData,
|
|
};
|
|
}
|
|
function mapTypeToComponent(typeName, version) {
|
|
switch (version) {
|
|
case oas_types_1.SpecMajorVersion.OAS3:
|
|
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.SpecMajorVersion.OAS2:
|
|
switch (typeName) {
|
|
case 'Schema':
|
|
return 'definitions';
|
|
case 'Parameter':
|
|
return 'parameters';
|
|
case 'Response':
|
|
return 'responses';
|
|
default:
|
|
return null;
|
|
}
|
|
case oas_types_1.SpecMajorVersion.Async2:
|
|
switch (typeName) {
|
|
case 'Schema':
|
|
return 'schemas';
|
|
case 'Parameter':
|
|
return 'parameters';
|
|
default:
|
|
return null;
|
|
}
|
|
case oas_types_1.SpecMajorVersion.Async3:
|
|
switch (typeName) {
|
|
case 'Schema':
|
|
return 'schemas';
|
|
case 'Parameter':
|
|
return 'parameters';
|
|
default:
|
|
return null;
|
|
}
|
|
case oas_types_1.SpecMajorVersion.Arazzo1:
|
|
switch (typeName) {
|
|
case 'Root.workflows_items.parameters_items':
|
|
case 'Root.workflows_items.steps_items.parameters_items':
|
|
return 'parameters';
|
|
default:
|
|
return null;
|
|
}
|
|
case oas_types_1.SpecMajorVersion.Overlay1:
|
|
switch (typeName) {
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
function replaceRef(ref, resolved, ctx) {
|
|
if (!(0, utils_1.isPlainObject)(resolved.node)) {
|
|
ctx.parent[ctx.key] = resolved.node;
|
|
}
|
|
else {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore
|
|
delete ref.$ref;
|
|
const obj = Object.assign({}, resolved.node, ref);
|
|
Object.assign(ref, obj); // assign ref itself again so ref fields take precedence
|
|
}
|
|
}
|
|
// function oas3Move
|
|
function makeBundleVisitor(version, dereference, skipRedoclyRegistryRefs, rootDocument, resolvedRefMap, keepUrlRefs) {
|
|
let components;
|
|
let rootLocation;
|
|
const visitor = {
|
|
ref: {
|
|
leave(node, ctx, resolved) {
|
|
if (!resolved.location || resolved.node === undefined) {
|
|
(0, 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 && (0, domains_1.isRedoclyRegistryURL)(node.$ref)) {
|
|
return;
|
|
}
|
|
if (keepUrlRefs && (0, ref_utils_1.isAbsoluteUrl)(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);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
Example: {
|
|
leave(node, ctx) {
|
|
if ((0, ref_utils_1.isExternalValue)(node) && node.value === undefined) {
|
|
const resolved = ctx.resolve({ $ref: node.externalValue });
|
|
if (!resolved.location || resolved.node === undefined) {
|
|
(0, no_unresolved_refs_1.reportUnresolvedRef)(resolved, ctx.report, ctx.location);
|
|
return;
|
|
}
|
|
if (keepUrlRefs && (0, ref_utils_1.isAbsoluteUrl)(node.externalValue)) {
|
|
return;
|
|
}
|
|
node.value = ctx.resolve({ $ref: node.externalValue }).node;
|
|
delete node.externalValue;
|
|
}
|
|
},
|
|
},
|
|
Root: {
|
|
enter(root, ctx) {
|
|
rootLocation = ctx.location;
|
|
if (version === oas_types_1.SpecMajorVersion.OAS3) {
|
|
components = root.components = root.components || {};
|
|
}
|
|
else if (version === oas_types_1.SpecMajorVersion.OAS2) {
|
|
components = root;
|
|
}
|
|
else if (version === oas_types_1.SpecMajorVersion.Async2) {
|
|
components = root.components = root.components || {};
|
|
}
|
|
else if (version === oas_types_1.SpecMajorVersion.Async3) {
|
|
components = root.components = root.components || {};
|
|
}
|
|
else if (version === oas_types_1.SpecMajorVersion.Arazzo1) {
|
|
components = root.components = root.components || {};
|
|
}
|
|
},
|
|
},
|
|
};
|
|
if (version === oas_types_1.SpecMajorVersion.OAS3) {
|
|
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) {
|
|
(0, no_unresolved_refs_1.reportUnresolvedRef)(resolved, ctx.report, ctx.location.child(name));
|
|
return;
|
|
}
|
|
const componentType = mapTypeToComponent('Schema', version);
|
|
mapping[name] = saveComponent(componentType, resolved, ctx);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
function resolveBundledComponent(node, resolved, ctx) {
|
|
const newRefId = (0, 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 saveComponent(componentType, target, ctx) {
|
|
components[componentType] = components[componentType] || {};
|
|
const name = getComponentName(target, componentType, ctx);
|
|
components[componentType][name] = target.node;
|
|
if (version === oas_types_1.SpecMajorVersion.OAS3 ||
|
|
version === oas_types_1.SpecMajorVersion.Async2 ||
|
|
version === oas_types_1.SpecMajorVersion.Async3) {
|
|
return `#/components/${componentType}/${name}`;
|
|
}
|
|
else {
|
|
return `#/${componentType}/${name}`;
|
|
}
|
|
}
|
|
function isEqualOrEqualRef(node, target, ctx) {
|
|
if ((0, ref_utils_1.isRef)(node) &&
|
|
ctx.resolve(node, rootLocation.absolutePointer).location?.absolutePointer ===
|
|
target.location.absolutePointer) {
|
|
return true;
|
|
}
|
|
return (0, utils_1.dequal)(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(utils_1.isTruthy); // slice(2) removes "#/"
|
|
while (refParts.length > 0) {
|
|
name = refParts.pop() + (name ? `-${name}` : '');
|
|
if (!componentsGroup ||
|
|
!componentsGroup[name] ||
|
|
isEqualOrEqualRef(componentsGroup[name], target, ctx)) {
|
|
return name;
|
|
}
|
|
}
|
|
name = (0, 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;
|
|
}
|