import type { StandardSchemaV1 } from "@standard-schema/spec" /** * Validates a value through a possibly asynchronous Standard Schema. * * @param schema - Standard Schema validator to invoke. * @param value - Unknown value to validate. * @param label - Label included in validation errors. * @throws When validation reports one or more issues. */ export async function validateStandardSchema( schema: TSchema, value: unknown, label: string, ): Promise> { return parseStandardSchemaResult( await schema["~standard"].validate(value), label, ) } /** * Validates a value through a synchronous Standard Schema. * * @param schema - Standard Schema validator to invoke. * @param value - Unknown value to validate. * @param label - Label included in validation errors. * @throws When validation is asynchronous or reports issues. */ export function validateStandardSchemaSync( schema: TSchema, value: unknown, label: string, ): StandardSchemaV1.InferOutput { const result = schema["~standard"].validate(value) if (isPromiseLike(result)) { throw new TypeError(`${label} schemas must validate synchronously.`) } return parseStandardSchemaResult(result, label) } /** * Converts a Standard Schema result into its value or the public error shape. * * @param result - Completed Standard Schema validation result. * @param label - Label included in validation errors. * @throws When validation reports one or more issues. */ function parseStandardSchemaResult( result: StandardSchemaV1.Result, label: string, ): TOutput { if (result.issues) { const issues = result.issues.map((issue) => ({ message: issue.message, path: issue.path?.map((segment) => typeof segment === "object" ? String(segment.key) : String(segment), ) ?? [], })) const summary = issues .map(({ message, path }) => path.length > 0 ? `${path.join(".")}: ${message}` : message, ) .join("; ") throw Object.assign( new Error( summary.length > 0 ? `${label} validation failed: ${summary}` : `${label} validation failed.`, ), { code: "validation_failed", details: { issues }, name: "SchemaValidationError", }, ) } return result.value } /** * Checks for promises and other PromiseLike validation results. * * @param value - Value returned by a Standard Schema validator. */ function isPromiseLike(value: unknown): value is PromiseLike { return ( (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function" ) }