@redocly/openapi-core
Version:
See https://github.com/Redocly/redocly-cli
344 lines • 15.6 kB
JavaScript
import * as fs from 'node:fs';
import * as path from 'node:path';
import { parseYaml, stringifyYaml } from '../js-yaml/index.js';
import { slash } from '../utils/slash.js';
import { doesYamlFileExist } from '../utils/does-yaml-file-exist.js';
import { isPlainObject } from '../utils/is-plain-object.js';
import { specVersions } from '../detect-spec.js';
import { isBrowser } from '../env.js';
import { getResolveConfig } from './get-resolve-config.js';
import { isAbsoluteUrl } from '../ref-utils.js';
import { groupAssertionRules } from './group-assertion-rules.js';
import { IGNORE_BANNER, IGNORE_FILE } from './constants.js';
function getIgnoreFilePath(configPath) {
if (configPath) {
return doesYamlFileExist(configPath)
? path.join(path.dirname(configPath), IGNORE_FILE)
: path.join(configPath, IGNORE_FILE);
}
else {
return isBrowser ? undefined : path.join(process.cwd(), IGNORE_FILE);
}
}
export class Config {
constructor(resolvedConfig, opts = {}) {
this.ignore = {};
this._usedRules = new Set();
this._usedVersions = new Set();
this.resolvedConfig = resolvedConfig;
this.configPath = opts.configPath;
this.document = opts.document;
this.resolvedRefMap = opts.resolvedRefMap;
this.resolve = getResolveConfig(this.resolvedConfig.resolve);
this._alias = opts.alias;
this.plugins = opts.plugins || [];
this.doNotResolveExamples = !!resolvedConfig.resolve?.doNotResolveExamples;
const group = (rules) => {
return groupAssertionRules({ rules }, this.plugins);
};
this.rules = {
oas2: group({ ...resolvedConfig.rules, ...resolvedConfig.oas2Rules }),
oas3_0: group({ ...resolvedConfig.rules, ...resolvedConfig.oas3_0Rules }),
oas3_1: group({ ...resolvedConfig.rules, ...resolvedConfig.oas3_1Rules }),
oas3_2: group({ ...resolvedConfig.rules, ...resolvedConfig.oas3_2Rules }),
async2: group({ ...resolvedConfig.rules, ...resolvedConfig.async2Rules }),
async3: group({ ...resolvedConfig.rules, ...resolvedConfig.async3Rules }),
arazzo1: group({ ...resolvedConfig.rules, ...resolvedConfig.arazzo1Rules }),
overlay1: group({ ...resolvedConfig.rules, ...resolvedConfig.overlay1Rules }),
};
this.preprocessors = {
oas2: { ...resolvedConfig.preprocessors, ...resolvedConfig.oas2Preprocessors },
oas3_0: {
...resolvedConfig.preprocessors,
...resolvedConfig.oas3_0Preprocessors,
},
oas3_1: {
...resolvedConfig.preprocessors,
...resolvedConfig.oas3_1Preprocessors,
},
oas3_2: {
...resolvedConfig.preprocessors,
...resolvedConfig.oas3_2Preprocessors,
},
async2: {
...resolvedConfig.preprocessors,
...resolvedConfig.async2Preprocessors,
},
async3: {
...resolvedConfig.preprocessors,
...resolvedConfig.async3Preprocessors,
},
arazzo1: {
...resolvedConfig.preprocessors,
...resolvedConfig.arazzo1Preprocessors,
},
overlay1: {
...resolvedConfig.preprocessors,
...resolvedConfig.overlay1Preprocessors,
},
};
this.decorators = {
oas2: { ...resolvedConfig.decorators, ...resolvedConfig.oas2Decorators },
oas3_0: { ...resolvedConfig.decorators, ...resolvedConfig.oas3_0Decorators },
oas3_1: { ...resolvedConfig.decorators, ...resolvedConfig.oas3_1Decorators },
oas3_2: { ...resolvedConfig.decorators, ...resolvedConfig.oas3_2Decorators },
async2: { ...resolvedConfig.decorators, ...resolvedConfig.async2Decorators },
async3: { ...resolvedConfig.decorators, ...resolvedConfig.async3Decorators },
arazzo1: { ...resolvedConfig.decorators, ...resolvedConfig.arazzo1Decorators },
overlay1: {
...resolvedConfig.decorators,
...resolvedConfig.overlay1Decorators,
},
};
this.resolveIgnore(getIgnoreFilePath(opts.configPath));
}
forAlias(alias) {
if (alias === undefined || !isPlainObject(this.resolvedConfig?.apis?.[alias])) {
return this;
}
const { apis, ...rest } = this.resolvedConfig;
const { root: _root, output: _output, ...aliasConfig } = apis[alias];
return new Config({ ...rest, ...aliasConfig }, {
configPath: this.configPath,
document: this.document,
resolvedRefMap: this.resolvedRefMap,
alias,
plugins: this.plugins,
});
}
resolveIgnore(ignoreFile) {
if (!ignoreFile || !doesYamlFileExist(ignoreFile))
return;
this.ignore =
parseYaml(fs.readFileSync(ignoreFile, 'utf-8')) || {};
// resolve ignore paths
for (const fileName of Object.keys(this.ignore)) {
this.ignore[isAbsoluteUrl(fileName) ? fileName : path.resolve(path.dirname(ignoreFile), fileName)] = this.ignore[fileName];
for (const ruleId of Object.keys(this.ignore[fileName])) {
this.ignore[fileName][ruleId] = new Set(this.ignore[fileName][ruleId]);
}
if (!isAbsoluteUrl(fileName)) {
delete this.ignore[fileName];
}
}
}
saveIgnore() {
const dir = this.configPath ? path.dirname(this.configPath) : process.cwd();
const ignoreFile = path.join(dir, IGNORE_FILE);
const mapped = {};
for (const absFileName of Object.keys(this.ignore)) {
const mappedDefinitionName = isAbsoluteUrl(absFileName)
? absFileName
: slash(path.relative(dir, absFileName));
const ignoredRules = (mapped[mappedDefinitionName] = this.ignore[absFileName]);
for (const ruleId of Object.keys(ignoredRules)) {
ignoredRules[ruleId] = Array.from(ignoredRules[ruleId]);
}
}
fs.writeFileSync(ignoreFile, IGNORE_BANNER + stringifyYaml(mapped));
}
addIgnore(problem) {
const ignore = this.ignore;
const loc = problem.location[0];
if (loc.pointer === undefined)
return;
const fileIgnore = (ignore[loc.source.absoluteRef] = ignore[loc.source.absoluteRef] || {});
const ruleIgnore = (fileIgnore[problem.ruleId] = fileIgnore[problem.ruleId] || new Set());
ruleIgnore.add(loc.pointer);
}
addProblemToIgnore(problem) {
const loc = problem.location[0];
if (loc.pointer === undefined)
return problem;
const fileIgnore = this.ignore[loc.source.absoluteRef] || {};
const ruleIgnore = fileIgnore[problem.ruleId];
const ignored = ruleIgnore && ruleIgnore.has(loc.pointer);
return ignored
? {
...problem,
ignored,
}
: problem;
}
extendTypes(types, version) {
let extendedTypes = types;
for (const plugin of this.plugins) {
if (plugin.typeExtension !== undefined) {
switch (version) {
case 'oas3_0':
case 'oas3_1':
case 'oas3_2':
if (!plugin.typeExtension.oas3)
continue;
extendedTypes = plugin.typeExtension.oas3(extendedTypes, version);
break;
case 'oas2':
if (!plugin.typeExtension.oas2)
continue;
extendedTypes = plugin.typeExtension.oas2(extendedTypes, version);
break;
case 'async2':
if (!plugin.typeExtension.async2)
continue;
extendedTypes = plugin.typeExtension.async2(extendedTypes, version);
break;
case 'async3':
if (!plugin.typeExtension.async3)
continue;
extendedTypes = plugin.typeExtension.async3(extendedTypes, version);
break;
case 'arazzo1':
if (!plugin.typeExtension.arazzo1)
continue;
extendedTypes = plugin.typeExtension.arazzo1(extendedTypes, version);
break;
case 'overlay1':
if (!plugin.typeExtension.overlay1)
continue;
extendedTypes = plugin.typeExtension.overlay1(extendedTypes, version);
break;
default:
throw new Error('Not implemented');
}
}
}
return extendedTypes;
}
getRuleSettings(ruleId, oasVersion) {
this._usedRules.add(ruleId);
this._usedVersions.add(oasVersion);
const settings = this.rules[oasVersion][ruleId] || 'off';
if (typeof settings === 'string') {
return {
severity: settings,
};
}
else {
return { severity: 'error', ...settings };
}
}
getPreprocessorSettings(ruleId, oasVersion) {
this._usedRules.add(ruleId);
this._usedVersions.add(oasVersion);
const settings = this.preprocessors[oasVersion][ruleId] || 'off';
if (typeof settings === 'string') {
return {
severity: settings === 'on' ? 'error' : settings,
};
}
else {
return { severity: 'error', ...settings };
}
}
getDecoratorSettings(ruleId, oasVersion) {
this._usedRules.add(ruleId);
this._usedVersions.add(oasVersion);
const settings = this.decorators[oasVersion][ruleId] || 'off';
if (typeof settings === 'string') {
return {
severity: settings === 'on' ? 'error' : settings,
};
}
else {
return { severity: 'error', ...settings };
}
}
getUnusedRules() {
const rules = [];
const decorators = [];
const preprocessors = [];
for (const usedVersion of Array.from(this._usedVersions)) {
rules.push(...Object.keys(this.rules[usedVersion]).filter((name) => !this._usedRules.has(name)));
decorators.push(...Object.keys(this.decorators[usedVersion]).filter((name) => !this._usedRules.has(name)));
preprocessors.push(...Object.keys(this.preprocessors[usedVersion]).filter((name) => !this._usedRules.has(name)));
}
return {
rules,
preprocessors,
decorators,
};
}
// TODO: add default case for redocly.yaml
getRulesForSpecVersion(version) {
switch (version) {
case 'oas3':
// eslint-disable-next-line no-case-declarations
const oas3Rules = [];
this.plugins.forEach((p) => p.preprocessors?.oas3 && oas3Rules.push(p.preprocessors.oas3));
this.plugins.forEach((p) => p.rules?.oas3 && oas3Rules.push(p.rules.oas3));
this.plugins.forEach((p) => p.decorators?.oas3 && oas3Rules.push(p.decorators.oas3));
return oas3Rules;
case 'oas2':
// eslint-disable-next-line no-case-declarations
const oas2Rules = [];
this.plugins.forEach((p) => p.preprocessors?.oas2 && oas2Rules.push(p.preprocessors.oas2));
this.plugins.forEach((p) => p.rules?.oas2 && oas2Rules.push(p.rules.oas2));
this.plugins.forEach((p) => p.decorators?.oas2 && oas2Rules.push(p.decorators.oas2));
return oas2Rules;
case 'async2':
// eslint-disable-next-line no-case-declarations
const asyncApi2Rules = [];
this.plugins.forEach((p) => p.preprocessors?.async2 && asyncApi2Rules.push(p.preprocessors.async2));
this.plugins.forEach((p) => p.rules?.async2 && asyncApi2Rules.push(p.rules.async2));
this.plugins.forEach((p) => p.decorators?.async2 && asyncApi2Rules.push(p.decorators.async2));
return asyncApi2Rules;
case 'async3':
// eslint-disable-next-line no-case-declarations
const asyncApi3Rules = [];
this.plugins.forEach((p) => p.preprocessors?.async3 && asyncApi3Rules.push(p.preprocessors.async3));
this.plugins.forEach((p) => p.rules?.async3 && asyncApi3Rules.push(p.rules.async3));
this.plugins.forEach((p) => p.decorators?.async3 && asyncApi3Rules.push(p.decorators.async3));
return asyncApi3Rules;
case 'arazzo1':
// eslint-disable-next-line no-case-declarations
const arazzo1Rules = [];
this.plugins.forEach((p) => p.preprocessors?.arazzo1 && arazzo1Rules.push(p.preprocessors.arazzo1));
this.plugins.forEach((p) => p.rules?.arazzo1 && arazzo1Rules.push(p.rules.arazzo1));
this.plugins.forEach((p) => p.decorators?.arazzo1 && arazzo1Rules.push(p.decorators.arazzo1));
return arazzo1Rules;
case 'overlay1':
// eslint-disable-next-line no-case-declarations
const overlay1Rules = [];
this.plugins.forEach((p) => p.preprocessors?.overlay1 && overlay1Rules.push(p.preprocessors.overlay1));
this.plugins.forEach((p) => p.rules?.overlay1 && overlay1Rules.push(p.rules.overlay1));
this.plugins.forEach((p) => p.decorators?.overlay1 && overlay1Rules.push(p.decorators.overlay1));
return overlay1Rules;
}
}
skipRules(rules) {
for (const ruleId of rules || []) {
for (const version of specVersions) {
if (this.rules[version][ruleId]) {
this.rules[version][ruleId] = 'off';
}
else if (Array.isArray(this.rules[version].assertions)) {
// skip assertions
for (const configurableRule of this.rules[version].assertions) {
if (configurableRule.assertionId === ruleId) {
configurableRule.severity = 'off';
}
}
}
}
}
}
skipPreprocessors(preprocessors) {
for (const preprocessorId of preprocessors || []) {
for (const version of specVersions) {
if (this.preprocessors[version][preprocessorId]) {
this.preprocessors[version][preprocessorId] = 'off';
}
}
}
}
skipDecorators(decorators) {
for (const decoratorId of decorators || []) {
for (const version of specVersions) {
if (this.decorators[version][decoratorId]) {
this.decorators[version][decoratorId] = 'off';
}
}
}
}
}
//# sourceMappingURL=config.js.map