Skip to content

Commit

Permalink
fix: string regex validation generation, expand object schema
Browse files Browse the repository at this point in the history
  • Loading branch information
DJankauskas committed Jul 7, 2023
1 parent 39d6cd1 commit 8870365
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 13 deletions.
Expand Up @@ -6,7 +6,7 @@ import pkgUp from "pkg-up";
import path from "path";

type T = {
datetime: StringSchema<{ max: 20; datetime: true }>;
datetime: StringSchema<{ max: 20; datetime: true, regex: ["[A-Z]", "oops"] }>;
addresses: AddrIface[];
enum: Enum;
};
Expand Down Expand Up @@ -54,11 +54,11 @@ const T: z.ZodTypeAny;
",
"node_modules/zodgen/src/__tests__/generate-in-node-modules.test.js": "const { z } = require("zod");
const { AddressIface: __symbol_AddrIface, Enum: __enum_Enum } = require("./common");
const T = z.object({ datetime: z.string().max(20).datetime(), addresses: z.array(z.lazy(() => __symbol_AddrIface)), enum: z.lazy(() => __enum_Enum) });
const T = z.object({ datetime: z.string().max(20).datetime().regex(new RegExp("[A-Z]"), "oops"), addresses: z.array(z.lazy(() => __symbol_AddrIface)), enum: z.lazy(() => __enum_Enum) });
",
"node_modules/zodgen/src/__tests__/generate-in-node-modules.test.mjs": "import { z } from "zod";
import { AddressIface as __symbol_AddrIface, Enum as __enum_Enum } from "./common";
const T = z.object({ datetime: z.string().max(20).datetime(), addresses: z.array(z.lazy(() => __symbol_AddrIface)), enum: z.lazy(() => __enum_Enum) });
const T = z.object({ datetime: z.string().max(20).datetime().regex(new RegExp("[A-Z]"), "oops"), addresses: z.array(z.lazy(() => __symbol_AddrIface)), enum: z.lazy(() => __enum_Enum) });
",
}
`));
9 changes: 6 additions & 3 deletions packages/ts-to-zod/src/__tests__/transform-refine.test.ts
Expand Up @@ -50,6 +50,8 @@ class Even<I extends TypeSchema<number>> extends SuperRefine<I> {
}
}

type Aliased = { x: string };

type T = {
a: ParseFloat<ToString<Date>>;
b: Coerce<string, "a" | "b">;
Expand All @@ -63,12 +65,13 @@ type T = {
gt: [5, "5 and below too small!"];
}>;
object: ObjectSchema<{}, { passthrough: true }>;
aliasedObject: ObjectSchema<Aliased, { strict: true }>;
array: ArraySchema<
number | null,
{ nonempty: true; min: [5, "at least five elements needed"] }
>;
datetime: StringSchema<{ datetime: { offset: true } }>;
catchall: ObjectSchema<{a: number}, {catchall: string}>;
catchall: ObjectSchema<{ a: number }, { catchall: string }>;
};

it(`transform`, async () =>
Expand All @@ -84,11 +87,11 @@ const T: z.ZodTypeAny;
",
"src/__tests__/transform-refine.test.codegen.js": "const { z } = require("zod");
const { ParseFloat, ToString, Coerce, ParsePet, Even } = require("./transform-refine.test");
const T = z.object({ a: z.date().transform(new ToString().transform).transform(new ParseFloat().transform), b: z.string().transform(new Coerce().transform), c: z.string().refine(new ParsePet().refine, new ParsePet().message), d: z.number().superRefine(new Even().superRefine), date: z.date().min(new Date("2023-01-10")), number: z.number().finite().safe("too big").gt(5, "5 and below too small!"), object: z.object({}).passthrough(), array: z.array(z.number().nullable()).nonempty().min(5, "at least five elements needed"), datetime: z.string().datetime({ offset: true }), catchall: z.object({ a: z.number() }).catchall(z.string()) });
const T = z.object({ a: z.date().transform(new ToString().transform).transform(new ParseFloat().transform), b: z.string().transform(new Coerce().transform), c: z.string().refine(new ParsePet().refine, new ParsePet().message), d: z.number().superRefine(new Even().superRefine), date: z.date().min(new Date("2023-01-10")), number: z.number().finite().safe("too big").gt(5, "5 and below too small!"), object: z.object({}).passthrough(), aliasedObject: z.object({ x: z.string() }).strict(), array: z.array(z.number().nullable()).nonempty().min(5, "at least five elements needed"), datetime: z.string().datetime({ offset: true }), catchall: z.object({ a: z.number() }).catchall(z.string()) });
",
"src/__tests__/transform-refine.test.codegen.mjs": "import { z } from "zod";
import { ParseFloat, ToString, Coerce, ParsePet, Even } from "./transform-refine.test";
const T = z.object({ a: z.date().transform(new ToString().transform).transform(new ParseFloat().transform), b: z.string().transform(new Coerce().transform), c: z.string().refine(new ParsePet().refine, new ParsePet().message), d: z.number().superRefine(new Even().superRefine), date: z.date().min(new Date("2023-01-10")), number: z.number().finite().safe("too big").gt(5, "5 and below too small!"), object: z.object({}).passthrough(), array: z.array(z.number().nullable()).nonempty().min(5, "at least five elements needed"), datetime: z.string().datetime({ offset: true }), catchall: z.object({ a: z.number() }).catchall(z.string()) });
const T = z.object({ a: z.date().transform(new ToString().transform).transform(new ParseFloat().transform), b: z.string().transform(new Coerce().transform), c: z.string().refine(new ParsePet().refine, new ParsePet().message), d: z.number().superRefine(new Even().superRefine), date: z.date().min(new Date("2023-01-10")), number: z.number().finite().safe("too big").gt(5, "5 and below too small!"), object: z.object({}).passthrough(), aliasedObject: z.object({ x: z.string() }).strict(), array: z.array(z.number().nullable()).nonempty().min(5, "at least five elements needed"), datetime: z.string().datetime({ offset: true }), catchall: z.object({ a: z.number() }).catchall(z.string()) });
",
}
`));
19 changes: 12 additions & 7 deletions packages/ts-to-zod/src/convertType.ts
Expand Up @@ -290,7 +290,11 @@ export function convertSymbol(
export function convertType(
ctx: ConvertTypeContext,
ty: tm.Type,
diagnosticItem: DiagnosticItem
diagnosticItem: DiagnosticItem,
/**
* If true, forces generation for the current type instead of using `z.lazy()`.
*/
generateInline?: boolean,
): ts.Expression {
const typeSymbol = ty.getSymbol();

Expand Down Expand Up @@ -386,7 +390,7 @@ export function convertType(
packageTypeName,
objectSchemaProperties,
typeArgs,
convertType(ctx, objectType, diagnosticItem),
convertType(ctx, objectType, diagnosticItem, true),
diagnosticItem
);
}
Expand Down Expand Up @@ -426,7 +430,7 @@ export function convertType(
}
}

