import { SwaggerEndpoint, SwaggerParameter } from "./types.js";
export class SwaggerDocumentationParser {
private swaggerUrl: string;
private endpoints: SwaggerEndpoint[] = [];
private parsed: boolean = false;
constructor(swaggerUrl: string) {
this.swaggerUrl = swaggerUrl;
}
async parseDocumentation(): Promise<void> {
try {
console.log(`🔍 Parsing Swagger documentation from: ${this.swaggerUrl}`);
// Instead of using SwaggerParser.validate, fetch and parse the JSON directly
// This avoids issues with complex $ref resolution
const response = await fetch(this.swaggerUrl);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const api = (await response.json()) as any;
console.log(
`📊 Swagger JSON fetched successfully. Found ${
Object.keys(api.paths || {}).length
} paths`
);
// Validate basic structure
if (!api.openapi && !api.swagger) {
throw new Error(
"Invalid OpenAPI/Swagger specification: missing version field"
);
}
if (!api.paths) {
throw new Error("Invalid OpenAPI/Swagger specification: missing paths");
}
this.endpoints = this.extractEndpoints(api);
console.log(`✅ Extracted ${this.endpoints.length} endpoints`);
this.parsed = true;
} catch (error) {
console.error(`❌ Swagger parsing failed for ${this.swaggerUrl}:`, error);
throw new Error(
`Failed to parse Swagger documentation: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
}
}
private extractEndpoints(api: any): SwaggerEndpoint[] {
const endpoints: SwaggerEndpoint[] = [];
if (!api.paths) {
return endpoints;
}
for (const [path, pathItem] of Object.entries(api.paths)) {
if (typeof pathItem !== "object" || pathItem === null) continue;
const methods = [
"get",
"post",
"put",
"delete",
"patch",
"options",
"head",
];
for (const method of methods) {
const operation = (pathItem as any)[method];
if (!operation) continue;
const endpoint: SwaggerEndpoint = {
path,
method: method.toUpperCase(),
summary: operation.summary,
description: operation.description,
parameters: this.extractParameters(
operation.parameters,
(pathItem as any).parameters
),
requestBody: operation.requestBody,
responses: operation.responses,
};
endpoints.push(endpoint);
}
}
return endpoints;
}
private extractParameters(
operationParams: any[] = [],
pathParams: any[] = []
): SwaggerParameter[] {
const allParams = [...(pathParams || []), ...(operationParams || [])];
return allParams.map((param) => ({
name: param.name,
in: param.in,
required: param.required || false,
schema: param.schema,
description: param.description,
}));
}
getEndpoints(): SwaggerEndpoint[] {
if (!this.parsed) {
throw new Error(
"Documentation not parsed yet. Call parseDocumentation() first."
);
}
return this.endpoints;
}
getEndpoint(path: string, method: string): SwaggerEndpoint | undefined {
return this.endpoints.find(
(endpoint) =>
endpoint.path === path &&
endpoint.method.toLowerCase() === method.toLowerCase()
);
}
getEndpointsByPath(path: string): SwaggerEndpoint[] {
return this.endpoints.filter((endpoint) => endpoint.path === path);
}
getEndpointsByMethod(method: string): SwaggerEndpoint[] {
return this.endpoints.filter(
(endpoint) => endpoint.method.toLowerCase() === method.toLowerCase()
);
}
searchEndpoints(query: string): SwaggerEndpoint[] {
const lowerQuery = query.toLowerCase();
return this.endpoints.filter(
(endpoint) =>
endpoint.path.toLowerCase().includes(lowerQuery) ||
endpoint.summary?.toLowerCase().includes(lowerQuery) ||
endpoint.description?.toLowerCase().includes(lowerQuery)
);
}
getEndpointSummary(): string {
if (!this.parsed) {
return "Documentation not loaded";
}
const summary = this.endpoints
.map(
(endpoint) =>
`${endpoint.method} ${endpoint.path}${
endpoint.summary ? ` - ${endpoint.summary}` : ""
}`
)
.join("\n");
return `Available endpoints (${this.endpoints.length}):\n${summary}`;
}
}