- parseSpec now tries dereference first, falls back to parse on failure - Shows warning when spec has broken references - Tested with Moravia Symfonie API (293 paths, 381 operations) Co-Authored-By: Claude <noreply@anthropic.com>
107 lines
2.9 KiB
TypeScript
107 lines
2.9 KiB
TypeScript
import SwaggerParser from '@apidevtools/swagger-parser';
|
|
import type { OpenAPI, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
|
|
import type { ParsedSpec, OpenAPISpec } from './types.js';
|
|
|
|
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] as const;
|
|
|
|
export async function parseSpec(specPath: string, options?: { dereference?: boolean }): Promise<{
|
|
spec: OpenAPISpec;
|
|
metadata: ParsedSpec;
|
|
dereferenced: boolean;
|
|
}> {
|
|
const shouldDereference = options?.dereference !== false;
|
|
|
|
let spec: OpenAPISpec;
|
|
let dereferenced = false;
|
|
|
|
if (shouldDereference) {
|
|
try {
|
|
spec = await SwaggerParser.dereference(specPath);
|
|
dereferenced = true;
|
|
} catch {
|
|
// Fallback to parse without dereferencing if refs are broken
|
|
spec = await SwaggerParser.parse(specPath);
|
|
}
|
|
} else {
|
|
spec = await SwaggerParser.parse(specPath);
|
|
}
|
|
|
|
const metadata = extractMetadata(spec);
|
|
return { spec, metadata, dereferenced };
|
|
}
|
|
|
|
export async function bundleSpec(specPath: string): Promise<OpenAPISpec> {
|
|
return SwaggerParser.bundle(specPath);
|
|
}
|
|
|
|
function extractMetadata(spec: OpenAPISpec): ParsedSpec {
|
|
const version = getSpecVersion(spec);
|
|
const info = spec.info;
|
|
|
|
const paths = spec.paths || {};
|
|
const pathCount = Object.keys(paths).length;
|
|
|
|
let operationCount = 0;
|
|
const tagsSet = new Set<string>();
|
|
|
|
for (const pathItem of Object.values(paths)) {
|
|
if (!pathItem) continue;
|
|
for (const method of HTTP_METHODS) {
|
|
const operation = (pathItem as Record<string, unknown>)[method] as OpenAPIV3.OperationObject | undefined;
|
|
if (operation) {
|
|
operationCount++;
|
|
if (operation.tags) {
|
|
operation.tags.forEach(tag => tagsSet.add(tag));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const schemaCount = getSchemaCount(spec);
|
|
const servers = getServers(spec);
|
|
|
|
return {
|
|
version,
|
|
title: info.title,
|
|
description: info.description,
|
|
servers,
|
|
pathCount,
|
|
schemaCount,
|
|
operationCount,
|
|
tags: Array.from(tagsSet).sort(),
|
|
};
|
|
}
|
|
|
|
function getSpecVersion(spec: OpenAPISpec): string {
|
|
if ('openapi' in spec) {
|
|
return `OpenAPI ${spec.openapi}`;
|
|
}
|
|
if ('swagger' in spec) {
|
|
return `Swagger ${spec.swagger}`;
|
|
}
|
|
return 'Unknown';
|
|
}
|
|
|
|
function getSchemaCount(spec: OpenAPISpec): number {
|
|
if ('components' in spec && spec.components?.schemas) {
|
|
return Object.keys(spec.components.schemas).length;
|
|
}
|
|
if ('definitions' in spec && spec.definitions) {
|
|
return Object.keys(spec.definitions).length;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function getServers(spec: OpenAPISpec): string[] {
|
|
if ('servers' in spec && spec.servers) {
|
|
return spec.servers.map(s => s.url);
|
|
}
|
|
if ('host' in spec) {
|
|
const swagger2 = spec as { host?: string; basePath?: string; schemes?: string[] };
|
|
const scheme = swagger2.schemes?.[0] || 'https';
|
|
const host = swagger2.host || 'localhost';
|
|
const basePath = swagger2.basePath || '';
|
|
return [`${scheme}://${host}${basePath}`];
|
|
}
|
|
return [];
|
|
}
|