mcp-swagger-parser
Version:
Enterprise-grade OpenAPI/Swagger specification parser for Model Context Protocol (MCP) projects
259 lines • 10.7 kB
JavaScript
;
/**
* Core OpenAPI parser
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAPIParser = void 0;
exports.parseFromUrl = parseFromUrl;
exports.parseFromFile = parseFromFile;
exports.parseFromString = parseFromString;
exports.validate = validate;
const index_1 = require("../errors/index");
const url_parser_1 = require("../parsers/url-parser");
const file_parser_1 = require("../parsers/file-parser");
const text_parser_1 = require("../parsers/text-parser");
const validator_1 = require("./validator");
const normalizer_1 = require("./normalizer");
const version_detector_1 = require("./version-detector");
const swagger2openapi_converter_1 = require("./swagger2openapi-converter");
/**
* Default parser configuration
*/
const DEFAULT_CONFIG = {
validateSchema: true,
resolveReferences: true,
allowEmptyPaths: false,
strictMode: false,
customValidators: [],
autoConvert: true,
autoFix: true,
swagger2Options: {
patch: true,
warnOnly: false,
resolveInternal: false,
targetVersion: '3.0.0',
preserveRefs: true,
warnProperty: 'x-s2o-warning',
debug: false
}
};
/**
* Main OpenAPI parser class
*/
class OpenAPIParser {
constructor(config = {}) {
this.config = { ...DEFAULT_CONFIG, ...config };
// Initialize sub-parsers
this.urlParser = new url_parser_1.UrlParser();
this.fileParser = new file_parser_1.FileParser();
this.textParser = new text_parser_1.TextParser();
this.validator = new validator_1.Validator(this.config);
this.normalizer = new normalizer_1.Normalizer(this.config);
// Initialize Swagger 2.0 converter
this.swagger2Converter = new swagger2openapi_converter_1.Swagger2OpenAPIConverter({
patch: this.config.swagger2Options.patch,
warnOnly: this.config.swagger2Options.warnOnly,
resolveInternal: this.config.swagger2Options.resolveInternal,
targetVersion: this.config.swagger2Options.targetVersion,
preserveRefs: this.config.swagger2Options.preserveRefs,
warnProperty: this.config.swagger2Options.warnProperty,
debug: this.config.swagger2Options.debug
});
}
/**
* Parse OpenAPI specification from a URL
*/
async parseFromUrl(url, options) {
try {
const startTime = Date.now();
// Parse spec from URL
const spec = await this.urlParser.parse(url, options);
// Process the parsed spec
return await this.processSpec(spec, {
sourceType: 'url',
sourceLocation: url,
parsedAt: new Date(),
parsingDuration: Date.now() - startTime
});
}
catch (error) {
throw this.handleError(error, 'parseFromUrl', { url });
}
}
/**
* Parse OpenAPI specification from a file
*/
async parseFromFile(filePath, options) {
try {
const startTime = Date.now();
// Parse spec from file
const spec = await this.fileParser.parse(filePath, options);
// Process the parsed spec
return await this.processSpec(spec, {
sourceType: 'file',
sourceLocation: filePath,
parsedAt: new Date(),
parsingDuration: Date.now() - startTime
});
}
catch (error) {
throw this.handleError(error, 'parseFromFile', { filePath });
}
}
/**
* Parse OpenAPI specification from text content
*/
async parseFromString(content, options) {
try {
const startTime = Date.now();
const sourceInfo = options?.filename || 'string';
// Parse spec from text
const spec = await this.textParser.parse(content, options);
// Process the parsed spec
return await this.processSpec(spec, {
sourceType: 'text',
sourceLocation: sourceInfo,
parsedAt: new Date(),
parsingDuration: Date.now() - startTime
});
}
catch (error) {
throw this.handleError(error, 'parseFromString', { content: content.substring(0, 100) + '...' });
}
}
/**
* Validate an OpenAPI specification
*/
async validate(spec) {
return await this.validator.validate(spec);
}
/**
* Process and validate the parsed specification
*/
async processSpec(spec, metadata) {
let processedSpec = spec;
let conversionResult = null;
// Detect API specification version
if (this.config.autoConvert) {
const version = version_detector_1.VersionDetector.detect(spec);
if (version === 'swagger2') {
console.log('检测到 Swagger 2.0 规范,正在转换为 OpenAPI 3.0...');
try {
conversionResult = await this.swagger2Converter.convert(spec);
processedSpec = conversionResult.openapi;
// Update metadata with conversion info
metadata.conversionPerformed = true;
metadata.originalVersion = conversionResult.originalVersion;
metadata.targetVersion = conversionResult.targetVersion;
metadata.conversionDuration = conversionResult.duration;
metadata.patchesApplied = conversionResult.patches;
metadata.conversionWarnings = conversionResult.warnings;
console.log(`✓ 转换完成: ${metadata.originalVersion} -> ${metadata.targetVersion} (${metadata.conversionDuration}ms)`);
if (conversionResult.patches && conversionResult.patches > 0) {
console.log(`✓ 应用了 ${conversionResult.patches} 个补丁修复`);
}
if (conversionResult.warnings && conversionResult.warnings.length > 0) {
console.log(`⚠ 转换过程中产生了 ${conversionResult.warnings.length} 个警告`);
}
}
catch (error) {
if (error instanceof index_1.Swagger2OpenAPIConversionError || error instanceof index_1.UnsupportedVersionError) {
throw error;
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
throw new index_1.Swagger2OpenAPIConversionError(`Failed to convert Swagger 2.0 specification: ${errorMessage}`, error instanceof Error ? error : new Error(String(error)));
}
}
else if (version === 'unknown') {
throw new index_1.UnsupportedVersionError(spec.swagger || spec.openapi || 'unknown');
}
// If version is 'openapi3', no conversion needed
}
// Normalize the specification
if (this.config.resolveReferences) {
processedSpec = await this.normalizer.normalize(processedSpec);
}
// Validate the specification
const validation = await this.validate(processedSpec);
if (!validation.valid && this.config.strictMode) {
throw new index_1.OpenAPIValidationError('Specification validation failed', validation.errors, validation.warnings);
}
// Generate complete metadata
const completeMetadata = this.generateMetadata(processedSpec, metadata);
// Create parsed spec
const parsedSpec = {
...processedSpec,
metadata: completeMetadata
};
return {
spec: parsedSpec,
validation,
metadata: completeMetadata
};
}
/**
* Generate metadata for the parsed specification
*/
generateMetadata(spec, partial) {
const pathCount = spec.paths ? Object.keys(spec.paths).length : 0;
let operationCount = 0;
if (spec.paths) {
for (const pathItem of Object.values(spec.paths)) {
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace'];
operationCount += methods.filter(method => pathItem[method]).length;
}
}
const schemaCount = spec.components?.schemas ? Object.keys(spec.components.schemas).length : 0;
const securitySchemeCount = spec.components?.securitySchemes ? Object.keys(spec.components.securitySchemes).length : 0;
return {
sourceType: partial.sourceType || 'text',
sourceLocation: partial.sourceLocation || 'unknown',
parsedAt: partial.parsedAt || new Date(),
parsingDuration: partial.parsingDuration || 0,
endpointCount: operationCount,
pathCount,
schemaCount,
securitySchemeCount,
openApiVersion: spec.openapi,
parserVersion: '1.0.0', // TODO: Get from package.json
// Enhanced metadata
conversionPerformed: partial.conversionPerformed || false,
originalVersion: partial.originalVersion,
targetVersion: partial.targetVersion,
conversionDuration: partial.conversionDuration,
patchesApplied: partial.patchesApplied,
conversionWarnings: partial.conversionWarnings
};
}
/**
* Handle and transform errors
*/
handleError(error, method, context) {
if (error instanceof index_1.OpenAPIParseError || error instanceof index_1.OpenAPIValidationError) {
return error;
}
const message = error instanceof Error ? error.message : String(error);
return new index_1.OpenAPIParseError(`Failed in ${method}: ${message}`, index_1.ERROR_CODES.PARSE_ERROR, undefined, error instanceof Error ? error : undefined);
}
}
exports.OpenAPIParser = OpenAPIParser;
/**
* Convenience functions for quick parsing
*/
async function parseFromUrl(url, config) {
const parser = new OpenAPIParser(config);
return parser.parseFromUrl(url);
}
async function parseFromFile(filePath, config) {
const parser = new OpenAPIParser(config);
return parser.parseFromFile(filePath);
}
async function parseFromString(content, config) {
const parser = new OpenAPIParser(config);
return parser.parseFromString(content);
}
async function validate(spec, config) {
const parser = new OpenAPIParser(config);
return parser.validate(spec);
}
//# sourceMappingURL=parser.js.map