@redocly/openapi-core
Version:
See https://github.com/Redocly/redocly-cli
163 lines • 5.72 kB
JavaScript
import { default as levenshtein } from 'js-levenshtein';
import { Location } from '../ref-utils.js';
import { validateJsonSchema } from './ajv.js';
import { isPlainObject } from '../utils/is-plain-object.js';
export function oasTypeOf(value) {
if (Array.isArray(value)) {
return 'array';
}
else if (value === null) {
return 'null';
}
else if (Number.isInteger(value)) {
return 'integer';
}
else {
return typeof value;
}
}
/**
* Checks if value matches specified JSON schema type
*
* @param {*} value - value to check
* @param {JSONSchemaType} type - JSON Schema type
* @returns boolean
*/
export function matchesJsonSchemaType(value, type, nullable) {
if (nullable && value === null) {
return true;
}
switch (type) {
case 'array':
return Array.isArray(value);
case 'object':
return typeof value === 'object' && value !== null && !Array.isArray(value);
case 'null':
return value === null;
case 'integer':
return Number.isInteger(value);
default:
return typeof value === type;
}
}
export function missingRequiredField(type, field) {
return `${type} object should contain \`${field}\` field.`;
}
export function missingRequiredOneOfFields(type, fields) {
return `${type} object should contain one of the fields: ${fields
.map((field) => `\`${field}\``)
.join(', ')}.`;
}
export function fieldNonEmpty(type, field) {
return `${type} object \`${field}\` must be non-empty string.`;
}
export function validateDefinedAndNonEmpty(fieldName, value, ctx) {
if (!isPlainObject(value)) {
return;
}
if (value[fieldName] === undefined) {
ctx.report({
message: missingRequiredField(ctx.type.name, fieldName),
location: ctx.location.child([fieldName]).key(),
});
}
else if (!value[fieldName]) {
ctx.report({
message: fieldNonEmpty(ctx.type.name, fieldName),
location: ctx.location.child([fieldName]).key(),
});
}
}
export function validateOneOfDefinedAndNonEmpty(fieldNames, value, ctx) {
if (!isPlainObject(value)) {
return;
}
if (!fieldNames.some((fieldName) => value.hasOwnProperty(fieldName))) {
ctx.report({
message: missingRequiredOneOfFields(ctx.type.name, fieldNames),
location: ctx.location.key(),
});
}
for (const fieldName of fieldNames) {
if (value.hasOwnProperty(fieldName) && !value[fieldName]) {
ctx.report({
message: fieldNonEmpty(ctx.type.name, fieldName),
location: ctx.location.child([fieldName]).key(),
});
}
}
}
export function getSuggest(given, variants) {
if (given === null)
return variants;
if (typeof given !== 'string' || !variants.length)
return [];
const distances = [];
for (let i = 0; i < variants.length; i++) {
const distance = levenshtein(given, variants[i]);
if (distance < 4) {
distances.push({ distance, variant: variants[i] });
}
}
distances.sort((a, b) => a.distance - b.distance);
// if (bestMatch.distance <= 4) return bestMatch.string;
return distances.map((d) => d.variant);
}
export function validateExample(example, schema, dataLoc, { resolve, location, report }, allowAdditionalProperties) {
try {
const { valid, errors } = validateJsonSchema(example, schema, location.child('schema'), dataLoc.pointer, resolve, allowAdditionalProperties);
if (!valid) {
for (const error of errors) {
report({
message: `Example value must conform to the schema: ${error.message}.`,
location: {
...new Location(dataLoc.source, error.instancePath),
reportOnKey: error.keyword === 'unevaluatedProperties' || error.keyword === 'additionalProperties',
},
from: location,
suggest: error.suggest,
});
}
}
}
catch (e) {
if (e.message === 'discriminator: requires oneOf or anyOf composite keyword') {
return;
}
report({
message: `Example validation errored: ${e.message}.`,
location: location.child('schema'),
from: location,
});
}
}
export function validateSchemaEnumType(schemaEnum, propertyValue, propName, refLocation, { report, location }) {
if (!schemaEnum) {
return;
}
if (!schemaEnum.includes(propertyValue)) {
report({
location,
message: `\`${propName}\` can be one of the following only: ${schemaEnum
.map((type) => `"${type}"`)
.join(', ')}.`,
from: refLocation,
suggest: getSuggest(propertyValue, schemaEnum),
});
}
}
export function validateResponseCodes(responseCodes, codeRange, { report }) {
const responseCodeRegexp = new RegExp(`^${codeRange[0]}[0-9Xx]{2}$`);
const containsNeededCode = responseCodes.some((code) => (codeRange === '2XX' && code === 'default') || // It's OK to replace 2xx codes with the default
responseCodeRegexp.test(code));
if (!containsNeededCode) {
report({
message: `Operation must have at least one \`${codeRange}\` response.`,
location: { reportOnKey: true },
});
}
}
export function getTagName(tag, ignoreCase) {
return ignoreCase ? tag.name.toLowerCase() : tag.name;
}
//# sourceMappingURL=utils.js.map