swagger-tools/src/utils/format.ts
rimskij 58c91615b9 feat: add OpenAPI 3.1 TypeScript generation support
- Handle type arrays: type: ['string', 'null'] → string | null
- Handle const keyword: const: "active" → 'active' literal type
- Handle nullable (OpenAPI 3.0 backward compatibility)
- Extract and display webhook count in metadata
- Add security escaping for string literals and JSDoc comments
- Add OpenAPI 3.1 test fixture and 12 unit tests

Fixes #365

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 21:20:13 +01:00

149 lines
3.8 KiB
TypeScript

import type { EndpointInfo, ParsedSpec, ValidationResult, SchemaInfo } from '../lib/types.js';
export function formatMetadata(metadata: ParsedSpec): string {
const lines = [
`# ${metadata.title}`,
`Version: ${metadata.version}`,
];
if (metadata.description) {
lines.push(`Description: ${metadata.description}`);
}
lines.push('');
lines.push(`## Statistics`);
lines.push(`- Paths: ${metadata.pathCount}`);
lines.push(`- Operations: ${metadata.operationCount}`);
lines.push(`- Schemas: ${metadata.schemaCount}`);
if (metadata.webhookCount !== undefined && metadata.webhookCount > 0) {
lines.push(`- Webhooks: ${metadata.webhookCount}`);
}
if (metadata.servers.length > 0) {
lines.push('');
lines.push(`## Servers`);
metadata.servers.forEach(s => lines.push(`- ${s}`));
}
if (metadata.tags.length > 0) {
lines.push('');
lines.push(`## Tags`);
metadata.tags.forEach(t => lines.push(`- ${t}`));
}
return lines.join('\n');
}
export function formatValidation(result: ValidationResult): string {
const lines: string[] = [];
if (result.valid) {
lines.push('Validation: PASSED');
} else {
lines.push('Validation: FAILED');
}
if (result.errors.length > 0) {
lines.push('');
lines.push('## Errors');
for (const err of result.errors) {
const path = err.path ? `[${err.path}] ` : '';
lines.push(`- ${path}${err.message}`);
}
}
if (result.warnings.length > 0) {
lines.push('');
lines.push('## Warnings');
for (const warn of result.warnings) {
const path = warn.path ? `[${warn.path}] ` : '';
lines.push(`- ${path}${warn.message}`);
}
}
return lines.join('\n');
}
export function formatEndpoints(endpoints: EndpointInfo[]): string {
if (endpoints.length === 0) {
return 'No endpoints found matching criteria.';
}
const lines = [`Found ${endpoints.length} endpoint(s):`, ''];
for (const ep of endpoints) {
lines.push(`## ${ep.method.toUpperCase()} ${ep.path}`);
if (ep.operationId) {
lines.push(`Operation ID: ${ep.operationId}`);
}
if (ep.summary) {
lines.push(`Summary: ${ep.summary}`);
}
if (ep.tags.length > 0) {
lines.push(`Tags: ${ep.tags.join(', ')}`);
}
if (ep.parameters.length > 0) {
lines.push('');
lines.push('### Parameters');
for (const param of ep.parameters) {
const required = param.required ? ' (required)' : '';
lines.push(`- **${param.name}** [${param.in}]${required}`);
if (param.description) {
lines.push(` ${param.description}`);
}
}
}
if (ep.requestBody) {
lines.push('');
lines.push('### Request Body');
const required = ep.requestBody.required ? ' (required)' : '';
lines.push(`Content types${required}: ${ep.requestBody.contentTypes.join(', ')}`);
if (ep.requestBody.description) {
lines.push(ep.requestBody.description);
}
}
if (ep.responses.length > 0) {
lines.push('');
lines.push('### Responses');
for (const resp of ep.responses) {
lines.push(`- **${resp.statusCode}**: ${resp.description || 'No description'}`);
}
}
lines.push('');
}
return lines.join('\n');
}
export function formatSchema(schema: SchemaInfo): string {
const lines = [
`# ${schema.name}`,
];
if (schema.description) {
lines.push(schema.description);
}
if (schema.type) {
lines.push(`Type: ${schema.type}`);
}
lines.push('');
lines.push('## Schema');
lines.push('```json');
lines.push(JSON.stringify(schema.schema, null, 2));
lines.push('```');
return lines.join('\n');
}
export function formatTypes(types: string): string {
return ['## Generated TypeScript Types', '', '```typescript', types, '```'].join('\n');
}