UNPKG

@redocly/openapi-core

Version:

See https://github.com/Redocly/redocly-cli

171 lines 7.4 kB
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