Skip to main content
Glama
SchemaConverter.ts6.81 kB
import { z } from 'zod'; import { logger } from '../utils/logger'; export class SchemaConverter { private refs: Map<string, any> = new Map(); constructor(definitions?: Record<string, any>) { // Store definitions for $ref resolution if (definitions) { for (const [name, schema] of Object.entries(definitions)) { this.refs.set(`#/definitions/${name}`, schema); this.refs.set(`#/components/schemas/${name}`, schema); } } } // Convert OpenAPI schema to Zod schema convert(schema: any): z.ZodType<any> { if (!schema) { return z.any(); } // Handle $ref if (schema.$ref) { const resolved = this.resolveRef(schema.$ref); if (resolved) { return this.convert(resolved); } logger.warn(`Unable to resolve $ref: ${schema.$ref}`); return z.any(); } // Handle combined schemas if (schema.allOf) { return this.convertAllOf(schema.allOf); } if (schema.oneOf) { return this.convertOneOf(schema.oneOf); } if (schema.anyOf) { return this.convertAnyOf(schema.anyOf); } // Handle basic types const type = schema.type; switch (type) { case 'string': return this.convertString(schema); case 'number': return this.convertNumber(schema); case 'integer': return this.convertInteger(schema); case 'boolean': return z.boolean(); case 'array': return this.convertArray(schema); case 'object': return this.convertObject(schema); case 'null': return z.null(); default: // If no type is specified but properties exist, assume object if (schema.properties) { return this.convertObject(schema); } return z.any(); } } private convertString(schema: any): z.ZodType<any> { let zodSchema = z.string(); if (schema.format) { switch (schema.format) { case 'email': zodSchema = z.string().email(); break; case 'uri': case 'url': zodSchema = z.string().url(); break; case 'uuid': zodSchema = z.string().uuid(); break; case 'date': case 'date-time': zodSchema = z.string().datetime(); break; } } if (schema.minLength !== undefined) { zodSchema = zodSchema.min(schema.minLength); } if (schema.maxLength !== undefined) { zodSchema = zodSchema.max(schema.maxLength); } if (schema.pattern) { zodSchema = zodSchema.regex(new RegExp(schema.pattern)); } if (schema.enum) { return z.enum(schema.enum); } return zodSchema; } private convertNumber(schema: any): z.ZodType<any> { let zodSchema = z.number(); if (schema.minimum !== undefined) { zodSchema = schema.exclusiveMinimum ? zodSchema.gt(schema.minimum) : zodSchema.gte(schema.minimum); } if (schema.maximum !== undefined) { zodSchema = schema.exclusiveMaximum ? zodSchema.lt(schema.maximum) : zodSchema.lte(schema.maximum); } if (schema.multipleOf !== undefined) { zodSchema = zodSchema.multipleOf(schema.multipleOf); } return zodSchema; } private convertInteger(schema: any): z.ZodType<any> { let zodSchema = z.number().int(); if (schema.minimum !== undefined) { zodSchema = schema.exclusiveMinimum ? zodSchema.gt(schema.minimum) : zodSchema.gte(schema.minimum); } if (schema.maximum !== undefined) { zodSchema = schema.exclusiveMaximum ? zodSchema.lt(schema.maximum) : zodSchema.lte(schema.maximum); } return zodSchema; } private convertArray(schema: any): z.ZodType<any> { const itemsSchema = schema.items ? this.convert(schema.items) : z.any(); let zodSchema: z.ZodType<any> = z.array(itemsSchema); if (schema.minItems !== undefined) { zodSchema = (zodSchema as z.ZodArray<any>).min(schema.minItems); } if (schema.maxItems !== undefined) { zodSchema = (zodSchema as z.ZodArray<any>).max(schema.maxItems); } if (schema.uniqueItems) { // Zod doesn't have built-in unique validation, so we add a custom refinement // This will return a ZodEffects type, which is still a ZodType zodSchema = zodSchema.refine( (items) => new Set(items).size === items.length, { message: 'Array must contain unique items' } ); } return zodSchema; } private convertObject(schema: any): z.ZodType<any> { const properties = schema.properties || {}; const required = schema.required || []; const shape: Record<string, z.ZodType<any>> = {}; for (const [key, propSchema] of Object.entries(properties)) { let zodProp = this.convert(propSchema); // Make property optional if not in required array if (!required.includes(key)) { zodProp = zodProp.optional(); } shape[key] = zodProp; } let zodSchema: z.ZodType<any> = z.object(shape); // Handle additionalProperties if (schema.additionalProperties === true) { zodSchema = z.object(shape).passthrough(); } else if (schema.additionalProperties === false) { zodSchema = z.object(shape).strict(); } else if (typeof schema.additionalProperties === 'object') { // Zod doesn't support typed additional properties directly // We'll use passthrough and add a custom refinement const additionalSchema = this.convert(schema.additionalProperties); zodSchema = z.object(shape).passthrough(); } return zodSchema; } private convertAllOf(schemas: any[]): z.ZodType<any> { // For allOf, we need to merge all schemas // Zod doesn't have a direct allOf, so we use intersection const zodSchemas = schemas.map(s => this.convert(s)); if (zodSchemas.length === 0) { return z.any(); } if (zodSchemas.length === 1) { return zodSchemas[0]; } // Use intersection for multiple schemas return zodSchemas.reduce((acc, schema) => z.intersection(acc, schema)); } private convertOneOf(schemas: any[]): z.ZodType<any> { const zodSchemas = schemas.map(s => this.convert(s)); if (zodSchemas.length === 0) { return z.any(); } if (zodSchemas.length === 1) { return zodSchemas[0]; } // Use discriminated union if possible, otherwise use union return z.union(zodSchemas as [z.ZodType<any>, z.ZodType<any>, ...z.ZodType<any>[]]); } private convertAnyOf(schemas: any[]): z.ZodType<any> { // anyOf is similar to oneOf in Zod context return this.convertOneOf(schemas); } private resolveRef(ref: string): any { return this.refs.get(ref); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/NakiriYuuzu/SwaggerMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server