Skip to content

Commit 95cd2e1

Browse files
authored
Merge pull request #1245 from Code-Hex/nickdegroot/fix-recursive-types
#907 for zodv4
2 parents 393b697 + 36a4d5e commit 95cd2e1

File tree

9 files changed

+73
-25
lines changed

9 files changed

+73
-25
lines changed

example/myzod/schemas.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as myzod from 'myzod'
2-
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'
2+
import { Admin, AttributeInput, ButtonComponentType, Comment, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'
33

44
export const definedNonNullAnySchema = myzod.object({});
55

@@ -25,6 +25,13 @@ export function AttributeInputSchema(): myzod.Type<AttributeInput> {
2525
})
2626
}
2727

28+
export function CommentSchema(): myzod.Type<Comment> {
29+
return myzod.object({
30+
__typename: myzod.literal('Comment').optional(),
31+
replies: myzod.array(myzod.lazy(() => CommentSchema())).optional().nullable()
32+
})
33+
}
34+
2835
export function ComponentInputSchema(): myzod.Type<ComponentInput> {
2936
return myzod.object({
3037
child: myzod.lazy(() => ComponentInputSchema().optional().nullable()),

example/test.graphql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,7 @@ directive @constraint(
121121
multipleOf: Float
122122
uniqueTypeName: String
123123
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
124+
125+
type Comment {
126+
replies: [Comment!]
127+
}

example/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export enum ButtonComponentType {
3131
Submit = 'SUBMIT'
3232
}
3333

34+
export type Comment = {
35+
__typename?: 'Comment';
36+
replies?: Maybe<Array<Comment>>;
37+
};
38+
3439
export type ComponentInput = {
3540
child?: InputMaybe<ComponentInput>;
3641
childrens?: InputMaybe<Array<InputMaybe<ComponentInput>>>;

example/valibot/schemas.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as v from 'valibot'
2-
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'
2+
import { Admin, AttributeInput, ButtonComponentType, Comment, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'
33

44
export const ButtonComponentTypeSchema = v.enum_(ButtonComponentType);
55

@@ -23,6 +23,13 @@ export function AttributeInputSchema(): v.GenericSchema<AttributeInput> {
2323
})
2424
}
2525

26+
export function CommentSchema(): v.GenericSchema<Comment> {
27+
return v.object({
28+
__typename: v.optional(v.literal('Comment')),
29+
replies: v.nullish(v.array(v.lazy(() => CommentSchema())))
30+
})
31+
}
32+
2633
export function ComponentInputSchema(): v.GenericSchema<ComponentInput> {
2734
return v.object({
2835
child: v.lazy(() => v.nullish(ComponentInputSchema())),

example/yup/schemas.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as yup from 'yup'
2-
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User, UserKind } from '../types'
2+
import { Admin, AttributeInput, ButtonComponentType, Comment, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User, UserKind } from '../types'
33

44
export const ButtonComponentTypeSchema = yup.string<ButtonComponentType>().oneOf(Object.values(ButtonComponentType)).defined();
55

@@ -29,6 +29,13 @@ export function AttributeInputSchema(): yup.ObjectSchema<AttributeInput> {
2929
})
3030
}
3131

32+
export function CommentSchema(): yup.ObjectSchema<Comment> {
33+
return yup.object({
34+
__typename: yup.string<'Comment'>().optional(),
35+
replies: yup.array(yup.lazy(() => CommentSchema().nonNullable())).defined().nullable().optional()
36+
})
37+
}
38+
3239
export function ComponentInputSchema(): yup.ObjectSchema<ComponentInput> {
3340
return yup.object({
3441
child: yup.lazy(() => ComponentInputSchema()).optional(),

example/zod/schemas.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { z } from 'zod/v3'
2-
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'
2+
import { Admin, AttributeInput, ButtonComponentType, Comment, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'
33

44
type Properties<T> = Required<{
55
[K in keyof T]: z.ZodType<T[K], any, T[K]>;
@@ -33,6 +33,13 @@ export function AttributeInputSchema(): z.ZodObject<Properties<AttributeInput>>
3333
})
3434
}
3535

36+
export function CommentSchema(): z.ZodObject<Properties<Comment>> {
37+
return z.object({
38+
__typename: z.literal('Comment').optional(),
39+
replies: z.array(z.lazy(() => CommentSchema())).nullish()
40+
})
41+
}
42+
3643
export function ComponentInputSchema(): z.ZodObject<Properties<ComponentInput>> {
3744
return z.object({
3845
child: z.lazy(() => ComponentInputSchema().nullish()),

example/zodv4/schemas.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as z from 'zod'
2-
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'
2+
import { Admin, AttributeInput, ButtonComponentType, Comment, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'
33

44
type Properties<T> = Required<{
55
[K in keyof T]: z.ZodType<T[K]>;
@@ -33,6 +33,13 @@ export function AttributeInputSchema(): z.ZodObject<Properties<AttributeInput>>
3333
})
3434
}
3535

36+
export function CommentSchema(): z.ZodObject<Properties<Comment>> {
37+
return z.object({
38+
__typename: z.literal('Comment').optional(),
39+
replies: z.array(z.lazy(() => CommentSchema())).nullish()
40+
})
41+
}
42+
3643
export function ComponentInputSchema(): z.ZodObject<Properties<ComponentInput>> {
3744
return z.object({
3845
child: z.lazy(() => ComponentInputSchema().nullish()),
@@ -128,7 +135,7 @@ export function UserSchema(): z.ZodObject<Properties<User>> {
128135
createdAt: definedNonNullAnySchema.nullish(),
129136
email: z.string().nullish(),
130137
id: z.string().nullish(),
131-
kind: UserKindSchema().nullish(),
138+
kind: z.lazy(() => UserKindSchema().nullish()),
132139
name: z.string().nullish(),
133140
password: z.string().nullish(),
134141
updatedAt: definedNonNullAnySchema.nullish()

src/zodv4/index.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ import type { Visitor } from '../visitor.js';
1616
import { resolveExternalModuleAndFn } from '@graphql-codegen/plugin-helpers';
1717
import { convertNameParts, DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
1818
import {
19+
isEnumType,
20+
isScalarType,
1921
Kind,
2022
} from 'graphql';
2123
import { buildApi, formatDirectiveConfig } from '../directive.js';
2224
import {
2325
escapeGraphQLCharacters,
2426
InterfaceTypeDefinitionBuilder,
25-
isInput,
2627
isListType,
2728
isNamedType,
2829
isNonNullType,
@@ -275,22 +276,22 @@ export class ZodV4SchemaVisitor extends BaseSchemaVisitor {
275276

276277
function generateFieldZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, indentCount: number): string {
277278
const gen = generateFieldTypeZodSchema(config, visitor, field, field.type);
278-
return indent(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount);
279+
return indent(`${field.name.value}: ${maybeLazy(visitor, field.type, gen)}`, indentCount);
279280
}
280281

281282
function generateFieldTypeZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, type: TypeNode, parentType?: TypeNode): string {
282283
if (isListType(type)) {
283284
const gen = generateFieldTypeZodSchema(config, visitor, field, type.type, type);
284285
if (!isNonNullType(parentType)) {
285-
const arrayGen = `z.array(${maybeLazy(type.type, gen)})`;
286+
const arrayGen = `z.array(${maybeLazy(visitor, type.type, gen)})`;
286287
const maybeLazyGen = applyDirectives(config, field, arrayGen);
287288
return `${maybeLazyGen}.nullish()`;
288289
}
289-
return `z.array(${maybeLazy(type.type, gen)})`;
290+
return `z.array(${maybeLazy(visitor, type.type, gen)})`;
290291
}
291292
if (isNonNullType(type)) {
292293
const gen = generateFieldTypeZodSchema(config, visitor, field, type.type, type);
293-
return maybeLazy(type.type, gen);
294+
return maybeLazy(visitor, type.type, gen);
294295
}
295296
if (isNamedType(type)) {
296297
const gen = generateNameNodeZodSchema(config, visitor, type.name);
@@ -371,11 +372,14 @@ function generateNameNodeZodSchema(config: ValidationSchemaPluginConfig, visitor
371372
}
372373
}
373374

374-
function maybeLazy(type: TypeNode, schema: string): string {
375-
if (isNamedType(type) && isInput(type.name.value))
376-
return `z.lazy(() => ${schema})`;
375+
function maybeLazy(visitor: Visitor, type: TypeNode, schema: string): string {
376+
if (!isNamedType(type)) {
377+
return schema;
378+
}
377379

378-
return schema;
380+
const schemaType = visitor.getType(type.name.value);
381+
const isComplexType = !isScalarType(schemaType) && !isEnumType(schemaType);
382+
return isComplexType ? `z.lazy(() => ${schema})` : schema;
379383
}
380384

381385
function zod4Scalar(config: ValidationSchemaPluginConfig, visitor: Visitor, scalarName: string): string {

tests/zodv4.spec.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,15 +1336,15 @@ describe('zodv4', () => {
13361336
export function BookSchema(): z.ZodObject<Properties<Book>> {
13371337
return z.object({
13381338
__typename: z.literal('Book').optional(),
1339-
author: AuthorSchema().nullish(),
1339+
author: z.lazy(() => AuthorSchema().nullish()),
13401340
title: z.string().nullish()
13411341
})
13421342
}
13431343
13441344
export function AuthorSchema(): z.ZodObject<Properties<Author>> {
13451345
return z.object({
13461346
__typename: z.literal('Author').optional(),
1347-
books: z.array(BookSchema().nullable()).nullish(),
1347+
books: z.array(z.lazy(() => BookSchema().nullable())).nullish(),
13481348
name: z.string().nullish()
13491349
})
13501350
}
@@ -1621,7 +1621,7 @@ describe('zodv4', () => {
16211621
export function GeometrySchema(): z.ZodObject<Properties<Geometry>> {
16221622
return z.object({
16231623
__typename: z.literal('Geometry').optional(),
1624-
shape: ShapeSchema().nullish()
1624+
shape: z.lazy(() => ShapeSchema().nullish())
16251625
})
16261626
}
16271627
"
@@ -1772,7 +1772,7 @@ describe('zodv4', () => {
17721772
17731773
export const GeometrySchema: z.ZodObject<Properties<Geometry>> = z.object({
17741774
__typename: z.literal('Geometry').optional(),
1775-
shape: ShapeSchema.nullish()
1775+
shape: z.lazy(() => ShapeSchema.nullish())
17761776
});
17771777
"
17781778
`)
@@ -1924,14 +1924,14 @@ describe('zodv4', () => {
19241924
19251925
export function BookSchema(): z.ZodObject<Properties<Book>> {
19261926
return z.object({
1927-
author: AuthorSchema().nullish(),
1927+
author: z.lazy(() => AuthorSchema().nullish()),
19281928
title: z.string().nullish()
19291929
})
19301930
}
19311931
19321932
export function AuthorSchema(): z.ZodObject<Properties<Author>> {
19331933
return z.object({
1934-
books: z.array(BookSchema().nullable()).nullish(),
1934+
books: z.array(z.lazy(() => BookSchema().nullable())).nullish(),
19351935
name: z.string().nullish()
19361936
})
19371937
}
@@ -1987,15 +1987,15 @@ describe('zodv4', () => {
19871987
export function BookSchema(): z.ZodObject<Properties<Book>> {
19881988
return z.object({
19891989
title: z.string(),
1990-
author: AuthorSchema()
1990+
author: z.lazy(() => AuthorSchema())
19911991
})
19921992
}
19931993
19941994
export function TextbookSchema(): z.ZodObject<Properties<Textbook>> {
19951995
return z.object({
19961996
__typename: z.literal('Textbook').optional(),
19971997
title: z.string(),
1998-
author: AuthorSchema(),
1998+
author: z.lazy(() => AuthorSchema()),
19991999
courses: z.array(z.string())
20002000
})
20012001
}
@@ -2004,15 +2004,15 @@ describe('zodv4', () => {
20042004
return z.object({
20052005
__typename: z.literal('ColoringBook').optional(),
20062006
title: z.string(),
2007-
author: AuthorSchema(),
2007+
author: z.lazy(() => AuthorSchema()),
20082008
colors: z.array(z.string())
20092009
})
20102010
}
20112011
20122012
export function AuthorSchema(): z.ZodObject<Properties<Author>> {
20132013
return z.object({
20142014
__typename: z.literal('Author').optional(),
2015-
books: z.array(BookSchema()).nullish(),
2015+
books: z.array(z.lazy(() => BookSchema())).nullish(),
20162016
name: z.string().nullish()
20172017
})
20182018
}

0 commit comments

Comments
 (0)