UNPKG

@scalar/openapi-parser

Version:

modern OpenAPI parser written in TypeScript

179 lines (178 loc) 5.38 kB
import { mergeObjects } from "../../utils/join/merge-objects.js"; import { upgrade } from "../../utils/upgrade.js"; import { bundle } from "@scalar/json-magic/bundle"; const getSetIntersection = (a, b) => { const result = []; for (const value of a) { if (b.has(value)) { result.push(value); } } return result; }; const withDefault = (value, defaultValue) => { if (Array.isArray(value)) { return value.length ? value : defaultValue; } if (typeof value === "object" && value !== null) { return Object.keys(value).length ? value : defaultValue; } return value ?? defaultValue; }; const mergePaths = (inputs) => { const result = {}; const conflicts = []; for (const paths of inputs) { if (typeof paths !== "object") { continue; } for (const [path, pathItem] of Object.entries(paths)) { if (!result[path]) { result[path] = pathItem; continue; } const intersectingKeys = getSetIntersection(new Set(Object.keys(result[path])), new Set(Object.keys(pathItem))); result[path] = { ...result[path], ...pathItem }; intersectingKeys.forEach((key) => conflicts.push({ method: key, path })); } } return { paths: result, conflicts }; }; const mergeTags = (inputs) => { const cache = /* @__PURE__ */ new Set(); const result = []; for (const tags of inputs) { for (const tag of tags) { if (!cache.has(tag.name)) { result.push(tag); } cache.add(tag.name); } } return result; }; const mergeServers = (inputs) => { const cache = /* @__PURE__ */ new Set(); const result = []; for (const servers of inputs) { for (const server of servers) { if (!cache.has(server.url)) { result.push(server); } cache.add(server.url); } } return result; }; const mergeComponents = (inputs) => { const result = {}; const conflicts = []; for (const components of inputs) { if (typeof components !== "object") { continue; } for (const [key, value] of Object.entries(components)) { for (const [name, component] of Object.entries(value)) { if (!result[key]) { result[key] = {}; } if (result[key][name]) { conflicts.push({ componentType: key, name }); } else { result[key][name] = component; } } } } return { components: result, conflicts }; }; const prefixComponents = async (inputs, prefixes) => { for (const index of inputs.keys()) { await bundle(inputs[index], { treeShake: false, urlMap: false, plugins: [ // Plugin to update $ref values to use the prefixed component names { type: "lifecycle", onBeforeNodeProcess: (node) => { const ref = node["$ref"]; if (typeof ref !== "string") { return; } if (!ref.startsWith("#/components/")) { return; } const parts = ref.split("/"); if (parts.length < 4) { return; } parts[3] = `${prefixes[index] ?? ""}${parts[3]}`; node["$ref"] = parts.join("/"); } }, // Plugin to rename component keys with the prefix { type: "lifecycle", onBeforeNodeProcess: (node, context) => { if (context.path.length === 2 && context.path[0] === "components") { const prefix = prefixes[index]; Object.keys(node).forEach((key) => { const newKey = `${prefix ?? ""}${key}`; const childNode = node[key]; delete node[key]; node[newKey] = childNode; }); } } } ] }); } }; const join = async (inputs, config) => { const upgraded = inputs.map((it) => upgrade(it).specification); if (config?.prefixComponents) { await prefixComponents(upgraded, config.prefixComponents); } upgraded.reverse(); const info = upgraded.reduce((acc, curr) => { if (curr.info && typeof curr.info === "object") { return mergeObjects(acc, curr.info); } return acc; }, {}); const { paths, conflicts: pathConflicts } = mergePaths(upgraded.map((it) => it.paths ?? {})); const { paths: webhooks, conflicts: webhookConflicts } = mergePaths(upgraded.map((it) => it.webhooks ?? {})); const tags = mergeTags(upgraded.map((it) => it.tags ?? [])); const servers = mergeServers(upgraded.map((it) => it.servers ?? [])); const { components, conflicts: componentConflicts } = mergeComponents(upgraded.map((it) => it.components ?? {})); const result = upgraded.reduce((acc, curr) => ({ ...acc, ...curr }), {}); const conflicts = [ ...pathConflicts.map((it) => ({ type: "path", ...it })), ...webhookConflicts.map((it) => ({ type: "webhook", ...it })), ...componentConflicts.map((it) => ({ type: "component", ...it })) ]; if (conflicts.length) { return { ok: false, conflicts }; } return { ok: true, document: { ...result, info, paths, webhooks: withDefault(webhooks, void 0), tags: withDefault(tags, void 0), servers: withDefault(servers, void 0), components: withDefault(components, void 0) } }; }; export { join }; //# sourceMappingURL=join.js.map