import type { ErrorObject } from "ajv"; import Ajv2020 from "ajv/dist/2020"; import addFormats from "ajv-formats"; import type { StandardSchema } from "./types"; import { unknownInputSchema } from "./types"; const ajv = new Ajv2020({ allErrors: true, strict: false, validateSchema: false, allowUnionTypes: true, }); addFormats(ajv); const decodePointerSegment = (segment: string): PropertyKey => { const decoded = segment.replaceAll("~1", "/").replaceAll("~0", "~"); return /^\d+$/.test(decoded) ? Number(decoded) : decoded; }; const pointerToPath = (pointer: string | undefined): ReadonlyArray | undefined => { if (!pointer || pointer.length === 0 || pointer === "/") { return undefined; } return pointer .split("/") .slice(1) .filter((segment) => segment.length > 0) .map(decodePointerSegment); }; const toIssueMessage = (error: ErrorObject): string => { const keyword = error.keyword.trim(); // oxlint-disable-next-line executor/no-unknown-error-message -- typed AJV ErrorObject exposes optional validation message copy const message = (error.message ?? "Invalid value").trim(); return keyword.length > 0 ? `${keyword}: ${message}` : message; }; export const standardSchemaFromJsonSchema = ( schema: unknown, options?: { vendor?: string; fallback?: StandardSchema; }, ): StandardSchema => { // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: AJV compile throws for invalid schemas and this adapter preserves fallback behavior try { const validate = ajv.compile(schema as Record); return { "~standard": { version: 1, vendor: options?.vendor ?? "json-schema", validate: (value: unknown) => { const valid = validate(value); if (valid) { return { value }; } const issues = (validate.errors ?? []).map((error) => ({ message: toIssueMessage(error), path: pointerToPath(error.instancePath), })); return { issues: issues.length > 0 ? issues : [{ message: "Invalid value" }], }; }, }, }; } catch { return options?.fallback ?? unknownInputSchema; } };