@redocly/openapi-core
Version:
See https://github.com/Redocly/redocly-cli
171 lines • 7.4 kB
JavaScript
import * as redoclyConfig from '@redocly/config';
import { createConfig } from './config/index.js';
import { lintDocument } from './lint.js';
import { BaseResolver, resolveDocument, makeDocumentFromString } from './resolve.js';
import { EntityKeyValid } from './rules/catalog-entity/entity-key-valid.js';
import { Assertions } from './rules/common/assertions/index.js';
import { NoUnresolvedRefs } from './rules/common/no-unresolved-refs.js';
import { Struct } from './rules/common/struct.js';
import { createEntityTypes, ENTITY_DISCRIMINATOR_PROPERTY_NAME, ENTITY_TYPES_WITH_API_SUPPORT, } from './types/entity.js';
import { normalizeTypes } from './types/index.js';
import { isEmptyObject } from './utils/is-empty-object.js';
import { isPlainObject } from './utils/is-plain-object.js';
import { categorizeAssertions, findDataSchemaInDocument, transformScorecardRulesToAssertions, } from './utils/scorecards.js';
import { normalizeVisitors, } from './visitors.js';
import { walkDocument, } from './walk.js';
export async function lintEntityFile(opts) {
const { document, entitySchema, entityDefaultSchema, severity, externalRefResolver = new BaseResolver(), assertionConfig = {}, } = opts;
const ctx = {
problems: [],
specVersion: 'entity', // FIXME: this should be proper SpecVersion
visitorsData: {},
};
const { entityTypes, discriminatorResolver } = createEntityTypes(entitySchema, entityDefaultSchema);
const types = normalizeTypes(entityTypes);
let rootType = types.Entity;
if (Array.isArray(document.parsed)) {
rootType = types.EntityFileArray;
}
else if (isPlainObject(document.parsed)) {
const discriminatedPropertyValue = document.parsed[ENTITY_DISCRIMINATOR_PROPERTY_NAME];
if (!discriminatedPropertyValue) {
rootType = types.Entity;
}
else {
const discriminatedTypeName = discriminatorResolver?.(document.parsed, discriminatedPropertyValue);
if (discriminatedTypeName &&
typeof discriminatedTypeName === 'string' &&
types[discriminatedTypeName]) {
rootType = types[discriminatedTypeName];
}
}
}
const assertionVisitors = Assertions(assertionConfig);
const flattenedAssertions = Array.isArray(assertionVisitors)
? assertionVisitors.map((visitor) => ({
severity: severity || 'error',
ruleId: 'entity assertions',
visitor: visitor,
}))
: [
{
severity: severity || 'error',
ruleId: 'entity assertions',
visitor: assertionVisitors,
},
];
const rules = [
{
severity: severity || 'error',
ruleId: 'entity struct',
visitor: Struct({ severity: 'error' }),
},
{
severity: severity || 'error',
ruleId: 'entity no-unresolved-refs',
visitor: NoUnresolvedRefs({ severity: 'error' }),
},
{
severity: severity || 'error',
ruleId: 'entity key-valid',
visitor: EntityKeyValid({ severity: 'error' }),
},
...flattenedAssertions,
];
const normalizedVisitors = normalizeVisitors(rules, types);
const resolvedRefMap = await resolveDocument({
rootDocument: document,
rootType,
externalRefResolver,
});
walkDocument({
document,
rootType,
normalizedVisitors,
resolvedRefMap,
ctx,
});
return ctx.problems;
}
export async function lintEntityWithScorecardLevel(entity, scorecardLevel, document) {
if (!scorecardLevel.rules) {
throw new Error(`Scorecard level "${scorecardLevel.name}" has no rules defined.`);
}
const externalRefResolver = new BaseResolver();
//Create a document from the entity object
const entityDocument = makeDocumentFromString(JSON.stringify(entity, null, 2), 'entity');
const discriminatorValue = entityDocument.parsed[ENTITY_DISCRIMINATOR_PROPERTY_NAME];
const assertionConfig = transformScorecardRulesToAssertions(discriminatorValue || 'unknown', scorecardLevel.rules);
const { entityRules, apiRules } = categorizeAssertions(assertionConfig);
const entityProblems = await lintEntityFile({
document: entityDocument,
entitySchema: redoclyConfig.entityFileSchema,
entityDefaultSchema: redoclyConfig.entityFileDefaultSchema,
externalRefResolver,
assertionConfig: entityRules,
});
if (ENTITY_TYPES_WITH_API_SUPPORT.includes(entity.type)) {
if (Object.keys(apiRules).length === 0) {
return entityProblems;
}
if (!document) {
throw new Error(`Document is required for entity type "${entity.type}". Provide the source API document to lint API rules.`);
}
if (entity.type === 'data-schema' && entity.metadata?.schema) {
Object.values(apiRules).forEach((rule) => {
if (typeof rule === 'object' && rule.subject.type !== 'Schema') {
throw new Error(`API rules for "data-schema" entity must target Schema subject, but found "${rule.subject.type}".`);
}
});
const schema = findDataSchemaInDocument(entity.title, entity.metadata.schema, document);
if (!schema) {
throw new Error(`Schema "${entity.title}" not found in the document. Ensure the schema exists in components.schemas.`);
}
const schemaProblems = await lintSchema({
schema,
schemaKey: entity.title,
config: await createConfig({
rules: apiRules,
}),
specType: entity.metadata.specType,
sourceDocument: document,
externalRefResolver,
});
return [...entityProblems, ...schemaProblems];
}
const apiProblems = await lintDocument({
document,
externalRefResolver,
config: await createConfig({
rules: apiRules,
}),
});
return [...entityProblems, ...apiProblems];
}
else if (!isEmptyObject(apiRules)) {
throw new Error(`API rules are not supported for entity type "${entity.type}". Only ${ENTITY_TYPES_WITH_API_SUPPORT.join(', ')} support API rules.`);
}
return entityProblems;
}
export async function lintSchema(opts) {
const { schema, schemaKey, config, sourceDocument, specType, externalRefResolver = new BaseResolver(config.resolve), } = opts;
const parsed = sourceDocument.parsed;
const specVersion = parsed[specType];
const info = parsed.info;
const schemaDocument = makeDocumentFromString(JSON.stringify({
[specType]: specVersion,
info,
components: {
schemas: {
[schemaKey]: schema,
},
},
}, null, 2), sourceDocument?.source.absoluteRef || `schema:${schemaKey}`);
const problems = await lintDocument({
document: schemaDocument,
config,
externalRefResolver,
});
return problems.filter((problem) => problem.location.some((loc) => loc.pointer?.includes(`/components/schemas/${schemaKey}`)));
}
//# sourceMappingURL=lint-entity.js.map