UNPKG

meld

Version:

Meld: A template language for LLM prompts

165 lines (146 loc) 6.49 kB
import type { DirectiveNode } from 'meld-spec'; import { validationLogger as logger } from '@core/utils/logger.js'; import { IValidationService } from './IValidationService.js'; import { MeldDirectiveError } from '@core/errors/MeldDirectiveError.js'; import { DirectiveErrorCode } from '@services/pipeline/DirectiveService/errors/DirectiveError.js'; import { ErrorSeverity } from '@core/errors/MeldError.js'; // Import default validators import { validateTextDirective } from './validators/TextDirectiveValidator.js'; import { validateDataDirective } from './validators/DataDirectiveValidator.js'; import { validateImportDirective } from './validators/ImportDirectiveValidator.js'; import { validateEmbedDirective } from './validators/EmbedDirectiveValidator.js'; import { validatePathDirective } from './validators/PathDirectiveValidator.js'; import { validateDefineDirective } from './validators/DefineDirectiveValidator.js'; import { validateRunDirective } from './validators/RunDirectiveValidator.js'; /** * Map of directive error codes to severity levels */ export const ValidationErrorSeverity: Record<DirectiveErrorCode, ErrorSeverity> = { [DirectiveErrorCode.VALIDATION_FAILED]: ErrorSeverity.Recoverable, [DirectiveErrorCode.RESOLUTION_FAILED]: ErrorSeverity.Recoverable, [DirectiveErrorCode.EXECUTION_FAILED]: ErrorSeverity.Recoverable, [DirectiveErrorCode.HANDLER_NOT_FOUND]: ErrorSeverity.Fatal, [DirectiveErrorCode.FILE_NOT_FOUND]: ErrorSeverity.Recoverable, [DirectiveErrorCode.CIRCULAR_REFERENCE]: ErrorSeverity.Fatal, [DirectiveErrorCode.VARIABLE_NOT_FOUND]: ErrorSeverity.Recoverable, [DirectiveErrorCode.STATE_ERROR]: ErrorSeverity.Fatal, [DirectiveErrorCode.INVALID_CONTEXT]: ErrorSeverity.Fatal, [DirectiveErrorCode.SECTION_NOT_FOUND]: ErrorSeverity.Recoverable }; export class ValidationService implements IValidationService { private validators = new Map<string, (node: DirectiveNode) => Promise<void>>(); constructor() { // Register default validators this.registerValidator('text', async (node) => validateTextDirective(node)); this.registerValidator('data', async (node) => validateDataDirective(node)); this.registerValidator('import', async (node) => validateImportDirective(node)); this.registerValidator('embed', async (node) => validateEmbedDirective(node)); this.registerValidator('path', async (node) => validatePathDirective(node)); this.registerValidator('define', async (node) => validateDefineDirective(node)); this.registerValidator('run', async (node) => validateRunDirective(node)); logger.debug('ValidationService initialized with default validators', { validators: Array.from(this.validators.keys()) }); } /** * Validate a directive node against its schema and constraints * @throws {MeldDirectiveError} If validation fails */ async validate(node: DirectiveNode): Promise<void> { logger.debug('Validating directive', { kind: node.directive.kind, location: node.location }); const validator = this.validators.get(node.directive.kind); if (!validator) { throw new MeldDirectiveError( `Unknown directive kind: ${node.directive.kind}`, node.directive.kind, { location: node.location?.start, code: DirectiveErrorCode.HANDLER_NOT_FOUND, severity: ValidationErrorSeverity[DirectiveErrorCode.HANDLER_NOT_FOUND] } ); } try { await validator(node); } catch (error) { // If it's already a MeldDirectiveError, just rethrow it if (error instanceof MeldDirectiveError) { throw error; } // Determine the error code and severity let code = DirectiveErrorCode.VALIDATION_FAILED; let severity = ValidationErrorSeverity[code]; // Check for specific error messages to classify them const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('required') || errorMessage.includes('missing')) { // Missing required fields are recoverable code = DirectiveErrorCode.VALIDATION_FAILED; severity = ValidationErrorSeverity[code]; } else if (errorMessage.includes('invalid format') || errorMessage.includes('must be')) { // Format validation errors are recoverable code = DirectiveErrorCode.VALIDATION_FAILED; severity = ValidationErrorSeverity[code]; } else if (errorMessage.includes('not found') || errorMessage.includes('does not exist')) { // Not found errors are recoverable code = DirectiveErrorCode.FILE_NOT_FOUND; severity = ValidationErrorSeverity[code]; } else if (errorMessage.includes('circular')) { // Circular reference errors are fatal code = DirectiveErrorCode.CIRCULAR_REFERENCE; severity = ValidationErrorSeverity[code]; } // Otherwise, wrap it in a MeldDirectiveError throw new MeldDirectiveError( errorMessage, node.directive.kind, { location: node.location?.start, code, cause: error instanceof Error ? error : undefined, severity } ); } logger.debug('Directive validation successful', { kind: node.directive.kind }); } /** * Register a validator for a directive kind */ registerValidator(kind: string, validator: (node: DirectiveNode) => Promise<void>): void { if (!kind || typeof kind !== 'string') { throw new Error('Validator kind must be a non-empty string'); } if (typeof validator !== 'function') { throw new Error('Validator must be a function'); } if (this.validators.has(kind)) { logger.warn(`Overriding existing validator for directive kind: ${kind}`); } this.validators.set(kind, validator); logger.debug(`Registered validator for directive kind: ${kind}`); } /** * Remove a validator for a directive kind */ removeValidator(kind: string): void { this.validators.delete(kind); logger.debug(`Removed validator for directive kind: ${kind}`); } /** * Check if a validator exists for a directive kind */ hasValidator(kind: string): boolean { return this.validators.has(kind); } /** * Get all registered directive kinds */ getRegisteredDirectiveKinds(): string[] { return Array.from(this.validators.keys()); } }