@redocly/openapi-core
Version:
See https://github.com/Redocly/redocly-cli
233 lines • 7.54 kB
JavaScript
import * as fs from 'node:fs';
import * as path from 'node:path';
import { minimatch } from 'minimatch';
import pluralizeOne from 'pluralize';
import { parseYaml } from './js-yaml/index.js';
import { env } from './env.js';
export { parseYaml, stringifyYaml } from './js-yaml/index.js';
export function pushStack(head, value) {
return { prev: head, value };
}
export function pluralize(sentence, count, inclusive) {
return sentence
.split(' ')
.map((word) => pluralizeOne(word, count, inclusive))
.join(' ');
}
export function popStack(head) {
return head?.prev ?? null;
}
export async function loadYaml(filename) {
const contents = await fs.promises.readFile(filename, 'utf-8');
return parseYaml(contents);
}
export function isDefined(x) {
return x !== undefined;
}
export function isPlainObject(value) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
export function isEmptyObject(value) {
return isPlainObject(value) && Object.keys(value).length === 0;
}
export function isNotEmptyObject(obj) {
return isPlainObject(obj) && !isEmptyObject(obj);
}
export function isEmptyArray(value) {
return Array.isArray(value) && value.length === 0;
}
export function isNotEmptyArray(args) {
return !!args && Array.isArray(args) && !!args.length;
}
export async function readFileFromUrl(url, config) {
const headers = {};
for (const header of config.headers) {
if (match(url, header.matches)) {
headers[header.name] =
header.envVariable !== undefined ? env[header.envVariable] || '' : header.value;
}
}
const req = await (config.customFetch || fetch)(url, {
headers: headers,
});
if (!req.ok) {
throw new Error(`Failed to load ${url}: ${req.status} ${req.statusText}`);
}
return { body: await req.text(), mimeType: req.headers.get('content-type') };
}
function match(url, pattern) {
if (!pattern.match(/^https?:\/\//)) {
// if pattern doesn't specify protocol directly, do not match against it
url = url.replace(/^https?:\/\//, '');
}
return minimatch(url, pattern);
}
export function splitCamelCaseIntoWords(str) {
const camel = str
.split(/(?:[-._])|([A-Z][a-z]+)/)
.filter(isTruthy)
.map((item) => item.toLocaleLowerCase());
const caps = str
.split(/([A-Z]{2,})/)
.filter((e) => e && e === e.toUpperCase())
.map((item) => item.toLocaleLowerCase());
return new Set([...camel, ...caps]);
}
export function validateMimeType({ type, value }, { report, location }, allowedValues) {
const ruleType = type === 'consumes' ? 'request' : 'response';
if (!allowedValues)
throw new Error(`Parameter "allowedValues" is not provided for "${ruleType}-mime-type" rule`);
if (!value[type])
return;
for (const mime of value[type]) {
if (!allowedValues.includes(mime)) {
report({
message: `Mime type "${mime}" is not allowed`,
location: location.child(value[type].indexOf(mime)).key(),
});
}
}
}
export function validateMimeTypeOAS3({ type, value }, { report, location }, allowedValues) {
const ruleType = type === 'consumes' ? 'request' : 'response';
if (!allowedValues)
throw new Error(`Parameter "allowedValues" is not provided for "${ruleType}-mime-type" rule`);
if (!value.content)
return;
for (const mime of Object.keys(value.content)) {
if (!allowedValues.includes(mime)) {
report({
message: `Mime type "${mime}" is not allowed`,
location: location.child('content').child(mime).key(),
});
}
}
}
export function readFileAsStringSync(filePath) {
return fs.readFileSync(filePath, 'utf-8');
}
export function yamlAndJsonSyncReader(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
return parseYaml(content);
}
export function isPathParameter(pathSegment) {
return pathSegment.startsWith('{') && pathSegment.endsWith('}');
}
/**
* Convert Windows backslash paths to slash paths: foo\\bar ➔ foo/bar
*/
export function slash(path) {
const isExtendedLengthPath = /^\\\\\?\\/.test(path);
if (isExtendedLengthPath) {
return path;
}
return path.replace(/\\/g, '/');
}
export function isString(value) {
return typeof value === 'string';
}
export function isNotString(value) {
return !isString(value);
}
export const assignConfig = (target, obj) => {
if (!obj)
return;
for (const k of Object.keys(obj)) {
if (isPlainObject(target[k]) && typeof obj[k] === 'string') {
target[k] = { ...target[k], severity: obj[k] };
}
else {
target[k] = obj[k];
}
}
};
export function assignOnlyExistingConfig(target, obj) {
if (!obj)
return;
for (const k of Object.keys(obj)) {
if (!target.hasOwnProperty(k))
continue;
if (isPlainObject(target[k]) && typeof obj[k] === 'string') {
target[k] = { ...target[k], severity: obj[k] };
}
else {
target[k] = obj[k];
}
}
}
export function getMatchingStatusCodeRange(code) {
return `${code}`.replace(/^(\d)\d\d$/, (_, firstDigit) => `${firstDigit}XX`);
}
export function isCustomRuleId(id) {
return id.includes('/');
}
export function doesYamlFileExist(filePath) {
return ((path.extname(filePath) === '.yaml' || path.extname(filePath) === '.yml') &&
!!fs?.existsSync?.(filePath));
}
export function isTruthy(value) {
return !!value;
}
export function identity(value) {
return value;
}
export function keysOf(obj) {
if (!obj)
return [];
return Object.keys(obj);
}
export function nextTick() {
return new Promise((resolve) => {
setTimeout(resolve);
});
}
export async function pause(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// TODO: move to a separate file
/**
* Checks if two objects are deeply equal.
* Borrowed the source code from https://github.com/lukeed/dequal.
*/
export function dequal(foo, bar) {
let ctor, len;
if (foo === bar)
return true;
if (foo && bar && (ctor = foo.constructor) === bar.constructor) {
if (ctor === Date)
return foo.getTime() === bar.getTime();
if (ctor === RegExp)
return foo.toString() === bar.toString();
if (ctor === Array) {
if ((len = foo.length) === bar.length) {
while (len-- && dequal(foo[len], bar[len]))
;
}
return len === -1;
}
if (!ctor || typeof foo === 'object') {
len = 0;
for (ctor in foo) {
if (Object.prototype.hasOwnProperty.call(foo, ctor) &&
++len &&
!Object.prototype.hasOwnProperty.call(bar, ctor))
return false;
if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor]))
return false;
}
return Object.keys(bar).length === len;
}
}
return foo !== foo && bar !== bar;
}
export function getOwn(obj, key) {
return obj.hasOwnProperty(key) ? obj[key] : undefined;
}
export function omit(obj, keys) {
const result = { ...obj };
keys.forEach((key) => {
delete result[key];
});
return result;
}
//# sourceMappingURL=utils.js.map