if (!ctx.isRoot && !isNativeObject(typeSymbol)) {
if (!ctx.isRoot && !generateInline && !isNativeObject(typeSymbol)) {
const symbol =
ty.getAliasSymbol() ||
(ty.isInterface() && !isNativeObject(typeSymbol) ? typeSymbol : null);
Expand Down Expand Up @@ -1063,19 +1067,20 @@ function convertSchemaType(

// Special-cases how to handle string value literals.
// This is needed for the case of `min` and `max` for
// `DateSchema`, as they take in instances of `Date`, but
// `DateSchema`, and `regex` for `StringSchema`, as they take
// in instances of `Date` and `RegExp`, but
// users can only pass string literals as type arguments.
// This function converts those literals to Dates at
// runtime.
const createValueStringLiteral = (s: string) => {
const createValueStringLiteral = (s: string, property: string) => {
const stringLiteral = factory.createStringLiteral(s);
return schemaTypeName === "DateSchema"
? factory.createNewExpression(
factory.createIdentifier("Date"),
[],
[stringLiteral]
)
: stringLiteral;
: schemaTypeName === "StringSchema" && property === "regex" ? factory.createNewExpression(factory.createIdentifier("RegExp"), [], [factory.createStringLiteral(s)]) : stringLiteral;
};

const typeArgsSymbol = typeArgs.getAliasSymbol() || typeArgs.getSymbol();
Expand Down Expand Up @@ -1216,7 +1221,7 @@ function convertSchemaType(
if (literalValue) {
let args: ts.Expression[];
if (typeof literalValue === "string")
args = [createValueStringLiteral(literalValue)];
args = [createValueStringLiteral(literalValue, name)];
else if (typeof literalValue === "number")
args = [factory.createNumericLiteral(literalValue)];
else if (typeof literalValue === "bigint")
Expand Down

0 comments on commit 8870365

Please sign in to comment.