Files
docs-app/src/components/tina-markdown/embedded-elements/api-reference/utils.ts
2026-04-01 09:38:50 +02:00

163 lines
4.9 KiB
TypeScript

import type { Endpoint, ExpandedResponsesState } from "./types";
export const resolveReference = (ref: string, definitions: any): any => {
if (!ref || typeof ref !== "string" || !ref.startsWith("#/")) {
return null;
}
// Extract the path from the reference
const path = ref.substring(2).split("/");
// Navigate through the definitions object
let result = definitions;
for (const segment of path) {
if (!result || !result[segment]) {
return null;
}
result = result[segment];
}
return result;
};
export const generateExample = (
schema: any,
definitions: any,
depth = 0
): any => {
if (depth > 3) return "...";
if (schema.$ref) {
const refSchema = resolveReference(schema.$ref, definitions);
if (refSchema) {
return generateExample(refSchema, definitions, depth);
}
return `<${schema.$ref.split("/").pop()}>`;
}
switch (schema.type) {
case "string":
return schema.example || schema.default || "string";
case "integer":
case "number":
return schema.example || schema.default || 0;
case "boolean":
return schema.example || schema.default || false;
case "array":
if (schema.items) {
const itemExample = generateExample(
schema.items,
definitions,
depth + 1
);
return [itemExample];
}
return [];
case "object":
if (schema.properties) {
const obj: any = {};
for (const [key, prop] of Object.entries(schema.properties)) {
obj[key] = generateExample(prop, definitions, depth + 1);
}
return obj;
}
return {};
default:
if (schema.properties) {
const obj: any = {};
for (const [key, prop] of Object.entries(schema.properties)) {
obj[key] = generateExample(prop, definitions, depth + 1);
}
return obj;
}
return null;
}
};
export const extractEndpoints = (schemaJson: any) => {
const endpoints: Endpoint[] = [];
if (schemaJson.paths) {
for (const path of Object.keys(schemaJson.paths)) {
const pathObj = schemaJson.paths[path];
for (const method of Object.keys(pathObj)) {
if (method === "parameters") continue; // Skip path-level parameters
const operation = pathObj[method];
// Handle request body for OpenAPI 3.0 or Swagger 2.0
let requestBody: any = undefined;
if (operation.requestBody) {
requestBody = operation.requestBody;
} else if (operation.parameters?.some((p: any) => p.in === "body")) {
requestBody = {
content: {
"application/json": {
schema:
operation.parameters.find((p: any) => p.in === "body")
?.schema || {},
},
},
};
}
// Filter out body parameters if we have a request body
const parameters = [
...(pathObj.parameters || []), // Include path-level parameters
...(operation.parameters || []),
].filter((p) => {
// If we have a request body from a body parameter, filter out that parameter
if (requestBody && p.in === "body") {
return false;
}
return true;
});
endpoints.push({
path,
method: method.toUpperCase(),
summary: operation.summary || `${method.toUpperCase()} ${path}`,
description: operation.description,
operationId: operation.operationId,
parameters,
responses: operation.responses,
requestBody,
tags: operation.tags,
security: operation.security,
});
}
}
}
return endpoints;
};
// Generates the initial expanded/collapsed state for endpoint responses.
// A response will be expanded by default when:
// 1. The response object contains expandable content (schema, content, etc.)
// The returned Map is keyed by `<METHOD>-<PATH>-<STATUS_CODE>` and the value
// indicates whether the response should start expanded (true) or collapsed (false).
export const generateInitialExpandedState = (
endpoints: Endpoint[]
): ExpandedResponsesState => {
const initialState: ExpandedResponsesState = new Map();
for (const endpoint of endpoints) {
if (!endpoint.responses) continue;
for (const [code, response] of Object.entries(endpoint.responses)) {
const key = `${endpoint.method}-${endpoint.path}-${code}`;
// Cast response to any for property access convenience
const resp = response as any;
const hasExpandableContent =
resp &&
((resp.content && Object.keys(resp.content).length > 0) || // OpenAPI 3.x style
resp.schema || // Swagger 2.0 style
(typeof resp === "object" &&
Object.keys(resp).some((k) => k !== "description")));
initialState.set(key, hasExpandableContent);
}
}
return initialState;
};