|
| 1 | +import { createNamespace, visit, Element, cloneDeep } from '@swagger-api/apidom-core'; |
| 2 | +import asyncApi3Namespace, { |
| 3 | + getNodeType, |
| 4 | + isAsyncApi3Element, |
| 5 | + keyMap, |
| 6 | + mediaTypes, |
| 7 | +} from '@swagger-api/apidom-ns-asyncapi-3'; |
| 8 | + |
| 9 | +import DereferenceStrategy, { DereferenceStrategyOptions } from '../DereferenceStrategy.ts'; |
| 10 | +import File from '../../../File.ts'; |
| 11 | +import Reference from '../../../Reference.ts'; |
| 12 | +import ReferenceSet from '../../../ReferenceSet.ts'; |
| 13 | +import AsyncAPI3DereferenceVisitor from './visitor.ts'; |
| 14 | +import type { ReferenceOptions } from '../../../options/index.ts'; |
| 15 | + |
| 16 | +export type { |
| 17 | + default as DereferenceStrategy, |
| 18 | + DereferenceStrategyOptions, |
| 19 | +} from '../DereferenceStrategy.ts'; |
| 20 | +export type { default as File, FileOptions } from '../../../File.ts'; |
| 21 | +export type { default as Reference, ReferenceOptions } from '../../../Reference.ts'; |
| 22 | +export type { default as ReferenceSet, ReferenceSetOptions } from '../../../ReferenceSet.ts'; |
| 23 | +export type { AsyncAPI3DereferenceVisitorOptions, mutationReplacer } from './visitor.ts'; |
| 24 | +export type { |
| 25 | + ReferenceOptions as ApiDOMReferenceOptions, |
| 26 | + ReferenceBundleOptions as ApiDOMReferenceBundleOptions, |
| 27 | + ReferenceDereferenceOptions as ApiDOMReferenceDereferenceOptions, |
| 28 | + ReferenceParseOptions as ApiDOMReferenceParseOptions, |
| 29 | + ReferenceResolveOptions as ApiDOMReferenceResolveOptions, |
| 30 | +} from '../../../options/index.ts'; |
| 31 | +export type { default as Parser, ParserOptions } from '../../../parse/parsers/Parser.ts'; |
| 32 | +export type { default as Resolver, ResolverOptions } from '../../../resolve/resolvers/Resolver.ts'; |
| 33 | +export type { |
| 34 | + default as ResolveStrategy, |
| 35 | + ResolveStrategyOptions, |
| 36 | +} from '../../../resolve/strategies/ResolveStrategy.ts'; |
| 37 | +export type { |
| 38 | + default as BundleStrategy, |
| 39 | + BundleStrategyOptions, |
| 40 | +} from '../../../bundle/strategies/BundleStrategy.ts'; |
| 41 | +export type { AncestorLineage } from '../../util.ts'; |
| 42 | + |
| 43 | +// @ts-ignore |
| 44 | +const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')]; |
| 45 | + |
| 46 | +/** |
| 47 | + * @public |
| 48 | + */ |
| 49 | +export interface AsyncAPI3DeferenceStrategyOptions |
| 50 | + extends Omit<DereferenceStrategyOptions, 'name'> {} |
| 51 | + |
| 52 | +/** |
| 53 | + * @public |
| 54 | + */ |
| 55 | +class AsyncAPI3DereferenceStrategy extends DereferenceStrategy { |
| 56 | + constructor(options?: AsyncAPI3DeferenceStrategyOptions) { |
| 57 | + super({ ...(options ?? {}), name: 'asyncapi-3' }); |
| 58 | + } |
| 59 | + |
| 60 | + canDereference(file: File): boolean { |
| 61 | + // assert by media type |
| 62 | + if (file.mediaType !== 'text/plain') { |
| 63 | + return mediaTypes.includes(file.mediaType); |
| 64 | + } |
| 65 | + |
| 66 | + // assert by inspecting ApiDOM |
| 67 | + return isAsyncApi3Element(file.parseResult?.api); |
| 68 | + } |
| 69 | + |
| 70 | + async dereference(file: File, options: ReferenceOptions): Promise<Element> { |
| 71 | + const namespace = createNamespace(asyncApi3Namespace); |
| 72 | + const immutableRefSet = options.dereference.refSet ?? new ReferenceSet(); |
| 73 | + const mutableRefSet = new ReferenceSet(); |
| 74 | + let refSet = immutableRefSet; |
| 75 | + let reference: Reference; |
| 76 | + |
| 77 | + if (!immutableRefSet.has(file.uri)) { |
| 78 | + reference = new Reference({ uri: file.uri, value: file.parseResult! }); |
| 79 | + immutableRefSet.add(reference); |
| 80 | + } else { |
| 81 | + // pre-computed refSet was provided as configuration option |
| 82 | + reference = immutableRefSet.find((ref) => ref.uri === file.uri)!; |
| 83 | + } |
| 84 | + |
| 85 | + /** |
| 86 | + * Clone refSet due the dereferencing process being mutable. |
| 87 | + * We don't want to mutate the original refSet and the references. |
| 88 | + */ |
| 89 | + if (options.dereference.immutable) { |
| 90 | + immutableRefSet.refs |
| 91 | + .map( |
| 92 | + (ref) => |
| 93 | + new Reference({ |
| 94 | + ...ref, |
| 95 | + value: cloneDeep(ref.value), |
| 96 | + }), |
| 97 | + ) |
| 98 | + .forEach((ref) => mutableRefSet.add(ref)); |
| 99 | + reference = mutableRefSet.find((ref) => ref.uri === file.uri)!; |
| 100 | + refSet = mutableRefSet; |
| 101 | + } |
| 102 | + |
| 103 | + const visitor = new AsyncAPI3DereferenceVisitor({ reference, namespace, options }); |
| 104 | + const dereferencedElement = await visitAsync(refSet.rootRef!.value, visitor, { |
| 105 | + keyMap, |
| 106 | + nodeTypeGetter: getNodeType, |
| 107 | + }); |
| 108 | + |
| 109 | + /** |
| 110 | + * If immutable option is set, replay refs from the refSet. |
| 111 | + */ |
| 112 | + if (options.dereference.immutable) { |
| 113 | + mutableRefSet.refs |
| 114 | + .filter((ref) => ref.uri.startsWith('immutable://')) |
| 115 | + .map( |
| 116 | + (ref) => |
| 117 | + new Reference({ |
| 118 | + ...ref, |
| 119 | + uri: ref.uri.replace(/^immutable:\/\//, ''), |
| 120 | + }), |
| 121 | + ) |
| 122 | + .forEach((ref) => immutableRefSet.add(ref)); |
| 123 | + } |
| 124 | + |
| 125 | + /** |
| 126 | + * Release all memory if this refSet was not provided as a configuration option. |
| 127 | + * If provided as configuration option, then provider is responsible for cleanup. |
| 128 | + */ |
| 129 | + if (options.dereference.refSet === null) { |
| 130 | + immutableRefSet.clean(); |
| 131 | + } |
| 132 | + |
| 133 | + mutableRefSet.clean(); |
| 134 | + |
| 135 | + return dereferencedElement; |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +export { AsyncAPI3DereferenceVisitor }; |
| 140 | +export default AsyncAPI3DereferenceStrategy; |
0 commit comments