UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

360 lines 14.2 kB
/** * Validation Middleware for Orchestration * * Integrates comprehensive validation at template creation and pre-execution stages */ import { TemplateEntityValidator } from '../../validation/TemplateEntityValidator.js'; import { TemplateValidator } from '../utils/TemplateValidator.js'; import { DirectTemplateValidator } from '../validators/DirectTemplateValidator.js'; import { SchemaCache } from '../../validation/SchemaCache.js'; import { getLogger } from '../../logging/Logger.js'; // Create a singleton schema cache instance for all validation const sharedSchemaCache = new SchemaCache({ ttl: 3600000, // 1 hour maxSize: 100, preloadAll: true }); export class ValidationMiddleware { logger = getLogger(); structureValidator = new TemplateValidator(); entityValidator; directTemplateValidator = new DirectTemplateValidator(); constructor() { // Pass shared cache to entity validator this.entityValidator = new TemplateEntityValidator(sharedSchemaCache); } /** * Validate template at creation time */ async validateTemplateCreation(template, options = {}) { const { validateStructure = true, validateEntities = true, platform } = options; this.logger.info({ templateId: template.id, templateName: template.name, validationOptions: options }, 'Validating template at creation'); const report = { valid: true, summary: '' }; // 1. Validate template structure if (validateStructure) { const structureResult = await this.structureValidator.validateTemplate(template); report.structureValidation = structureResult; if (!structureResult.valid) { report.valid = false; } } // 2. Validate entity data against API schemas if (validateEntities) { const entityResult = await this.entityValidator.validateTemplate(template); report.entityValidation = entityResult; if (!entityResult.valid) { report.valid = false; } } // Generate summary report.summary = this.generateSummary(report); this.logger.info({ templateId: template.id, valid: report.valid, structureErrors: report.structureValidation?.errors.length || 0, entityErrors: report.entityValidation?.errors.length || 0 }, 'Template validation complete'); return report; } /** * Validate before execution with resolved parameters */ async validateBeforeExecution(template, parameters, options = {}) { this.logger.info({ templateId: template.id, templateName: template.name, parameters: Object.keys(parameters) }, 'Validating template before execution'); const report = { valid: true, summary: '' }; // 1. Validate parameters match template expectations const paramResult = await this.structureValidator.validateParameters(template.parameters, parameters); if (!paramResult.valid) { report.valid = false; report.structureValidation = { valid: false, errors: paramResult.errors, warnings: paramResult.warnings }; } // 2. Validate all entities with resolved parameters const entityResult = await this.entityValidator.validateBeforeExecution(template, parameters); report.entityValidation = entityResult; if (!entityResult.valid) { report.valid = false; } // Generate summary report.summary = this.generateSummary(report); return report; } /** * Validate orchestration template with direct ModelFriendlyTemplate format */ async validateDirectTemplate(template, options = {}) { const report = { valid: true, summary: '' }; // 1. Validate structure if (options.validateStructure !== false) { const structureValidation = await this.validateTemplateStructure(template); report.structureValidation = structureValidation; if (!structureValidation.valid) { report.valid = false; } } // 2. Validate entities using direct template validator if (options.validateDirectTemplate !== false && template.steps) { const directValidation = this.validateDirectTemplateSteps(template, options); report.directTemplateValidation = directValidation; if (!directValidation.valid) { report.valid = false; } } // 3. Validate entities using existing validator if (options.validateEntities !== false) { const entityValidation = await this.validateTemplateEntities(template, options); report.entityValidation = entityValidation; if (!entityValidation.valid) { report.valid = false; } } // 4. Generate summary report.summary = this.generateSummary(report); return report; } validateDirectTemplateSteps(template, options) { const allErrors = []; const allWarnings = []; const allSuggestions = []; if (!template.steps) { return { valid: true, errors: [], warnings: [], suggestions: [] }; } for (const step of template.steps) { const result = this.directTemplateValidator.validateStep(step, { platform: options.platform, validateModelFriendly: true }); allErrors.push(...result.errors); allWarnings.push(...result.warnings); allSuggestions.push(...result.suggestions); } return { valid: allErrors.length === 0, errors: allErrors, warnings: allWarnings, suggestions: allSuggestions }; } async validateTemplateStructure(template) { // Use existing structure validator return await this.structureValidator.validateTemplate(template); } async validateTemplateEntities(template, options) { // Use existing entity validator return await this.entityValidator.validateTemplate(template); } /** * Generate human-readable summary */ generateSummary(report) { const lines = []; if (report.valid) { lines.push('✅ Validation passed!'); } else { lines.push('❌ Validation failed!'); } // Structure validation summary if (report.structureValidation && !report.structureValidation.valid) { const errors = report.structureValidation.errors || []; if (errors.length > 0) { lines.push(`\nStructure Errors (${errors.length}):`); errors.slice(0, 3).forEach((err) => lines.push(` - ${err}`)); if (errors.length > 3) { lines.push(` ... and ${errors.length - 3} more`); } } } // Direct template validation summary if (report.directTemplateValidation) { const { errors, warnings } = report.directTemplateValidation; if (errors.length > 0) { lines.push(`\nDirect Template Errors (${errors.length}):`); errors.slice(0, 3).forEach(err => lines.push(` - ${err}`)); if (errors.length > 3) { lines.push(` ... and ${errors.length - 3} more`); } } if (warnings.length > 0) { lines.push(`\nDirect Template Warnings (${warnings.length}):`); warnings.slice(0, 2).forEach(warn => lines.push(` ⚠️ ${warn}`)); if (warnings.length > 2) { lines.push(` ... and ${warnings.length - 2} more`); } } } if (report.structureValidation) { const { errors, warnings } = report.structureValidation; if (errors.length > 0) { lines.push(`\nStructure Errors (${errors.length}):`); errors.slice(0, 3).forEach(err => lines.push(` - ${err}`)); if (errors.length > 3) { lines.push(` ... and ${errors.length - 3} more`); } } if (warnings.length > 0) { lines.push(`\nStructure Warnings (${warnings.length}):`); warnings.slice(0, 2).forEach(warn => lines.push(` ⚠️ ${warn}`)); if (warnings.length > 2) { lines.push(` ... and ${warnings.length - 2} more`); } } } if (report.entityValidation) { const { errors, warnings } = report.entityValidation; if (errors.length > 0) { lines.push(`\nEntity Validation Errors (${errors.length}):`); errors.slice(0, 3).forEach(err => { lines.push(` - Step ${err.stepId} (${err.entityType}):`); err.errors.slice(0, 2).forEach(e => lines.push(` • ${e}`)); if (err.errors.length > 2) { lines.push(` ... and ${err.errors.length - 2} more`); } }); if (errors.length > 3) { lines.push(` ... and ${errors.length - 3} more steps with errors`); } } if (warnings.length > 0) { lines.push(`\nEntity Validation Warnings (${warnings.length}):`); warnings.slice(0, 2).forEach(warn => { lines.push(` - Step ${warn.stepId}: ${warn.warnings[0]}`); }); if (warnings.length > 2) { lines.push(` ... and ${warnings.length - 2} more warnings`); } } } return lines.join('\n'); } /** * Format validation errors for user display */ formatErrorsForDisplay(report) { if (report.valid) { return 'No validation errors found.'; } const lines = ['Validation Errors:', '']; // Structure errors if (report.structureValidation && !report.structureValidation.valid) { lines.push('Template Structure Issues:'); report.structureValidation.errors.forEach((error, index) => { lines.push(`${index + 1}. ${error}`); }); lines.push(''); } // Entity errors if (report.entityValidation && !report.entityValidation.valid) { lines.push('Entity Validation Issues:'); report.entityValidation.errors.forEach((stepError, index) => { lines.push(`${index + 1}. Step "${stepError.stepId}" (${stepError.entityType}):`); stepError.errors.forEach(err => { lines.push(` - ${err}`); }); }); } return lines.join('\n'); } /** * Get actionable fixes for common errors */ getSuggestedFixes(report) { const fixes = []; if (!report.entityValidation) return fixes; for (const error of report.entityValidation.errors) { for (const err of error.errors) { // Event category fixes if (err.includes("Invalid category: 'conversion'")) { fixes.push("Change category from 'conversion' to 'convert' or another valid value"); } // Platform fixes if (err.includes("'feature_experimentation'")) { fixes.push("Change platform from 'feature_experimentation' to 'custom'"); } // Missing required field fixes const requiredMatch = err.match(/Missing required field '(\w+)'/); if (requiredMatch) { fixes.push(`Add required field '${requiredMatch[1]}' to step ${error.stepId}`); } // Invalid enum fixes const enumMatch = err.match(/Invalid (\w+): '([^']+)'\. Valid values are: ([^.]+)/); if (enumMatch) { const [, field, value, validValues] = enumMatch; const values = validValues.split(', '); const suggestion = this.findClosestMatch(value, values); if (suggestion) { fixes.push(`Change ${field} from '${value}' to '${suggestion}'`); } } } } return [...new Set(fixes)]; // Remove duplicates } /** * Find closest match for typos */ findClosestMatch(value, validValues) { const lowerValue = value.toLowerCase(); // Exact case-insensitive match const exactMatch = validValues.find(v => v.toLowerCase() === lowerValue); if (exactMatch) return exactMatch; // Partial match const partialMatch = validValues.find(v => v.toLowerCase().includes(lowerValue) || lowerValue.includes(v.toLowerCase())); if (partialMatch) return partialMatch; return null; } /** * Get cache statistics */ getCacheStats() { return sharedSchemaCache.getStats(); } /** * Clear schema cache */ clearSchemaCache() { sharedSchemaCache.clearAll(); this.logger.info('Schema cache cleared'); } /** * Get cache memory usage */ getCacheMemoryUsage() { return sharedSchemaCache.getMemoryUsage(); } /** * Clean up expired cache entries */ cleanupCache() { return sharedSchemaCache.cleanup(); } } //# sourceMappingURL=ValidationMiddleware.js.map