// Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { IfAny, IfEmptyObject, IfNever, IfUnknown, IsLiteral, LiteralToPrimitive, Primitive, } from 'type-fest'; import type { SafeParseReturnType, ZodError, ZodType, ZodTypeDef } from 'zod'; type Schema = ZodType; type SafeResult = SafeParseReturnType; type LooseInput = IsLiteral extends true ? LiteralToPrimitive : Record; type PartialInput = T extends Primitive ? T | null | void : { [Key in keyof T]?: T[Key] | null | void }; export class SchemaParseError extends TypeError { constructor(schema: Schema, error: ZodError) { let message = 'zod: issues found when parsing with schema'; if (schema.description) { message += ` (${schema.description})`; } message += ':'; for (const issue of error.issues) { message += `\n - ${issue.path.join('.')}: ${issue.message}`; } super(message); } } function parse( schema: Schema, input: unknown ): Output { const result = schema.safeParse(input); if (result.success) { return result.data; } throw new SchemaParseError(schema, result.error); } function safeParse( schema: Schema, input: unknown ): SafeResult { return schema.safeParse(input); } /** * This uses type-fest to validate that the data being passed into parse() and * safeParse() is not types like `any`, `{}`, `never`, or an unexpected `unknown`. * * `never` is hard to prevent from being passed in, so instead we make the function * arguments themselves not constructable using an intersection with a warning. */ // Must be exactly `unknown` type UnknownArgs = IfAny extends true ? [data: Data] & 'Unexpected input `any` must be `unknown`' : IfNever extends true ? [data: Data] & 'Unexpected input `never` must be `unknown`' : IfEmptyObject extends true ? [data: Data] & 'Unexpected input `{}` must be `unknown`' : IfUnknown extends true ? [data: Data] : [data: Data] & 'Unexpected input type must be `unknown`'; type TypedArgs = IfAny extends true ? [data: Data] & 'Unexpected input `any` must be typed' : IfNever extends true ? [data: Data] & 'Unexpected input `never` must be typed' : IfEmptyObject extends true ? [data: Data] & 'Unexpected input `{}` must be typed' : IfUnknown extends true ? [data: Data] & 'Unexpected input `unknown` must be typed' : [data: Data]; // prettier-ignore type ParseUnknown = (schema: Schema, ...args: UnknownArgs) => Output; // prettier-ignore type SafeParseUnknown = (schema: Schema, ...args: UnknownArgs) => SafeResult; // prettier-ignore type ParseStrict = (schema: Schema, ...args: TypedArgs) => Output; // prettier-ignore type SafeParseStrict = (schema: Schema, ...args: TypedArgs) => SafeResult; // prettier-ignore type ParseLoose = >(schema: Schema, ...args: TypedArgs) => Output; // prettier-ignore type SafeParseLoose = >(schema: Schema, ...args: TypedArgs) => SafeResult; // prettier-ignore type ParsePartial = >(schema: Schema, ...args: TypedArgs) => Output; // prettier-ignore type SafeParsePartial = >(schema: Schema, ...args: TypedArgs) => SafeResult; /** * Parse an *unknown* value with a zod schema. * ```ts * type Input = unknown // unknown * type Output = { prop: string } * ``` * @throws {SchemaParseError} */ export const parseUnknown: ParseUnknown = parse; /** * Safely parse an *unknown* value with a zod schema. * ```ts * type Input = unknown // unknown * type Output = { success: true, error: null, data: { prop: string } } * ``` */ export const safeParseUnknown: SafeParseUnknown = safeParse; /** * Parse a *strict* value with a zod schema. * ```ts * type Input = { prop: string } // strict * type Output = { prop: string } * ``` * @throws {SchemaParseError} */ export const parseStrict: ParseStrict = parse; /** * Safely parse a *strict* value with a zod schema. * ```ts * type Input = { prop: string } // strict * type Output = { success: true, error: null, data: { prop: string } } * ``` */ export const safeParseStrict: SafeParseStrict = safeParse; /** * Parse a *loose* value with a zod schema. * ```ts * type Input = { prop: unknown } // loose * type Output = { prop: string } * ``` * @throws {SchemaParseError} */ export const parseLoose: ParseLoose = parse; /** * Safely parse a *loose* value with a zod schema. * ```ts * type Input = { prop: unknown } // loose * type Output = { success: true, error: null, data: { prop: string } } * ``` */ export const safeParseLoose: SafeParseLoose = safeParse; /** * Parse a *partial* value with a zod schema. * ```ts * type Input = { prop?: string | null | undefined } // partial * type Output = { prop: string } * ``` * @throws {SchemaParseError} */ export const parsePartial: ParsePartial = parse; /** * Safely parse a *partial* value with a zod schema. * ```ts * type Input = { prop?: string | null | undefined } // partial * type Output = { success: true, error: null, data: { prop: string } } * ``` */ export const safeParsePartial: SafeParsePartial = safeParse;