import fs from "fs"; import path from "path"; import Ajv2020, { type ErrorObject, type ValidateFunction } from "ajv/dist/2020"; import { SCHEMAS_DIR } from "../config"; import type { NormalizedPayload, ValidationResult } from "../types/normalizer"; type SchemaVersion = "v1" | "v2" | "v2_0_1" | "v2_0_2"; const validators = new Map(); function schemaPath(version: SchemaVersion): string { if (version === "v1") { return path.resolve(SCHEMAS_DIR, "normalized_query_v1.json"); } if (version === "v2_0_1") { return path.resolve(SCHEMAS_DIR, "normalized_query_v2_0_1.json"); } if (version === "v2_0_2") { return path.resolve(SCHEMAS_DIR, "normalized_query_v2_0_2.json"); } return path.resolve(SCHEMAS_DIR, "normalized_query_v2.json"); } function loadValidator(version: SchemaVersion): ValidateFunction { const cached = validators.get(version); if (cached) { return cached; } const raw = fs.readFileSync(schemaPath(version), "utf-8"); const schema = JSON.parse(raw); const ajv = new Ajv2020({ allErrors: true, strict: false }); const compiled = ajv.compile(schema); validators.set(version, compiled); return compiled; } function normalizeAjvErrors(errors: ErrorObject[] | null | undefined): string[] { if (!errors || errors.length === 0) { return []; } return errors.map((item) => `${item.instancePath || "/"} ${item.message ?? "validation error"}`.trim()); } export function validateNormalized(payload: unknown, schemaVersion: SchemaVersion = "v1"): ValidationResult { const check = loadValidator(schemaVersion); const passed = check(payload); return { passed: Boolean(passed), errors: passed ? [] : normalizeAjvErrors(check.errors) }; } export function assertNormalized(payload: unknown, schemaVersion: SchemaVersion = "v1"): NormalizedPayload { const validation = validateNormalized(payload, schemaVersion); if (!validation.passed) { throw new Error(`Invalid normalized JSON: ${validation.errors.join("; ")}`); } return payload as NormalizedPayload; }