import { readFile } from "node:fs/promises"; import { resolve } from "node:path"; export async function loadJsonSource(source) { if (/^https?:\/\//u.test(source)) { const response = await fetch(source, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(15_000), }); if (!response.ok) { throw new Error( `Unable to load OpenAPI document: ${response.status} ${response.statusText}`, ); } return response.json(); } const content = await readFile(resolve(source), "utf8"); return JSON.parse(content); } export function findMissingOperations(contract, openApiDocument) { const openApiPaths = openApiDocument?.paths; if (!openApiPaths || typeof openApiPaths !== "object") { throw new Error("OpenAPI document does not contain a paths object"); } const normalizedPaths = new Map( Object.entries(openApiPaths).map(([path, pathItem]) => [ normalizePath(path), pathItem, ]), ); return Object.entries(contract).flatMap(([operationId, operation]) => { const method = operation.method.toLowerCase(); const pathItem = normalizedPaths.get(normalizePath(operation.path)); if (!pathItem) { return [{ operationId, ...operation, reason: "path is missing" }]; } if (!pathItem[method]) { return [{ operationId, ...operation, reason: "method is missing" }]; } return []; }); } export function normalizePath(path) { const withoutTrailingSlash = path.length > 1 ? path.replace(/\/+$/u, "") : path; return withoutTrailingSlash.replace(/\{[^/{}]+\}/gu, "{}"); }