mirror of https://github.com/cupcakearmy/zod.git
Add ZodPipeline
This commit is contained in:
parent
0ce88be33d
commit
bcef014180
|
@ -5,7 +5,7 @@ coverage
|
|||
.vscode
|
||||
.idea
|
||||
*.log
|
||||
src/playground.ts
|
||||
playground.ts
|
||||
deno/lib/playground.ts
|
||||
.eslintcache
|
||||
workspace.code-workspace
|
||||
|
|
|
@ -75,6 +75,8 @@ test("first party switch", () => {
|
|||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodBranded:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodPipeline:
|
||||
break;
|
||||
default:
|
||||
util.assertNever(def);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
// @ts-ignore TS6133
|
||||
import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts";
|
||||
const test = Deno.test;
|
||||
|
||||
import * as z from "../index.ts";
|
||||
|
||||
test("string to number pipeline", () => {
|
||||
const schema = z.string().transform(Number).pipe(z.number());
|
||||
expect(schema.parse("1234")).toEqual(1234);
|
||||
});
|
||||
|
||||
test("string to number pipeline async", async () => {
|
||||
const schema = z
|
||||
.string()
|
||||
.transform(async (val) => Number(val))
|
||||
.pipe(z.number());
|
||||
expect(await schema.parseAsync("1234")).toEqual(1234);
|
||||
});
|
||||
|
||||
test("break if dirty", () => {
|
||||
const schema = z
|
||||
.string()
|
||||
.refine((c) => c === "1234")
|
||||
.transform(async (val) => Number(val))
|
||||
.pipe(z.number().refine((v) => v < 100));
|
||||
const r1: any = schema.safeParse("12345");
|
||||
expect(r1.error.issues.length).toBe(1);
|
||||
const r2: any = schema.safeParse("3");
|
||||
expect(r2.error.issues.length).toBe(1);
|
||||
});
|
|
@ -371,6 +371,7 @@ export abstract class ZodType<
|
|||
this.default = this.default.bind(this);
|
||||
this.catch = this.catch.bind(this);
|
||||
this.describe = this.describe.bind(this);
|
||||
this.pipe = this.pipe.bind(this);
|
||||
this.isNullable = this.isNullable.bind(this);
|
||||
this.isOptional = this.isOptional.bind(this);
|
||||
}
|
||||
|
@ -448,6 +449,10 @@ export abstract class ZodType<
|
|||
});
|
||||
}
|
||||
|
||||
pipe<T extends ZodTypeAny>(target: T): ZodPipeline<this, T> {
|
||||
return ZodPipeline.create(this, target);
|
||||
}
|
||||
|
||||
isOptional(): boolean {
|
||||
return this.safeParse(undefined).success;
|
||||
}
|
||||
|
@ -1279,7 +1284,7 @@ export interface ZodAnyDef extends ZodTypeDef {
|
|||
|
||||
export class ZodAny extends ZodType<any, ZodAnyDef> {
|
||||
// to prevent instances of other classes from extending ZodAny. this causes issues with catchall in ZodObject.
|
||||
_any: true = true;
|
||||
_any = true as const;
|
||||
_parse(input: ParseInput): ParseReturnType<this["_output"]> {
|
||||
return OK(input.data);
|
||||
}
|
||||
|
@ -1304,7 +1309,7 @@ export interface ZodUnknownDef extends ZodTypeDef {
|
|||
|
||||
export class ZodUnknown extends ZodType<unknown, ZodUnknownDef> {
|
||||
// required
|
||||
_unknown: true = true;
|
||||
_unknown = true as const;
|
||||
_parse(input: ParseInput): ParseReturnType<this["_output"]> {
|
||||
return OK(input.data);
|
||||
}
|
||||
|
@ -3914,6 +3919,82 @@ export class ZodBranded<
|
|||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
////////////////////////////////////////////
|
||||
////////// //////////
|
||||
////////// ZodPipeline //////////
|
||||
////////// //////////
|
||||
////////////////////////////////////////////
|
||||
////////////////////////////////////////////
|
||||
|
||||
export interface ZodPipelineDef<A extends ZodTypeAny, B extends ZodTypeAny>
|
||||
extends ZodTypeDef {
|
||||
in: A;
|
||||
out: B;
|
||||
typeName: ZodFirstPartyTypeKind.ZodPipeline;
|
||||
}
|
||||
|
||||
export class ZodPipeline<
|
||||
A extends ZodTypeAny,
|
||||
B extends ZodTypeAny
|
||||
> extends ZodType<B["_output"], ZodPipelineDef<A, B>, A["_input"]> {
|
||||
_parse(input: ParseInput): ParseReturnType<any> {
|
||||
const { status, ctx } = this._processInputParams(input);
|
||||
if (ctx.common.async) {
|
||||
const handleAsync = async () => {
|
||||
const inResult = await this._def.in._parseAsync({
|
||||
data: ctx.data,
|
||||
path: ctx.path,
|
||||
parent: ctx,
|
||||
});
|
||||
if (inResult.status === "aborted") return INVALID;
|
||||
if (inResult.status === "dirty") {
|
||||
status.dirty();
|
||||
return DIRTY(inResult.value);
|
||||
} else {
|
||||
return this._def.out._parseAsync({
|
||||
data: inResult.value,
|
||||
path: ctx.path,
|
||||
parent: ctx,
|
||||
});
|
||||
}
|
||||
};
|
||||
return handleAsync();
|
||||
} else {
|
||||
const inResult = this._def.in._parseSync({
|
||||
data: ctx.data,
|
||||
path: ctx.path,
|
||||
parent: ctx,
|
||||
});
|
||||
if (inResult.status === "aborted") return INVALID;
|
||||
if (inResult.status === "dirty") {
|
||||
status.dirty();
|
||||
return {
|
||||
status: "dirty",
|
||||
value: inResult.value,
|
||||
};
|
||||
} else {
|
||||
return this._def.out._parseSync({
|
||||
data: inResult.value,
|
||||
path: ctx.path,
|
||||
parent: ctx,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static create<A extends ZodTypeAny, B extends ZodTypeAny>(
|
||||
a: A,
|
||||
b: B
|
||||
): ZodPipeline<A, B> {
|
||||
return new ZodPipeline({
|
||||
in: a,
|
||||
out: b,
|
||||
typeName: ZodFirstPartyTypeKind.ZodPipeline,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const custom = <T>(
|
||||
check?: (data: unknown) => any,
|
||||
params: Parameters<ZodTypeAny["refine"]>[1] = {},
|
||||
|
@ -3970,6 +4051,7 @@ export enum ZodFirstPartyTypeKind {
|
|||
ZodCatch = "ZodCatch",
|
||||
ZodPromise = "ZodPromise",
|
||||
ZodBranded = "ZodBranded",
|
||||
ZodPipeline = "ZodPipeline",
|
||||
}
|
||||
export type ZodFirstPartySchemaTypes =
|
||||
| ZodString
|
||||
|
@ -4004,7 +4086,8 @@ export type ZodFirstPartySchemaTypes =
|
|||
| ZodDefault<any>
|
||||
| ZodCatch<any>
|
||||
| ZodPromise<any>
|
||||
| ZodBranded<any, any>;
|
||||
| ZodBranded<any, any>
|
||||
| ZodPipeline<any, any>;
|
||||
|
||||
// new approach that works for abstract classes
|
||||
// but required TS 4.4+
|
||||
|
@ -4051,6 +4134,7 @@ const effectsType = ZodEffects.create;
|
|||
const optionalType = ZodOptional.create;
|
||||
const nullableType = ZodNullable.create;
|
||||
const preprocessType = ZodEffects.createWithPreprocess;
|
||||
const pipelineType = ZodPipeline.create;
|
||||
const ostring = () => stringType().optional();
|
||||
const onumber = () => numberType().optional();
|
||||
const oboolean = () => booleanType().optional();
|
||||
|
@ -4081,6 +4165,7 @@ export {
|
|||
onumber,
|
||||
optionalType as optional,
|
||||
ostring,
|
||||
pipelineType as pipeline,
|
||||
preprocessType as preprocess,
|
||||
promiseType as promise,
|
||||
recordType as record,
|
||||
|
|
|
@ -4,22 +4,6 @@
|
|||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"testRegex": "src/.*\\.test\\.ts$",
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
],
|
||||
"coverageReporters": [
|
||||
"json-summary",
|
||||
"text",
|
||||
"lcov"
|
||||
],
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"tsconfig": "tsconfig.json"
|
||||
}
|
||||
}
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||
"coverageReporters": ["json-summary", "text", "lcov"]
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
"test": "jest --coverage",
|
||||
"test:deno": "cd deno && deno test",
|
||||
"prepublishOnly": "npm run test && npm run build && npm run build:deno",
|
||||
"play": "nodemon -e ts -w . -x tsx src/playground.ts",
|
||||
"play": "nodemon -e ts -w . -x tsx playground.ts",
|
||||
"depcruise": "depcruise -c .dependency-cruiser.js src",
|
||||
"benchmark": "tsx src/benchmarks/index.ts",
|
||||
"prepare": "husky install"
|
||||
|
@ -68,7 +68,7 @@
|
|||
"devDependencies": {
|
||||
"@rollup/plugin-typescript": "^8.2.0",
|
||||
"@types/benchmark": "^2.1.0",
|
||||
"@types/jest": "^26.0.17",
|
||||
"@types/jest": "^29.2.2",
|
||||
"@types/node": "14",
|
||||
"@typescript-eslint/eslint-plugin": "^5.15.0",
|
||||
"@typescript-eslint/parser": "^5.15.0",
|
||||
|
@ -81,13 +81,13 @@
|
|||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"husky": "^7.0.4",
|
||||
"jest": "^27.5.1",
|
||||
"jest": "^29.3.1",
|
||||
"lint-staged": "^12.3.7",
|
||||
"nodemon": "^2.0.15",
|
||||
"prettier": "^2.6.0",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"rollup": "^2.70.1",
|
||||
"ts-jest": "^27.1.3",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-morph": "^14.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.3.1",
|
||||
|
|
|
@ -74,6 +74,8 @@ test("first party switch", () => {
|
|||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodBranded:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodPipeline:
|
||||
break;
|
||||
default:
|
||||
util.assertNever(def);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
// @ts-ignore TS6133
|
||||
import { expect, test } from "@jest/globals";
|
||||
|
||||
import * as z from "../index";
|
||||
|
||||
test("string to number pipeline", () => {
|
||||
const schema = z.string().transform(Number).pipe(z.number());
|
||||
expect(schema.parse("1234")).toEqual(1234);
|
||||
});
|
||||
|
||||
test("string to number pipeline async", async () => {
|
||||
const schema = z
|
||||
.string()
|
||||
.transform(async (val) => Number(val))
|
||||
.pipe(z.number());
|
||||
expect(await schema.parseAsync("1234")).toEqual(1234);
|
||||
});
|
||||
|
||||
test("break if dirty", () => {
|
||||
const schema = z
|
||||
.string()
|
||||
.refine((c) => c === "1234")
|
||||
.transform(async (val) => Number(val))
|
||||
.pipe(z.number().refine((v) => v < 100));
|
||||
const r1: any = schema.safeParse("12345");
|
||||
expect(r1.error.issues.length).toBe(1);
|
||||
const r2: any = schema.safeParse("3");
|
||||
expect(r2.error.issues.length).toBe(1);
|
||||
});
|
91
src/types.ts
91
src/types.ts
|
@ -371,6 +371,7 @@ export abstract class ZodType<
|
|||
this.default = this.default.bind(this);
|
||||
this.catch = this.catch.bind(this);
|
||||
this.describe = this.describe.bind(this);
|
||||
this.pipe = this.pipe.bind(this);
|
||||
this.isNullable = this.isNullable.bind(this);
|
||||
this.isOptional = this.isOptional.bind(this);
|
||||
}
|
||||
|
@ -448,6 +449,10 @@ export abstract class ZodType<
|
|||
});
|
||||
}
|
||||
|
||||
pipe<T extends ZodTypeAny>(target: T): ZodPipeline<this, T> {
|
||||
return ZodPipeline.create(this, target);
|
||||
}
|
||||
|
||||
isOptional(): boolean {
|
||||
return this.safeParse(undefined).success;
|
||||
}
|
||||
|
@ -1279,7 +1284,7 @@ export interface ZodAnyDef extends ZodTypeDef {
|
|||
|
||||
export class ZodAny extends ZodType<any, ZodAnyDef> {
|
||||
// to prevent instances of other classes from extending ZodAny. this causes issues with catchall in ZodObject.
|
||||
_any: true = true;
|
||||
_any = true as const;
|
||||
_parse(input: ParseInput): ParseReturnType<this["_output"]> {
|
||||
return OK(input.data);
|
||||
}
|
||||
|
@ -1304,7 +1309,7 @@ export interface ZodUnknownDef extends ZodTypeDef {
|
|||
|
||||
export class ZodUnknown extends ZodType<unknown, ZodUnknownDef> {
|
||||
// required
|
||||
_unknown: true = true;
|
||||
_unknown = true as const;
|
||||
_parse(input: ParseInput): ParseReturnType<this["_output"]> {
|
||||
return OK(input.data);
|
||||
}
|
||||
|
@ -3914,6 +3919,82 @@ export class ZodBranded<
|
|||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
////////////////////////////////////////////
|
||||
////////// //////////
|
||||
////////// ZodPipeline //////////
|
||||
////////// //////////
|
||||
////////////////////////////////////////////
|
||||
////////////////////////////////////////////
|
||||
|
||||
export interface ZodPipelineDef<A extends ZodTypeAny, B extends ZodTypeAny>
|
||||
extends ZodTypeDef {
|
||||
in: A;
|
||||
out: B;
|
||||
typeName: ZodFirstPartyTypeKind.ZodPipeline;
|
||||
}
|
||||
|
||||
export class ZodPipeline<
|
||||
A extends ZodTypeAny,
|
||||
B extends ZodTypeAny
|
||||
> extends ZodType<B["_output"], ZodPipelineDef<A, B>, A["_input"]> {
|
||||
_parse(input: ParseInput): ParseReturnType<any> {
|
||||
const { status, ctx } = this._processInputParams(input);
|
||||
if (ctx.common.async) {
|
||||
const handleAsync = async () => {
|
||||
const inResult = await this._def.in._parseAsync({
|
||||
data: ctx.data,
|
||||
path: ctx.path,
|
||||
parent: ctx,
|
||||
});
|
||||
if (inResult.status === "aborted") return INVALID;
|
||||
if (inResult.status === "dirty") {
|
||||
status.dirty();
|
||||
return DIRTY(inResult.value);
|
||||
} else {
|
||||
return this._def.out._parseAsync({
|
||||
data: inResult.value,
|
||||
path: ctx.path,
|
||||
parent: ctx,
|
||||
});
|
||||
}
|
||||
};
|
||||
return handleAsync();
|
||||
} else {
|
||||
const inResult = this._def.in._parseSync({
|
||||
data: ctx.data,
|
||||
path: ctx.path,
|
||||
parent: ctx,
|
||||
});
|
||||
if (inResult.status === "aborted") return INVALID;
|
||||
if (inResult.status === "dirty") {
|
||||
status.dirty();
|
||||
return {
|
||||
status: "dirty",
|
||||
value: inResult.value,
|
||||
};
|
||||
} else {
|
||||
return this._def.out._parseSync({
|
||||
data: inResult.value,
|
||||
path: ctx.path,
|
||||
parent: ctx,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static create<A extends ZodTypeAny, B extends ZodTypeAny>(
|
||||
a: A,
|
||||
b: B
|
||||
): ZodPipeline<A, B> {
|
||||
return new ZodPipeline({
|
||||
in: a,
|
||||
out: b,
|
||||
typeName: ZodFirstPartyTypeKind.ZodPipeline,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const custom = <T>(
|
||||
check?: (data: unknown) => any,
|
||||
params: Parameters<ZodTypeAny["refine"]>[1] = {},
|
||||
|
@ -3970,6 +4051,7 @@ export enum ZodFirstPartyTypeKind {
|
|||
ZodCatch = "ZodCatch",
|
||||
ZodPromise = "ZodPromise",
|
||||
ZodBranded = "ZodBranded",
|
||||
ZodPipeline = "ZodPipeline",
|
||||
}
|
||||
export type ZodFirstPartySchemaTypes =
|
||||
| ZodString
|
||||
|
@ -4004,7 +4086,8 @@ export type ZodFirstPartySchemaTypes =
|
|||
| ZodDefault<any>
|
||||
| ZodCatch<any>
|
||||
| ZodPromise<any>
|
||||
| ZodBranded<any, any>;
|
||||
| ZodBranded<any, any>
|
||||
| ZodPipeline<any, any>;
|
||||
|
||||
// new approach that works for abstract classes
|
||||
// but required TS 4.4+
|
||||
|
@ -4051,6 +4134,7 @@ const effectsType = ZodEffects.create;
|
|||
const optionalType = ZodOptional.create;
|
||||
const nullableType = ZodNullable.create;
|
||||
const preprocessType = ZodEffects.createWithPreprocess;
|
||||
const pipelineType = ZodPipeline.create;
|
||||
const ostring = () => stringType().optional();
|
||||
const onumber = () => numberType().optional();
|
||||
const oboolean = () => booleanType().optional();
|
||||
|
@ -4081,6 +4165,7 @@ export {
|
|||
onumber,
|
||||
optionalType as optional,
|
||||
ostring,
|
||||
pipelineType as pipeline,
|
||||
preprocessType as preprocess,
|
||||
promiseType as promise,
|
||||
recordType as record,
|
||||
|
|
Loading…
Reference in New Issue