validation-helpers.ts•4.5 kB
/**
 * Validation helpers for handler arguments
 */
import { HandlerError, ToolArguments } from './base-handler.js';
export interface ValidationRule<T = any> {
  field: string;
  required?: boolean;
  type?: 'string' | 'number' | 'boolean' | 'object' | 'array';
  validator?: (value: T) => boolean;
  errorMessage?: string;
}
export class ValidationHelpers {
  /**
   * Validate arguments against a set of rules
   */
  static validateArguments(
    args: ToolArguments,
    rules: ValidationRule[],
    toolName: string,
  ): void {
    for (const rule of rules) {
      const value = args?.[rule.field];
      // Check required fields
      if (rule.required && (value === undefined || value === null || value === '')) {
        throw new HandlerError(
          rule.errorMessage ?? `${rule.field} is required`,
          toolName,
          'MISSING_ARGUMENT',
          { field: rule.field, rules },
        );
      }
      // Skip type and custom validation if value is not present and not required
      if (!rule.required && (value === undefined || value === null)) {
        continue;
      }
      // Check type (including for required fields that have values)
      if (rule.type && value !== undefined && value !== null && !this.validateType(value, rule.type)) {
        throw new HandlerError(
          rule.errorMessage ?? `${rule.field} must be of type ${rule.type}`,
          toolName,
          'INVALID_TYPE',
          { field: rule.field, expectedType: rule.type, actualType: typeof value },
        );
      }
      // Custom validation
      if (rule.validator && !rule.validator(value)) {
        throw new HandlerError(
          rule.errorMessage ?? `${rule.field} validation failed`,
          toolName,
          'VALIDATION_FAILED',
          { field: rule.field, value },
        );
      }
    }
  }
  /**
   * Quick validation for required string fields
   * @deprecated Use CommonValidations.requiredString() with validateArguments() instead
   * @example
   * // Old way
   * ValidationHelpers.requireStrings(args, ['fieldName'], 'tool_name');
   *
   * // New way
   * ValidationHelpers.validateArguments(args, CommonValidations.requiredString('fieldName'), 'tool_name');
   */
  static requireStrings(args: ToolArguments, fields: string[], toolName: string): void {
    const rules: ValidationRule[] = fields.map(field => ({
      field,
      required: true,
      type: 'string' as const,
      errorMessage: `${field} is required for ${toolName}`,
    }));
    this.validateArguments(args, rules, toolName);
  }
  /**
   * Validate a single field with custom validator
   */
  static validateField<T>(
    args: ToolArguments,
    field: string,
    validator: (value: T) => boolean,
    errorMessage: string,
    toolName: string,
  ): void {
    const rules: ValidationRule[] = [{
      field,
      validator,
      errorMessage,
    }];
    this.validateArguments(args, rules, toolName);
  }
  private static validateType(value: any, type: ValidationRule['type']): boolean {
    switch (type) {
      case 'string':
        return typeof value === 'string';
      case 'number':
        return typeof value === 'number' && !isNaN(value);
      case 'boolean':
        return typeof value === 'boolean';
      case 'object':
        return typeof value === 'object' && value !== null && !Array.isArray(value);
      case 'array':
        return Array.isArray(value);
      default:
        return true;
    }
  }
}
/**
 * Common validation rules factory
 */
export const CommonValidations = {
  /**
   * Create a required string field validation
   */
  requiredString: (field: string, customMessage?: string): ValidationRule[] => [{
    field,
    required: true,
    type: 'string',
    validator: (value: string) => value.trim().length > 0,
    errorMessage: customMessage ?? `${field} must be a non-empty string`,
  }],
  /**
   * Create a required field validation with custom validator
   */
  requiredField: (
    field: string,
    type: ValidationRule['type'],
    validator: (value: any) => boolean,
    errorMessage: string,
  ): ValidationRule[] => [{
    field,
    required: true,
    type,
    validator,
    errorMessage,
  }],
  /**
   * Create an optional field validation
   */
  optionalField: (
    field: string,
    type: ValidationRule['type'],
    validator: (value: any) => boolean,
    errorMessage: string,
  ): ValidationRule[] => [{
    field,
    required: false,
    type,
    validator,
    errorMessage,
  }],
};