UNPKG

@apiture/api-ref-resolver

Version:

Tool to merge multiple OpenAPI or AsyncAPI documents that use JSON Reference links (`$ref`) to reference API definition elements across source files.

120 lines (108 loc) 3.69 kB
import { ApiObject } from './ApiRefResolver'; import { JsonNavigation, JsonItem } from './JsonNavigation'; /** * Recursively walk a JSON object and invoke a callback function * on each `{ "$ref" : "path" }` object found */ /** * A container: a JSON object or array - a JSON container node */ export type Node = ApiObject; /** * Represents a JSON Reference object, such as * `{"$ref": "#/components/schemas/problemResponse" }` */ export interface RefObject { $ref: string; } /** * Function signature for the visitRefObjects callback */ export type RefVisitor = (node: RefObject, nav: JsonNavigation) => Promise<JsonItem>; /** * Function signature for the walkObject callback */ export type ObjectVisitor = (node: ApiObject, nav: JsonNavigation) => Promise<JsonItem>; /** * Test if a JSON node is a `{ $ref: "uri" }` object */ export function isRef(node: Node): boolean { return ( node !== null && typeof node === 'object' && node.hasOwnProperty('$ref') && typeof (node as RefObject).$ref === 'string' ); } /** * @param node a JSON document node * @returns true if the node has already been processed and resolved. */ function isResolved(node: Node): boolean { // this depends on the tag being added in ApiRefResolver return node !== null && typeof node === 'object' && node.hasOwnProperty('x__resolved__'); } /** * Walk a JSON object and apply `refCallback` when a JSON `{$ref: url }` is found * @param node a node in the OpenAPI document * @param refCallback the function to call on JSON `$ref` objects * @param nav tracks where we are in the original document * @return the modified (annotated) node */ export async function visitRefObjects( node: ApiObject, refCallback: RefVisitor, nav?: JsonNavigation, ): Promise<JsonItem> { const objectVisitor = async (node: object, nav: JsonNavigation): Promise<JsonItem> => { if (isRef(node)) { if (isResolved(node)) { return node; } return await refCallback(node as RefObject, nav); } return node; }; return walkObject(node, objectVisitor, nav); } /** * Walk a JSON object or array and apply objectCallback when a JSON object is found * @param node a node in the OpenAPI document * @param objectCallback the function to call on JSON objects * @param nav tracks where we are in the original document * @return the modified (annotated) node */ export async function walkObject( node: ApiObject, objectCallback: ObjectVisitor, nav?: JsonNavigation, ): Promise<JsonItem> { return walkObj(node, nav || new JsonNavigation(node)); async function walkObj(node: ApiObject, location: JsonNavigation): Promise<JsonItem> { const object = objectCallback(node, location); if (object !== null && typeof object === 'object') { const keys = [...Object.keys(node)]; // make copy since this code may re-enter objects for (const key of keys) { const val = node[key]; if (Array.isArray(val)) { node[key] = await walkArray(val as [], location.with(key)); } else if (val !== null && typeof val === 'object') { node[key] = await walkObj(val, location.with(key)); } } } return object; } async function walkArray(a: [], nav: JsonNavigation): Promise<[]> { const array = a as Node; for (let index = 0; index < a.length; index += 1) { const val = array[index] as Node; if (val !== null && typeof val === 'object') { array[index] = (await walkObj(val, nav.with(index))) as object; } else if (Array.isArray(val)) { array[index] = (await walkArray(val as [], nav.with(index))) as []; } } return a; } }