@redocly/openapi-core
Version:
See https://github.com/Redocly/redocly-cli
255 lines • 9.86 kB
JavaScript
import { isAbsoluteUrl, replaceRef, isExternalValue, isRef, refBaseName } from '../ref-utils.js';
import { makeRefId } from '../utils/make-ref-id.js';
import { reportUnresolvedRef } from '../rules/common/no-unresolved-refs.js';
import { isTruthy } from '../utils/is-truthy.js';
import { dequal } from '../utils/dequal.js';
export function mapTypeToComponent(typeName, version) {
switch (version) {
case '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 'oas2':
switch (typeName) {
case 'Schema':
return 'definitions';
case 'Parameter':
return 'parameters';
case 'Response':
return 'responses';
default:
return null;
}
case 'async2':
switch (typeName) {
case 'Schema':
return 'schemas';
case 'Parameter':
return 'parameters';
default:
return null;
}
case 'async3':
switch (typeName) {
case 'Schema':
return 'schemas';
case 'Parameter':
return 'parameters';
default:
return null;
}
case 'arazzo1':
switch (typeName) {
case 'Root.workflows_items.parameters_items':
case 'Root.workflows_items.steps_items.parameters_items':
return 'parameters';
default:
return null;
}
case 'overlay1':
switch (typeName) {
default:
return null;
}
case 'openrpc1':
switch (typeName) {
case 'ContentDescriptor':
return 'contentDescriptors';
case 'Schema':
return 'schemas';
case 'Example':
return 'examples';
case 'Link':
return 'links';
case 'ErrorObject':
return 'errors';
case 'ExamplePairing':
return 'examplePairingObjects';
case 'Tag':
return 'tags';
default:
return null;
}
}
}
export function makeBundleVisitor(version, dereference, rootDocument, resolvedRefMap, keepUrlRefs) {
let components;
let rootLocation;
const visitor = {
ref: {
leave(node, ctx, resolved) {
if (!resolved.location || resolved.node === undefined) {
reportUnresolvedRef(resolved, ctx.report, ctx.location);
return;
}
if (resolved.location.source === rootDocument.source &&
resolved.location.source === ctx.location.source &&
ctx.type.name !== 'scalar' &&
!dereference) {
// Normalize explicit self-file refs to internal pointer
if (node.$ref !== resolved.location.pointer) {
node.$ref = resolved.location.pointer;
}
return;
}
if (keepUrlRefs && 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 (isExternalValue(node) && node.value === undefined) {
const resolved = ctx.resolve({ $ref: node.externalValue });
if (!resolved.location || resolved.node === undefined) {
reportUnresolvedRef(resolved, ctx.report, ctx.location);
return;
}
if (keepUrlRefs && isAbsoluteUrl(node.externalValue)) {
return;
}
node.value = ctx.resolve({ $ref: node.externalValue }).node;
delete node.externalValue;
}
},
},
Root: {
enter(root, ctx) {
rootLocation = ctx.location;
if (version === 'oas3') {
components = root.components = root.components || {};
}
else if (version === 'oas2') {
components = root;
}
else if (version === 'async2') {
components = root.components = root.components || {};
}
else if (version === 'async3') {
components = root.components = root.components || {};
}
else if (version === 'arazzo1') {
components = root.components = root.components || {};
}
else if (version === 'openrpc1') {
components = root.components = root.components || {};
}
},
},
};
if (version === '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) {
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 = 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 === 'oas3' ||
version === 'async2' ||
version === 'async3' ||
version === 'openrpc1') {
return `#/components/${componentType}/${name}`;
}
else {
return `#/${componentType}/${name}`;
}
}
function isEqualOrEqualRef(node, target, ctx) {
if (isRef(node) &&
ctx.resolve(node, rootLocation.absolutePointer).location?.absolutePointer ===
target.location.absolutePointer) {
return true;
}
return 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(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 = 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;
}
//# sourceMappingURL=bundle-visitor.js.map