UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.

347 lines (346 loc) 12.5 kB
/** * Schema Validation Middleware * * Task: P2-3.2 - JSON Schema Validation Enforcement * Provides Express middleware for automatic schema validation * * Usage: * ```typescript * app.post('/api/pattern-deployment', * validateRequest('database-handoffs/pattern-deployment', '1.0.0'), * handlePatternDeployment * ); * ``` * * @module schema-validation */ import { IntegrationSchemaValidator } from '../lib/integration-schema-validator.js'; import { StandardError, ErrorCode } from '../lib/errors.js'; import { getGlobalLogger } from '../lib/logging.js'; import path from 'path'; const logger = getGlobalLogger(); // ============================================================================ // Global Validator Instance // ============================================================================ let globalValidator = null; /** * Initialize global validator instance */ export async function initializeSchemaValidation(schemaPath) { const resolvedPath = schemaPath || path.join(process.cwd(), 'schemas/integration-points'); globalValidator = new IntegrationSchemaValidator({ schemaPath: resolvedPath, enableCache: true, strictMode: true }); await globalValidator.initialize(); logger.info('Schema validation middleware initialized', { schemaPath: resolvedPath }); } /** * Get global validator instance */ function getValidator() { if (!globalValidator) { throw new StandardError(ErrorCode.CONFIGURATION_ERROR, 'Schema validator not initialized. Call initializeSchemaValidation() first.', { initialized: false }); } return globalValidator; } // ============================================================================ // Middleware Functions // ============================================================================ /** * Validate request body against schema * * @param schemaId - Schema identifier (e.g., "database-handoffs/pattern-deployment") * @param version - Schema version (optional, defaults to latest) * @param options - Validation options */ export function validateRequest(schemaId, version, options = {}) { return async (req, res, next)=>{ const startTime = Date.now(); try { const validator = getValidator(); // Validate request body await validator.validate(req.body, schemaId, version); const duration = Date.now() - startTime; logger.debug('Request validation succeeded', { schemaId, version, path: req.path, method: req.method, duration }); // Store validated data in request for downstream use if (options.attachValidatedData) { req.validatedData = req.body; } next(); } catch (error) { const duration = Date.now() - startTime; logger.warn('Request validation failed', { schemaId, version, path: req.path, method: req.method, duration, error: error instanceof Error ? error.message : String(error) }); if (error instanceof StandardError) { res.status(400).json({ error: 'Validation Failed', message: error.message, code: error.code, details: error.context?.errors || [], suggestions: error.context?.suggestions || [] }); } else { res.status(500).json({ error: 'Internal Server Error', message: 'Schema validation error' }); } } }; } /** * Validate response body against schema * * @param schemaId - Schema identifier * @param version - Schema version (optional) */ export function validateResponse(schemaId, version) { return async (req, res, next)=>{ const originalSend = res.send; const originalJson = res.json; // Intercept res.send() res.send = function(data) { validateResponseData(data, schemaId, version, req, res); return originalSend.call(this, data); }; // Intercept res.json() res.json = function(data) { validateResponseData(data, schemaId, version, req, res); return originalJson.call(this, data); }; next(); }; } /** * Validate response data helper */ async function validateResponseData(data, schemaId, version, req, res) { const startTime = Date.now(); try { const validator = getValidator(); // Only validate if response is successful (2xx) if (res.statusCode >= 200 && res.statusCode < 300) { await validator.validate(data, schemaId, version); const duration = Date.now() - startTime; logger.debug('Response validation succeeded', { schemaId, version, path: req.path, statusCode: res.statusCode, duration }); } } catch (error) { const duration = Date.now() - startTime; logger.error('Response validation failed', error, { schemaId, version, path: req.path, statusCode: res.statusCode, duration }); // Log error but don't modify response // Response validation failures are logged for monitoring } } /** * Validate both request and response */ export function validateRequestResponse(requestSchemaId, responseSchemaId, requestVersion, responseVersion) { return [ validateRequest(requestSchemaId, requestVersion), validateResponse(responseSchemaId, responseVersion) ]; } /** * Create validation middleware with custom error handler */ export function createValidationMiddleware(options) { return async (req, res, next)=>{ const startTime = Date.now(); try { const validator = getValidator(); // Determine schema based on request path or custom function let schemaId; let version; if (options.schemaResolver) { const resolved = options.schemaResolver(req); schemaId = resolved.schemaId; version = resolved.version; } else if (options.schemaId) { schemaId = options.schemaId; version = options.version; } else { throw new StandardError(ErrorCode.CONFIGURATION_ERROR, 'No schema resolver or schemaId provided', { options }); } // Validate request body await validator.validate(req.body, schemaId, version); const duration = Date.now() - startTime; logger.debug('Validation middleware succeeded', { schemaId, version, path: req.path, duration }); next(); } catch (error) { const duration = Date.now() - startTime; logger.warn('Validation middleware failed', { path: req.path, duration, error: error instanceof Error ? error.message : String(error) }); if (options.errorHandler) { options.errorHandler(error, req, res, next); } else { // Default error handler if (error instanceof StandardError) { res.status(400).json({ error: 'Validation Failed', message: error.message, code: error.code, details: error.context?.errors || [] }); } else { res.status(500).json({ error: 'Internal Server Error', message: 'Validation error' }); } } } }; } /** * Validate query parameters against schema */ export function validateQueryParams(schemaId, version) { return async (req, res, next)=>{ try { const validator = getValidator(); // Validate query parameters await validator.validate(req.query, schemaId, version); logger.debug('Query parameter validation succeeded', { schemaId, version, path: req.path }); next(); } catch (error) { logger.warn('Query parameter validation failed', { schemaId, version, path: req.path, error: error instanceof Error ? error.message : String(error) }); if (error instanceof StandardError) { res.status(400).json({ error: 'Invalid Query Parameters', message: error.message, details: error.context?.errors || [] }); } else { res.status(500).json({ error: 'Internal Server Error' }); } } }; } /** * Batch validation middleware * Validates an array of items in request body */ export function validateBatch(schemaId, version, options = {}) { return async (req, res, next)=>{ try { const validator = getValidator(); // Ensure request body is an array if (!Array.isArray(req.body)) { throw new StandardError(ErrorCode.VALIDATION_FAILED, 'Request body must be an array for batch validation', { bodyType: typeof req.body }); } // Validate batch const result = await validator.validateBatch(req.body, schemaId, version, { failFast: options.failFast }); if (!result.valid) { res.status(400).json({ error: 'Batch Validation Failed', message: `${result.invalidRecords} of ${result.totalRecords} records failed validation`, totalRecords: result.totalRecords, validRecords: result.validRecords, invalidRecords: result.invalidRecords, errors: result.errors }); return; } logger.debug('Batch validation succeeded', { schemaId, version, totalRecords: result.totalRecords }); next(); } catch (error) { if (error instanceof StandardError) { res.status(400).json({ error: 'Batch Validation Error', message: error.message, code: error.code }); } else { res.status(500).json({ error: 'Internal Server Error' }); } } }; } // ============================================================================ // Utility Functions // ============================================================================ /** * Create schema resolver based on route patterns * * Example: * ```typescript * const resolver = createRouteSchemaResolver({ * '/api/patterns': 'database-handoffs/pattern-deployment', * '/api/metrics': 'database-handoffs/execution-metrics', * }); * ``` */ export function createRouteSchemaResolver(routeMap) { return (req)=>{ const schemaId = routeMap[req.path]; if (!schemaId) { throw new StandardError(ErrorCode.CONFIGURATION_ERROR, `No schema mapping found for route: ${req.path}`, { path: req.path, availableRoutes: Object.keys(routeMap) }); } return { schemaId }; }; } /** * Shutdown schema validation middleware */ export async function shutdownSchemaValidation() { if (globalValidator) { await globalValidator.shutdown(); globalValidator = null; logger.info('Schema validation middleware shutdown complete'); } } //# sourceMappingURL=schema-validation.js.map