Skip to content

Commit 7980cef

Browse files
authored
Merge pull request #17 from DouglasNeuroInformatics/dev
feat: add $$Function helper to replicate Zod v3 behaviour
2 parents 5f77921 + 7cb78b5 commit 7980cef

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

src/__tests__/zod.test.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,17 @@ import { describe, expect, expectTypeOf, it, test } from 'vitest';
33
import { z as z3 } from 'zod/v3';
44
import { z as z4 } from 'zod/v4';
55

6-
import { $BooleanLike, $NumberLike, $Uint8ArrayLike, $UrlLike, isZodType, isZodTypeLike, safeParse } from '../zod.js';
6+
import {
7+
$$Function,
8+
$AnyFunction,
9+
$BooleanLike,
10+
$NumberLike,
11+
$Uint8ArrayLike,
12+
$UrlLike,
13+
isZodType,
14+
isZodTypeLike,
15+
safeParse
16+
} from '../zod.js';
717

818
import type {
919
ZodErrorLike,
@@ -216,6 +226,41 @@ describe('$Uint8ArrayLike', () => {
216226
});
217227
});
218228

229+
describe('$AnyFunction', () => {
230+
it('should be correctly typed', () => {
231+
expectTypeOf<z4.infer<typeof $AnyFunction>>().toEqualTypeOf<(...args: any[]) => any>();
232+
});
233+
it('should fail to validate a non-function', () => {
234+
expect($AnyFunction.safeParse('').success).toBe(false);
235+
});
236+
it('should validate a function', () => {
237+
expect($AnyFunction.safeParse(safeParse).success).toBe(true);
238+
});
239+
});
240+
241+
describe('$$Function', () => {
242+
const $Schema = $$Function({ input: [z4.number(), z4.number()], output: z4.number() });
243+
it('should be correctly typed', () => {
244+
expectTypeOf<z4.infer<typeof $Schema>>().toEqualTypeOf<(arg_0: number, arg_1: number) => number>();
245+
});
246+
it('should fail to validate a non-function', () => {
247+
expect($Schema.safeParse('').success).toBe(false);
248+
});
249+
it('should return a function that throws when called with the incorrect number of arguments', () => {
250+
const fn = $Schema.parse((..._args: any[]) => 0) as (...args: any[]) => number;
251+
expect(() => fn(1)).toThrow();
252+
expect(() => fn(1, 2, 3)).toThrow();
253+
});
254+
it('should return a function that throws when it returns an invalid value', () => {
255+
const fn = $Schema.parse((..._args: any[]) => 'hello') as (...args: any[]) => any;
256+
expect(() => fn(1, 2)).toThrow();
257+
});
258+
it('should return a function that returns a valid value, when called with valid inputs', () => {
259+
const fn = $Schema.parse((a: number, b: number) => a + b);
260+
expect(fn(1, 2)).toBe(3);
261+
});
262+
});
263+
219264
describe('safeParse', () => {
220265
const $Schema = z4.object({ foo: z4.enum(['1', '2']).transform(Number) });
221266
it('should return an Ok result with the parsed data if successful', () => {

src/zod.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,28 @@ export const $Uint8ArrayLike: z4.ZodType<Uint8Array> = z4
119119
return arg;
120120
});
121121

122+
export const $AnyFunction = z4.custom<(...args: any[]) => any>((arg) => typeof arg === 'function', 'must be function');
123+
124+
export const $$Function = <TInput extends [z4.ZodType, ...z4.ZodType[]], TOutput extends z4.ZodType>({
125+
input,
126+
output
127+
}: {
128+
input: TInput;
129+
output: TOutput;
130+
}): z4.ZodType<(...args: z4.output<z4.ZodTuple<TInput, null>>) => z4.output<TOutput>> => {
131+
const $Schema = z4.function({
132+
input: z4.tuple(input),
133+
output
134+
});
135+
return z4.custom().transform((arg, ctx) => {
136+
if (typeof arg !== 'function') {
137+
ctx.addIssue('Must be function');
138+
return z4.NEVER;
139+
}
140+
return $Schema.implement(arg as (...args: any[]) => any);
141+
});
142+
};
143+
122144
export function safeParse<TSchema extends z4.ZodTypeAny>(
123145
data: unknown,
124146
$Schema: TSchema

0 commit comments

Comments
 (0)