@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
360 lines • 14.2 kB
JavaScript
/**
* 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