import { SpecExtension } from './types'; import type { NormalizedNodeType } from './types'; import type { Stack } from './utils'; import type { UserContext, ResolveResult, ProblemSeverity } from './walk'; import type { Location } from './ref-utils'; import type { Oas3Definition, Oas3_1Definition, Oas3ExternalDocs, Oas3Info, Oas3Contact, Oas3Components, Oas3_1Components, Oas3License, Oas3Schema, Oas3_1Schema, Oas3Header, Oas3Parameter, Oas3Operation, Oas3PathItem, Oas3ServerVariable, Oas3Server, Oas3MediaType, Oas3Response, Oas3Example, Oas3RequestBody, Oas3Tag, OasRef, Oas3SecurityScheme, Oas3SecurityRequirement, Oas3Encoding, Oas3Link, Oas3Xml, Oas3Discriminator, Oas3Callback, } from './typings/openapi'; import type { Oas2Definition, Oas2Tag, Oas2ExternalDocs, Oas2SecurityRequirement, Oas2Info, Oas2Contact, Oas2License, Oas2PathItem, Oas2Operation, Oas2Header, Oas2Response, Oas2Schema, Oas2Xml, Oas2Parameter, Oas2SecurityScheme, } from './typings/swagger'; import type { Async2Definition } from './typings/asyncapi'; import type { Async3Definition } from './typings/asyncapi3'; import type { ArazzoDefinition, ArazzoSourceDescription, CriteriaObject, ExtendedOperation, InfoObject, OnFailureObject, OnSuccessObject, OpenAPISourceDescription, Parameter, Replacement, RequestBody, SourceDescription, Step, Workflow, } from './typings/arazzo'; import type { Overlay1Definition } from './typings/overlay'; export type SkipFunctionContext = Pick< UserContext, 'location' | 'rawNode' | 'resolve' | 'rawLocation' >; export type VisitFunction = ( node: T, ctx: UserContext & { ignoreNextVisitorsOnNode: () => void }, parents?: any, context?: any ) => void; type VisitRefFunction = (node: OasRef, ctx: UserContext, resolved: ResolveResult) => void; type SkipFunction = (node: T, key: string | number, ctx: SkipFunctionContext) => boolean; type VisitObject = { enter?: VisitFunction; leave?: VisitFunction; skip?: SkipFunction; }; export type NestedVisitObject = VisitObject & NestedVisitor

