/** * Adapter Contract Validation * * Validates that an object implements the Adapter interface correctly. * Checks for required methods, optional methods, and proper signatures. */ import type { Adapter } from "@wavespec/types"; /** * Validation result * * Indicates whether validation passed and provides detailed error messages. */ export interface ValidationResult { /** Whether validation passed */ valid: boolean; /** Validation error messages (if any) */ errors: string[]; } /** * Adapter contract validator * * Validates that an object conforms to the Adapter interface contract. * Checks for required properties (name, version, run) and optional methods * (handshake, connect, disconnect, isConnected, preValidate, stream, reset). * * @param adapter - The object to validate as an Adapter * @returns Validation result with detailed error messages * * @example * ```typescript * const result = validateAdapterContract(myAdapter); * if (!result.valid) { * console.error("Adapter validation failed:"); * result.errors.forEach(err => console.error(` - ${err}`)); * } * ``` */ export function validateAdapterContract(adapter: unknown): ValidationResult { const errors: string[] = []; // Check if adapter is an object if (!adapter || typeof adapter !== "object") { return { valid: false, errors: ["Adapter must be an object"], }; } const obj = adapter as Record; // Check required property: name if (!("name" in obj)) { errors.push("Missing required property: name"); } else if (typeof obj["name"] !== "string") { errors.push("Property 'name' must be a string"); } else if (obj["name"].length === 0) { errors.push("Property 'name' cannot be empty"); } // Check required property: version if (!("version" in obj)) { errors.push("Missing required property: version"); } else if (typeof obj["version"] !== "string") { errors.push("Property 'version' must be a string"); } else if (obj["version"].length === 0) { errors.push("Property 'version' cannot be empty"); } // Check required method: run if (!("run" in obj)) { errors.push("Missing required method: run()"); } else if (typeof obj["run"] !== "function") { errors.push("Property 'run' must be a function"); } else { // Check run method signature (should accept 2 parameters: spec and ctx) const runFn = obj["run"] as Function; if (runFn.length < 2) { errors.push( `Method 'run' should accept 2 parameters (spec, ctx), but accepts ${runFn.length}`, ); } } // Check optional property: capabilities if ("capabilities" in obj) { if (typeof obj["capabilities"] !== "object" || obj["capabilities"] === null) { errors.push("Property 'capabilities' must be an object"); } else { const caps = obj["capabilities"] as Record; for (const [key, value] of Object.entries(caps)) { if (typeof value !== "boolean" && value !== undefined) { errors.push( `Capability '${key}' must be a boolean or undefined, got ${typeof value}`, ); } } } } // Check optional methods const optionalMethods: Array<{ name: string; paramCount: number }> = [ { name: "handshake", paramCount: 1 }, { name: "connect", paramCount: 2 }, { name: "disconnect", paramCount: 0 }, { name: "isConnected", paramCount: 0 }, { name: "preValidate", paramCount: 2 }, { name: "stream", paramCount: 2 }, { name: "reset", paramCount: 1 }, ]; for (const method of optionalMethods) { if (method.name in obj) { const value = obj[method.name]; if (typeof value !== "function") { errors.push(`Optional method '${method.name}' must be a function if provided`); } else { const fn = value as Function; if (fn.length < method.paramCount) { errors.push( `Optional method '${method.name}' should accept ${method.paramCount} parameter(s), but accepts ${fn.length}`, ); } } } } return { valid: errors.length === 0, errors, }; } /** * Type guard for Adapter interface * * Checks if an object implements the Adapter interface. * This is a runtime check that performs full contract validation. * * @param adapter - The object to check * @returns True if the object is a valid Adapter * * @example * ```typescript * if (isAdapter(obj)) { * // TypeScript now knows obj is an Adapter * await obj.run(spec, ctx); * } * ``` */ export function isAdapter(adapter: unknown): adapter is Adapter { const result = validateAdapterContract(adapter); return result.valid; }