@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
text/typescript
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;
}
}