superaugment
Version:
Enterprise-grade MCP server with world-class C++ analysis, robust error handling, and production-ready architecture for VS Code Augment
434 lines • 14.9 kB
JavaScript
/**
* SuperAugment Schema Converter
*
* Provides robust and maintainable conversion between Zod schemas and JSON Schema
* for MCP protocol compatibility. Designed to be simple, reliable, and extensible.
*/
import { z } from 'zod';
import { logger } from './logger.js';
import { ErrorCode, ErrorSeverity, SuperAugmentError, } from '../errors/ErrorTypes.js';
/**
* Default conversion options
*/
const DEFAULT_OPTIONS = {
includeDescriptions: true,
includeDefaults: true,
strictMode: false,
maxDepth: 10,
};
/**
* Robust Schema Converter for Zod to JSON Schema conversion
*/
export class SchemaConverter {
conversionCache = new Map();
stats = {
totalProperties: 0,
successfulConversions: 0,
failedConversions: 0,
unsupportedTypes: [],
conversionTime: 0,
};
/**
* Convert Zod schema to JSON Schema
*/
convertZodToJsonSchema(zodSchema, options = {}) {
const startTime = Date.now();
const opts = { ...DEFAULT_OPTIONS, ...options };
try {
this.resetStats();
const result = this.convertZodSchemaInternal(zodSchema, opts, 0);
this.stats.conversionTime = Date.now() - startTime;
logger.debug('Schema conversion completed', {
stats: this.stats,
options: opts,
});
return result;
}
catch (error) {
this.stats.conversionTime = Date.now() - startTime;
this.stats.failedConversions++;
throw new SuperAugmentError(`Schema conversion failed: ${error instanceof Error ? error.message : 'Unknown error'}`, ErrorCode.VALIDATION_FAILED, ErrorSeverity.HIGH, { additionalInfo: { stats: this.stats, options: opts } }, false, error instanceof Error ? error : undefined);
}
}
/**
* Convert Zod type to JSON Schema property
*/
convertZodTypeToProperty(zodType, options = {}) {
const opts = { ...DEFAULT_OPTIONS, ...options };
return this.convertZodTypeInternal(zodType, opts, 0);
}
/**
* Get conversion statistics
*/
getStats() {
return { ...this.stats };
}
/**
* Clear conversion cache
*/
clearCache() {
this.conversionCache.clear();
logger.debug('Schema conversion cache cleared');
}
/**
* Internal schema conversion implementation
*/
convertZodSchemaInternal(zodSchema, options, depth) {
if (depth > options.maxDepth) {
throw new SuperAugmentError(`Maximum conversion depth exceeded: ${options.maxDepth}`, ErrorCode.VALIDATION_FAILED, ErrorSeverity.HIGH, { additionalInfo: { depth, maxDepth: options.maxDepth } }, false);
}
// Handle ZodObject specifically
if (zodSchema instanceof z.ZodObject) {
return this.convertZodObject(zodSchema, options, depth);
}
// Handle other schema types that can be converted to objects
const property = this.convertZodTypeInternal(zodSchema, options, depth);
if (property.type === 'object' && property.properties) {
const result = {
type: 'object',
properties: property.properties,
additionalProperties: false,
};
if (property.required) {
result.required = property.required;
}
return result;
}
// Fallback: wrap non-object types in a generic object
return {
type: 'object',
properties: {
value: property,
},
required: ['value'],
additionalProperties: false,
};
}
/**
* Convert ZodObject to JSON Schema
*/
convertZodObject(zodObject, options, depth) {
const properties = {};
const required = [];
const shape = zodObject.shape;
for (const [key, zodType] of Object.entries(shape)) {
try {
this.stats.totalProperties++;
const property = this.convertZodTypeInternal(zodType, options, depth + 1);
properties[key] = property;
// Check if property is required (not optional or has default)
if (this.isRequiredProperty(zodType)) {
required.push(key);
}
this.stats.successfulConversions++;
}
catch (error) {
this.stats.failedConversions++;
logger.warn(`Failed to convert property '${key}'`, { error });
if (options.strictMode) {
throw error;
}
// Fallback property in non-strict mode
properties[key] = {
type: 'string',
description: `Failed to convert property: ${error instanceof Error ? error.message : 'Unknown error'}`,
};
}
}
const result = {
type: 'object',
properties,
additionalProperties: false,
};
if (required.length > 0) {
result.required = required;
}
return result;
}
/**
* Convert individual Zod type to JSON Schema property
*/
convertZodTypeInternal(zodType, options, depth) {
if (depth > options.maxDepth) {
return {
type: 'string',
description: 'Maximum depth exceeded',
};
}
// Check cache first
const cacheKey = this.getCacheKey(zodType);
if (this.conversionCache.has(cacheKey)) {
return this.conversionCache.get(cacheKey);
}
const property = this.convertZodTypeCore(zodType, options, depth);
// Cache the result
this.conversionCache.set(cacheKey, property);
return property;
}
/**
* Core Zod type conversion logic
*/
convertZodTypeCore(zodType, options, depth) {
const typeName = zodType._def?.typeName;
switch (typeName) {
case 'ZodString':
return this.convertZodString(zodType, options);
case 'ZodNumber':
return this.convertZodNumber(zodType, options);
case 'ZodBoolean':
return this.convertZodBoolean(zodType, options);
case 'ZodArray':
return this.convertZodArray(zodType, options, depth);
case 'ZodObject':
return this.convertZodObjectToProperty(zodType, options, depth);
case 'ZodEnum':
return this.convertZodEnum(zodType, options);
case 'ZodNativeEnum':
return this.convertZodNativeEnum(zodType, options);
case 'ZodLiteral':
return this.convertZodLiteral(zodType, options);
case 'ZodUnion':
return this.convertZodUnion(zodType, options, depth);
case 'ZodOptional':
return this.convertZodOptional(zodType, options, depth);
case 'ZodDefault':
return this.convertZodDefault(zodType, options, depth);
case 'ZodNullable':
return this.convertZodNullable(zodType, options, depth);
case 'ZodRecord':
return this.convertZodRecord(zodType, options, depth);
default:
this.stats.unsupportedTypes.push(typeName || 'unknown');
logger.warn(`Unsupported Zod type: ${typeName}`, { typeName });
const result = {
type: 'string',
};
if (options.includeDescriptions) {
result.description = `Unsupported type: ${typeName}`;
}
return result;
}
}
/**
* Convert ZodString
*/
convertZodString(zodString, options) {
const property = { type: 'string' };
if (options.includeDescriptions && zodString.description) {
property.description = zodString.description;
}
// Extract string constraints
const checks = zodString._def.checks || [];
for (const check of checks) {
switch (check.kind) {
case 'min':
property.minLength = check.value;
break;
case 'max':
property.maxLength = check.value;
break;
case 'regex':
property.pattern = check.regex.source;
break;
case 'email':
property.format = 'email';
break;
case 'url':
property.format = 'uri';
break;
case 'uuid':
property.format = 'uuid';
break;
}
}
return property;
}
/**
* Convert ZodNumber
*/
convertZodNumber(zodNumber, options) {
const property = { type: 'number' };
if (options.includeDescriptions && zodNumber.description) {
property.description = zodNumber.description;
}
// Extract number constraints
const checks = zodNumber._def.checks || [];
for (const check of checks) {
switch (check.kind) {
case 'min':
property.minimum = check.value;
break;
case 'max':
property.maximum = check.value;
break;
case 'int':
property.type = 'integer';
break;
}
}
return property;
}
/**
* Convert ZodBoolean
*/
convertZodBoolean(zodBoolean, options) {
const property = { type: 'boolean' };
if (options.includeDescriptions && zodBoolean.description) {
property.description = zodBoolean.description;
}
return property;
}
/**
* Convert ZodArray
*/
convertZodArray(zodArray, options, depth) {
const property = {
type: 'array',
items: this.convertZodTypeInternal(zodArray.element, options, depth + 1),
};
if (options.includeDescriptions && zodArray.description) {
property.description = zodArray.description;
}
return property;
}
/**
* Convert ZodObject to property
*/
convertZodObjectToProperty(zodObject, options, depth) {
const objectSchema = this.convertZodObject(zodObject, options, depth);
const result = {
type: 'object',
properties: objectSchema.properties,
};
if (objectSchema.required) {
result.required = objectSchema.required;
}
return result;
}
/**
* Convert ZodEnum
*/
convertZodEnum(zodEnum, options) {
const property = {
type: 'string',
enum: zodEnum.options,
};
if (options.includeDescriptions && zodEnum.description) {
property.description = zodEnum.description;
}
return property;
}
/**
* Convert ZodNativeEnum
*/
convertZodNativeEnum(zodNativeEnum, options) {
const enumValues = Object.values(zodNativeEnum.enum);
const property = {
type: 'string',
enum: enumValues,
};
if (options.includeDescriptions && zodNativeEnum.description) {
property.description = zodNativeEnum.description;
}
return property;
}
/**
* Convert ZodLiteral
*/
convertZodLiteral(zodLiteral, options) {
const value = zodLiteral.value;
const property = {
type: typeof value,
enum: [value],
};
if (options.includeDescriptions && zodLiteral.description) {
property.description = zodLiteral.description;
}
return property;
}
/**
* Convert ZodUnion (simplified - takes first option)
*/
convertZodUnion(zodUnion, options, depth) {
// For simplicity, convert the first option
// In a more sophisticated implementation, this could create a oneOf schema
const firstOption = zodUnion.options[0];
const property = this.convertZodTypeInternal(firstOption, options, depth + 1);
if (options.includeDescriptions) {
property.description = `Union type (simplified to first option)${zodUnion.description ? ': ' + zodUnion.description : ''}`;
}
return property;
}
/**
* Convert ZodOptional
*/
convertZodOptional(zodOptional, options, depth) {
return this.convertZodTypeInternal(zodOptional.unwrap(), options, depth);
}
/**
* Convert ZodDefault
*/
convertZodDefault(zodDefault, options, depth) {
const property = this.convertZodTypeInternal(zodDefault._def.innerType, options, depth);
if (options.includeDefaults) {
property.default = zodDefault._def.defaultValue();
}
return property;
}
/**
* Convert ZodNullable
*/
convertZodNullable(zodNullable, options, depth) {
const property = this.convertZodTypeInternal(zodNullable.unwrap(), options, depth);
if (options.includeDescriptions) {
property.description = `Nullable: ${property.description || 'No description'}`;
}
return property;
}
/**
* Convert ZodRecord
*/
convertZodRecord(zodRecord, options, _depth) {
const description = options.includeDescriptions
? `Record type${zodRecord.description ? ': ' + zodRecord.description : ''}`
: undefined;
return {
type: 'object',
...(description && { description }),
};
}
/**
* Check if a Zod type represents a required property
*/
isRequiredProperty(zodType) {
const typeName = zodType._def?.typeName;
return typeName !== 'ZodOptional' && typeName !== 'ZodDefault';
}
/**
* Generate cache key for Zod type
*/
getCacheKey(zodType) {
try {
// Simple cache key based on type name and basic properties
const typeName = zodType._def?.typeName || 'unknown';
const description = zodType.description || '';
return `${typeName}:${description}`;
}
catch {
return 'unknown';
}
}
/**
* Reset conversion statistics
*/
resetStats() {
this.stats = {
totalProperties: 0,
successfulConversions: 0,
failedConversions: 0,
unsupportedTypes: [],
conversionTime: 0,
};
}
}
//# sourceMappingURL=SchemaConverter.js.map