; type VisitFunctionOrObject = VisitFunction | VisitObject; export type VisitorNode = { ruleId: string; severity: ProblemSeverity; message?: string; context: VisitorLevelContext | VisitorSkippedLevelContext; depth: number; visit: VisitFunction; skip?: SkipFunction; }; type VisitorRefNode = { ruleId: string; severity: ProblemSeverity; message?: string; context: VisitorLevelContext; depth: number; visit: VisitRefFunction; }; export type VisitorLevelContext = { isSkippedLevel: false; type: NormalizedNodeType; parent: VisitorLevelContext | null; activatedOn: Stack<{ node?: any; withParentNode?: any; skipped: boolean; nextLevelTypeActivated: Stack; location?: Location; }>; }; export type VisitorSkippedLevelContext = { isSkippedLevel: true; parent: VisitorLevelContext; seen: Set; }; export type NormalizeVisitor = Fn extends VisitFunction ? VisitorNode : never; export type BaseVisitor = { any?: | { enter?: VisitFunction; leave?: VisitFunction; skip?: SkipFunction; } | VisitFunction; ref?: | { enter?: VisitRefFunction; leave?: VisitRefFunction; } | VisitRefFunction; }; type Oas3FlatVisitor = { Root?: VisitFunctionOrObject; Tag?: VisitFunctionOrObject; ExternalDocs?: VisitFunctionOrObject; Server?: VisitFunctionOrObject; ServerVariable?: VisitFunctionOrObject; SecurityRequirement?: VisitFunctionOrObject; Info?: VisitFunctionOrObject; Contact?: VisitFunctionOrObject; License?: VisitFunctionOrObject; Paths?: VisitFunctionOrObject>>; PathItem?: VisitFunctionOrObject>; Callback?: VisitFunctionOrObject>; CallbacksMap?: VisitFunctionOrObject>>; Parameter?: VisitFunctionOrObject>; Operation?: VisitFunctionOrObject>; RequestBody?: VisitFunctionOrObject>; MediaTypesMap?: VisitFunctionOrObject>>; MediaType?: VisitFunctionOrObject>; Example?: VisitFunctionOrObject; Encoding?: VisitFunctionOrObject>; Header?: VisitFunctionOrObject>; Responses?: VisitFunctionOrObject>>; Response?: VisitFunctionOrObject>; Link?: VisitFunctionOrObject; Schema?: VisitFunctionOrObject; Xml?: VisitFunctionOrObject; SchemaProperties?: VisitFunctionOrObject>; DiscriminatorMapping?: VisitFunctionOrObject>; Discriminator?: VisitFunctionOrObject; Components?: VisitFunctionOrObject; NamedSchemas?: VisitFunctionOrObject>; NamedResponses?: VisitFunctionOrObject>>; NamedParameters?: VisitFunctionOrObject>>; NamedExamples?: VisitFunctionOrObject>; NamedRequestBodies?: VisitFunctionOrObject< Record> >; NamedHeaders?: VisitFunctionOrObject>>; NamedSecuritySchemes?: VisitFunctionOrObject>; NamedLinks?: VisitFunctionOrObject>; NamedCallbacks?: VisitFunctionOrObject>>; ImplicitFlow?: VisitFunctionOrObject; PasswordFlow?: VisitFunctionOrObject; ClientCredentials?: VisitFunctionOrObject; AuthorizationCode?: VisitFunctionOrObject; OAuth2Flows?: VisitFunctionOrObject; SecurityScheme?: VisitFunctionOrObject; SpecExtension?: VisitFunctionOrObject; }; type Oas2FlatVisitor = { Root?: VisitFunctionOrObject; Tag?: VisitFunctionOrObject; ExternalDocs?: VisitFunctionOrObject; SecurityRequirement?: VisitFunctionOrObject; Info?: VisitFunctionOrObject; Contact?: VisitFunctionOrObject; License?: VisitFunctionOrObject; Paths?: VisitFunctionOrObject>; PathItem?: VisitFunctionOrObject; Parameter?: VisitFunctionOrObject; Operation?: VisitFunctionOrObject; Examples?: VisitFunctionOrObject>; Header?: VisitFunctionOrObject; Responses?: VisitFunctionOrObject>; Response?: VisitFunctionOrObject; Schema?: VisitFunctionOrObject; Xml?: VisitFunctionOrObject; SchemaProperties?: VisitFunctionOrObject>; NamedSchemas?: VisitFunctionOrObject>; NamedResponses?: VisitFunctionOrObject>; NamedParameters?: VisitFunctionOrObject>; SecurityScheme?: VisitFunctionOrObject; NamedSecuritySchemes?: VisitFunctionOrObject>; SpecExtension?: VisitFunctionOrObject; }; type Async2FlatVisitor = { Root?: VisitFunctionOrObject; }; type Async3FlatVisitor = { Root?: VisitFunctionOrObject; }; type ArazzoFlatVisitor = { Root?: VisitFunctionOrObject; ParameterObject?: VisitFunctionOrObject; InfoObject?: VisitFunctionOrObject; OpenAPISourceDescription?: VisitFunctionOrObject; ArazzoSourceDescription?: VisitFunctionOrObject; SourceDescription?: VisitFunctionOrObject; ExtendedOperation?: VisitFunctionOrObject; Replacement?: VisitFunctionOrObject; RequestBody?: VisitFunctionOrObject; CriteriaObject?: VisitFunctionOrObject; OnSuccessObject?: VisitFunctionOrObject; OnFailureObject?: VisitFunctionOrObject; Step?: VisitFunctionOrObject; Steps?: VisitFunctionOrObject; Workflow?: VisitFunctionOrObject; Workflows?: VisitFunctionOrObject; }; type Overlay1FlatVisitor = { Root?: VisitFunctionOrObject; }; const legacyTypesMap = { Root: 'DefinitionRoot', ServerVariablesMap: 'ServerVariableMap', Paths: ['PathMap', 'PathsMap'], CallbacksMap: 'CallbackMap', MediaTypesMap: 'MediaTypeMap', ExamplesMap: 'ExampleMap', EncodingMap: 'EncodingsMap', HeadersMap: 'HeaderMap', LinksMap: 'LinkMap', OAuth2Flows: 'SecuritySchemeFlows', Responses: 'ResponsesMap', }; type Oas3NestedVisitor = { // eslint-disable-next-line @typescript-eslint/ban-types [T in keyof Oas3FlatVisitor]: Oas3FlatVisitor[T] extends Function ? Oas3FlatVisitor[T] : Oas3FlatVisitor[T] & NestedVisitor; }; type Oas2NestedVisitor = { // eslint-disable-next-line @typescript-eslint/ban-types [T in keyof Oas2FlatVisitor]: Oas2FlatVisitor[T] extends Function ? Oas2FlatVisitor[T] : Oas2FlatVisitor[T] & NestedVisitor; }; type Async2NestedVisitor = { // eslint-disable-next-line @typescript-eslint/ban-types [T in keyof Async2FlatVisitor]: Async2FlatVisitor[T] extends Function ? Async2FlatVisitor[T] : Async2FlatVisitor[T] & NestedVisitor; }; type Async3NestedVisitor = { // eslint-disable-next-line @typescript-eslint/ban-types [T in keyof Async3FlatVisitor]: Async3FlatVisitor[T] extends Function ? Async3FlatVisitor[T] : Async3FlatVisitor[T] & NestedVisitor; }; type ArazzoNestedVisitor = { // eslint-disable-next-line @typescript-eslint/ban-types [T in keyof ArazzoFlatVisitor]: ArazzoFlatVisitor[T] extends Function ? ArazzoFlatVisitor[T] : ArazzoFlatVisitor[T] & NestedVisitor; }; type Overlay1NestedVisitor = { // eslint-disable-next-line @typescript-eslint/ban-types [T in keyof Overlay1FlatVisitor]: Overlay1FlatVisitor[T] extends Function ? Overlay1FlatVisitor[T] : Overlay1FlatVisitor[T] & NestedVisitor; }; export type Oas3Visitor = BaseVisitor & Oas3NestedVisitor & Record | NestedVisitObject>; export type Oas2Visitor = BaseVisitor & Oas2NestedVisitor & Record | NestedVisitObject>; export type Async2Visitor = BaseVisitor & Async2NestedVisitor & Record | NestedVisitObject>; export type Async3Visitor = BaseVisitor & Async3NestedVisitor & Record | NestedVisitObject>; export type Arazzo1Visitor = BaseVisitor & ArazzoNestedVisitor & Record | NestedVisitObject>; export type Overlay1Visitor = BaseVisitor & Overlay1NestedVisitor & Record | NestedVisitObject>; export type NestedVisitor = Exclude; export type NormalizedOasVisitors = { [V in keyof T]-?: { enter: Array>; leave: Array>; }; } & { ref: { enter: Array; leave: Array; }; [k: string]: { // any internal types enter: Array>; leave: Array>; }; }; export type Oas3Rule = (options: Record) => Oas3Visitor | Oas3Visitor[]; export type Oas2Rule = (options: Record) => Oas2Visitor | Oas2Visitor[]; export type Async2Rule = (options: Record) => Async2Visitor | Async2Visitor[]; export type Async3Rule = (options: Record) => Async3Visitor | Async3Visitor[]; export type Arazzo1Rule = (options: Record) => Arazzo1Visitor | Arazzo1Visitor[]; export type Overlay1Rule = (options: Record) => Overlay1Visitor | Overlay1Visitor[]; export type Oas3Preprocessor = (options: Record) => Oas3Visitor; export type Oas2Preprocessor = (options: Record) => Oas2Visitor; export type Async2Preprocessor = (options: Record) => Async2Visitor; export type Async3Preprocessor = (options: Record) => Async3Visitor; export type Arazzo1Preprocessor = (options: Record) => Arazzo1Visitor; export type Overlay1Preprocessor = (options: Record) => Overlay1Visitor; export type Oas3Decorator = (options: Record) => Oas3Visitor; export type Oas2Decorator = (options: Record) => Oas2Visitor; export type Async2Decorator = (options: Record) => Async2Visitor; export type Async3Decorator = (options: Record) => Async3Visitor; export type Arazzo1Decorator = (options: Record) => Arazzo1Visitor; export type Overlay1Decorator = (options: Record) => Overlay1Visitor; // alias for the latest version supported // every time we update it - consider semver export type OasRule = Oas3Rule; export type OasPreprocessor = Oas3Preprocessor; export type OasDecorator = Oas3Decorator; export type RuleInstanceConfig = { ruleId: string; severity: ProblemSeverity; message?: string; }; export function normalizeVisitors( visitorsConfig: (RuleInstanceConfig & { visitor: NestedVisitObject })[], types: Record ): NormalizedOasVisitors { const normalizedVisitors: NormalizedOasVisitors = {} as any; normalizedVisitors.any = { enter: [], leave: [], }; for (const typeName of Object.keys(types) as Array) { normalizedVisitors[typeName] = { enter: [], leave: [], } as any; } normalizedVisitors.ref = { enter: [], leave: [], }; for (const { ruleId, severity, message, visitor } of visitorsConfig) { normalizeVisitorLevel({ ruleId, severity, message }, visitor, null); } for (const v of Object.keys(normalizedVisitors)) { normalizedVisitors[v].enter.sort((a, b) => b.depth - a.depth); normalizedVisitors[v].leave.sort((a, b) => a.depth - b.depth); } return normalizedVisitors; function addWeakNodes( ruleConf: RuleInstanceConfig, from: NormalizedNodeType, to: NormalizedNodeType, parentContext: VisitorLevelContext, stack: NormalizedNodeType[] = [] ) { if (stack.includes(from)) return; stack = [...stack, from]; const possibleChildren = new Set(); for (const type of Object.values(from.properties)) { if (type === to) { addWeakFromStack(ruleConf, stack); continue; } if (typeof type === 'object' && type !== null && type.name) { possibleChildren.add(type); } } if (from.additionalProperties && typeof from.additionalProperties !== 'function') { if (from.additionalProperties === to) { addWeakFromStack(ruleConf, stack); } else if (from.additionalProperties.name !== undefined) { possibleChildren.add(from.additionalProperties); } } if (from.items && typeof from.items !== 'function') { if (from.items === to) { addWeakFromStack(ruleConf, stack); } else if (from.items.name !== undefined) { possibleChildren.add(from.items); } } if (from.extensionsPrefix) { possibleChildren.add(SpecExtension); } for (const fromType of Array.from(possibleChildren.values())) { addWeakNodes(ruleConf, fromType, to, parentContext, stack); } function addWeakFromStack(ruleConf: RuleInstanceConfig, stack: NormalizedNodeType[]) { for (const interType of stack.slice(1)) { (normalizedVisitors as any)[interType.name] = normalizedVisitors[interType.name] || { enter: [], leave: [], }; normalizedVisitors[interType.name].enter.push({ ...ruleConf, visit: () => undefined, depth: 0, context: { isSkippedLevel: true, seen: new Set(), parent: parentContext, }, }); } } } function findLegacyVisitorNode( visitor: NestedVisitObject, typeName: keyof T | Array ) { if (Array.isArray(typeName)) { const name = typeName.find((name) => visitor[name]) || undefined; return name && visitor[name]; } return visitor[typeName]; } function normalizeVisitorLevel( ruleConf: RuleInstanceConfig, visitor: NestedVisitObject, parentContext: VisitorLevelContext | null, depth = 0 ) { const visitorKeys = Object.keys(types) as Array; if (depth === 0) { visitorKeys.push('any'); visitorKeys.push('ref'); } else { if (visitor.any) { throw new Error('any() is allowed only on top level'); } if (visitor.ref) { throw new Error('ref() is allowed only on top level'); } } for (const typeName of visitorKeys as Array) { const typeVisitor = (visitor[typeName] || findLegacyVisitorNode( visitor, legacyTypesMap[typeName as keyof typeof legacyTypesMap] as keyof T )) as NestedVisitObject; const normalizedTypeVisitor = normalizedVisitors[typeName]; if (!typeVisitor) continue; let visitorEnter: VisitFunction | undefined; let visitorLeave: VisitFunction | undefined; let visitorSkip: SkipFunction | undefined; const isObjectVisitor = typeof typeVisitor === 'object'; if (typeName === 'ref' && isObjectVisitor && typeVisitor.skip) { throw new Error('ref() visitor does not support skip'); } if (typeof typeVisitor === 'function') { visitorEnter = typeVisitor; } else if (isObjectVisitor) { visitorEnter = typeVisitor.enter; visitorLeave = typeVisitor.leave; visitorSkip = typeVisitor.skip; } const context: VisitorLevelContext = { activatedOn: null, type: types[typeName], parent: parentContext, isSkippedLevel: false, }; if (typeof typeVisitor === 'object') { normalizeVisitorLevel(ruleConf, typeVisitor, context, depth + 1); } if (parentContext) { addWeakNodes(ruleConf, parentContext.type, types[typeName], parentContext); } if (visitorEnter || isObjectVisitor) { if (visitorEnter && typeof visitorEnter !== 'function') { throw new Error('DEV: should be function'); } normalizedTypeVisitor.enter.push({ ...ruleConf, visit: visitorEnter || (() => undefined), skip: visitorSkip, depth, context, }); } if (visitorLeave) { if (typeof visitorLeave !== 'function') { throw new Error('DEV: should be function'); } normalizedTypeVisitor.leave.push({ ...ruleConf, visit: visitorLeave, depth, context, }); } } } }