From 3420e07e116ac11416a1b99411a814251bdd39c7 Mon Sep 17 00:00:00 2001 From: Nick DeGroot <1966472+nickthegroot@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:39:05 -0700 Subject: [PATCH] :bug: Fix #599: add lazy support for potential self-referential types --- example/myzod/schemas.ts | 2 +- example/valibot/schemas.ts | 2 +- example/yup/schemas.ts | 2 +- example/zod/schemas.ts | 2 +- src/myzod/index.ts | 22 +++++++++++++--------- src/valibot/index.ts | 19 +++++++++++-------- src/yup/index.ts | 23 +++++++++++++---------- src/zod/index.ts | 22 +++++++++++++--------- 8 files changed, 54 insertions(+), 40 deletions(-) diff --git a/example/myzod/schemas.ts b/example/myzod/schemas.ts index 68c8623d..3a47f823 100644 --- a/example/myzod/schemas.ts +++ b/example/myzod/schemas.ts @@ -120,7 +120,7 @@ export function UserSchema(): myzod.Type { createdAt: definedNonNullAnySchema.optional().nullable(), email: myzod.string().optional().nullable(), id: myzod.string().optional().nullable(), - kind: UserKindSchema().optional().nullable(), + kind: myzod.lazy(() => UserKindSchema().optional().nullable()), name: myzod.string().optional().nullable(), password: myzod.string().optional().nullable(), updatedAt: definedNonNullAnySchema.optional().nullable() diff --git a/example/valibot/schemas.ts b/example/valibot/schemas.ts index 14c8bac1..dbc09340 100644 --- a/example/valibot/schemas.ts +++ b/example/valibot/schemas.ts @@ -118,7 +118,7 @@ export function UserSchema(): v.GenericSchema { createdAt: v.nullish(v.any()), email: v.nullish(v.string()), id: v.nullish(v.string()), - kind: v.nullish(UserKindSchema()), + kind: v.lazy(() => v.nullish(UserKindSchema())), name: v.nullish(v.string()), password: v.nullish(v.string()), updatedAt: v.nullish(v.any()) diff --git a/example/yup/schemas.ts b/example/yup/schemas.ts index abc049aa..d78a3abd 100644 --- a/example/yup/schemas.ts +++ b/example/yup/schemas.ts @@ -124,7 +124,7 @@ export function UserSchema(): yup.ObjectSchema { createdAt: yup.mixed().nullable().optional(), email: yup.string().defined().nullable().optional(), id: yup.string().defined().nullable().optional(), - kind: UserKindSchema().nullable().optional(), + kind: yup.lazy(() => UserKindSchema().nullable()).optional(), name: yup.string().defined().nullable().optional(), password: yup.string().defined().nullable().optional(), updatedAt: yup.mixed().nullable().optional() diff --git a/example/zod/schemas.ts b/example/zod/schemas.ts index 407e3895..7bd0aef7 100644 --- a/example/zod/schemas.ts +++ b/example/zod/schemas.ts @@ -128,7 +128,7 @@ export function UserSchema(): z.ZodObject> { createdAt: definedNonNullAnySchema.nullish(), email: z.string().nullish(), id: z.string().nullish(), - kind: UserKindSchema().nullish(), + kind: z.lazy(() => UserKindSchema().nullish()), name: z.string().nullish(), password: z.string().nullish(), updatedAt: definedNonNullAnySchema.nullish() diff --git a/src/myzod/index.ts b/src/myzod/index.ts index 8804a153..46dae647 100644 --- a/src/myzod/index.ts +++ b/src/myzod/index.ts @@ -16,13 +16,14 @@ import type { Visitor } from '../visitor.js'; import { resolveExternalModuleAndFn } from '@graphql-codegen/plugin-helpers'; import { convertNameParts, DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common'; import { + isEnumType, + isScalarType, Kind, } from 'graphql'; import { buildApi, formatDirectiveConfig } from '../directive.js'; import { escapeGraphQLCharacters, InterfaceTypeDefinitionBuilder, - isInput, isListType, isNamedType, isNonNullType, @@ -258,22 +259,22 @@ export class MyZodSchemaVisitor extends BaseSchemaVisitor { function generateFieldMyZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, indentCount: number): string { const gen = generateFieldTypeMyZodSchema(config, visitor, field, field.type); - return indent(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount); + return indent(`${field.name.value}: ${maybeLazy(visitor, field.type, gen)}`, indentCount); } function generateFieldTypeMyZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, type: TypeNode, parentType?: TypeNode): string { if (isListType(type)) { const gen = generateFieldTypeMyZodSchema(config, visitor, field, type.type, type); if (!isNonNullType(parentType)) { - const arrayGen = `myzod.array(${maybeLazy(type.type, gen)})`; + const arrayGen = `myzod.array(${maybeLazy(visitor, type.type, gen)})`; const maybeLazyGen = applyDirectives(config, field, arrayGen); return `${maybeLazyGen}.optional().nullable()`; } - return `myzod.array(${maybeLazy(type.type, gen)})`; + return `myzod.array(${maybeLazy(visitor, type.type, gen)})`; } if (isNonNullType(type)) { const gen = generateFieldTypeMyZodSchema(config, visitor, field, type.type, type); - return maybeLazy(type.type, gen); + return maybeLazy(visitor, type.type, gen); } if (isNamedType(type)) { const gen = generateNameNodeMyZodSchema(config, visitor, type.name); @@ -354,11 +355,14 @@ function generateNameNodeMyZodSchema(config: ValidationSchemaPluginConfig, visit } } -function maybeLazy(type: TypeNode, schema: string): string { - if (isNamedType(type) && isInput(type.name.value)) - return `myzod.lazy(() => ${schema})`; +function maybeLazy(visitor: Visitor, type: TypeNode, schema: string): string { + if (!isNamedType(type)) { + return schema; + } - return schema; + const schemaType = visitor.getType(type.name.value); + const isComplexType = !isScalarType(schemaType) && !isEnumType(schemaType); + return isComplexType ? `myzod.lazy(() => ${schema})` : schema; } function myzod4Scalar(config: ValidationSchemaPluginConfig, visitor: Visitor, scalarName: string): string { diff --git a/src/valibot/index.ts b/src/valibot/index.ts index 71c4d09b..281fdfde 100644 --- a/src/valibot/index.ts +++ b/src/valibot/index.ts @@ -10,6 +10,7 @@ import type { TypeNode, UnionTypeDefinitionNode, } from 'graphql'; +import { isEnumType, isScalarType } from 'graphql'; import type { ValidationSchemaPluginConfig } from '../config.js'; import type { Visitor } from '../visitor.js'; @@ -17,7 +18,6 @@ import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common import { buildApiForValibot, formatDirectiveConfig } from '../directive.js'; import { InterfaceTypeDefinitionBuilder, - isInput, isListType, isNamedType, isNonNullType, @@ -201,13 +201,13 @@ export class ValibotSchemaVisitor extends BaseSchemaVisitor { function generateFieldValibotSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, indentCount: number): string { const gen = generateFieldTypeValibotSchema(config, visitor, field, field.type); - return indent(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount); + return indent(`${field.name.value}: ${maybeLazy(visitor, field.type, gen)}`, indentCount); } function generateFieldTypeValibotSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, type: TypeNode, parentType?: TypeNode): string { if (isListType(type)) { const gen = generateFieldTypeValibotSchema(config, visitor, field, type.type, type); - const arrayGen = `v.array(${maybeLazy(type.type, gen)})`; + const arrayGen = `v.array(${maybeLazy(visitor, type.type, gen)})`; if (!isNonNullType(parentType)) return `v.nullish(${arrayGen})`; @@ -215,7 +215,7 @@ function generateFieldTypeValibotSchema(config: ValidationSchemaPluginConfig, vi } if (isNonNullType(type)) { const gen = generateFieldTypeValibotSchema(config, visitor, field, type.type, type); - return maybeLazy(type.type, gen); + return maybeLazy(visitor, type.type, gen); } if (isNamedType(type)) { const gen = generateNameNodeValibotSchema(config, visitor, type.name); @@ -274,11 +274,14 @@ function generateNameNodeValibotSchema(config: ValidationSchemaPluginConfig, vis } } -function maybeLazy(type: TypeNode, schema: string): string { - if (isNamedType(type) && isInput(type.name.value)) - return `v.lazy(() => ${schema})`; +function maybeLazy(visitor: Visitor, type: TypeNode, schema: string): string { + if (!isNamedType(type)) { + return schema; + } - return schema; + const schemaType = visitor.getType(type.name.value); + const isComplexType = !isScalarType(schemaType) && !isEnumType(schemaType); + return isComplexType ? `v.lazy(() => ${schema})` : schema; } function valibot4Scalar(config: ValidationSchemaPluginConfig, visitor: Visitor, scalarName: string): string { diff --git a/src/yup/index.ts b/src/yup/index.ts index b1805d8a..773a1ba6 100644 --- a/src/yup/index.ts +++ b/src/yup/index.ts @@ -16,13 +16,14 @@ import type { Visitor } from '../visitor.js'; import { resolveExternalModuleAndFn } from '@graphql-codegen/plugin-helpers'; import { convertNameParts, DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common'; import { + isEnumType, + isScalarType, Kind, } from 'graphql'; import { buildApi, formatDirectiveConfig } from '../directive.js'; import { escapeGraphQLCharacters, InterfaceTypeDefinitionBuilder, - isInput, isListType, isNamedType, isNonNullType, @@ -319,20 +320,20 @@ function generateFieldYupSchema(config: ValidationSchemaPluginConfig, visitor: V const formatted = formatDirectiveConfig(config.directives); gen += buildApi(formatted, field.directives); } - return indent(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount); + return indent(`${field.name.value}: ${maybeLazy(visitor, field.type, gen)}`, indentCount); } function generateFieldTypeYupSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, type: TypeNode, parentType?: TypeNode): string { if (isListType(type)) { const gen = generateFieldTypeYupSchema(config, visitor, type.type, type); if (!isNonNullType(parentType)) - return `yup.array(${maybeLazy(type.type, gen)}).defined().nullable()`; + return `yup.array(${maybeLazy(visitor, type.type, gen)}).defined().nullable()`; - return `yup.array(${maybeLazy(type.type, gen)}).defined()`; + return `yup.array(${maybeLazy(visitor, type.type, gen)}).defined()`; } if (isNonNullType(type)) { const gen = generateFieldTypeYupSchema(config, visitor, type.type, type); - return maybeLazy(type.type, gen); + return maybeLazy(visitor, type.type, gen); } if (isNamedType(type)) { const gen = generateNameNodeYupSchema(config, visitor, type.name); @@ -375,12 +376,14 @@ function generateNameNodeYupSchema(config: ValidationSchemaPluginConfig, visitor } } -function maybeLazy(type: TypeNode, schema: string): string { - if (isNamedType(type) && isInput(type.name.value)) { - // https://github.com/jquense/yup/issues/1283#issuecomment-786559444 - return `yup.lazy(() => ${schema})`; +function maybeLazy(visitor: Visitor, type: TypeNode, schema: string): string { + if (!isNamedType(type)) { + return schema; } - return schema; + + const schemaType = visitor.getType(type.name.value); + const isComplexType = !isScalarType(schemaType) && !isEnumType(schemaType); + return isComplexType ? `yup.lazy(() => ${schema})` : schema; } function yup4Scalar(config: ValidationSchemaPluginConfig, visitor: Visitor, scalarName: string): string { diff --git a/src/zod/index.ts b/src/zod/index.ts index b1164fb0..084d1dcf 100644 --- a/src/zod/index.ts +++ b/src/zod/index.ts @@ -16,13 +16,14 @@ import type { Visitor } from '../visitor.js'; import { resolveExternalModuleAndFn } from '@graphql-codegen/plugin-helpers'; import { convertNameParts, DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common'; import { + isEnumType, + isScalarType, Kind, } from 'graphql'; import { buildApi, formatDirectiveConfig } from '../directive.js'; import { escapeGraphQLCharacters, InterfaceTypeDefinitionBuilder, - isInput, isListType, isNamedType, isNonNullType, @@ -274,22 +275,22 @@ export class ZodSchemaVisitor extends BaseSchemaVisitor { function generateFieldZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, indentCount: number): string { const gen = generateFieldTypeZodSchema(config, visitor, field, field.type); - return indent(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount); + return indent(`${field.name.value}: ${maybeLazy(visitor, field.type, gen)}`, indentCount); } function generateFieldTypeZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, type: TypeNode, parentType?: TypeNode): string { if (isListType(type)) { const gen = generateFieldTypeZodSchema(config, visitor, field, type.type, type); if (!isNonNullType(parentType)) { - const arrayGen = `z.array(${maybeLazy(type.type, gen)})`; + const arrayGen = `z.array(${maybeLazy(visitor, type.type, gen)})`; const maybeLazyGen = applyDirectives(config, field, arrayGen); return `${maybeLazyGen}.nullish()`; } - return `z.array(${maybeLazy(type.type, gen)})`; + return `z.array(${maybeLazy(visitor, type.type, gen)})`; } if (isNonNullType(type)) { const gen = generateFieldTypeZodSchema(config, visitor, field, type.type, type); - return maybeLazy(type.type, gen); + return maybeLazy(visitor, type.type, gen); } if (isNamedType(type)) { const gen = generateNameNodeZodSchema(config, visitor, type.name); @@ -370,11 +371,14 @@ function generateNameNodeZodSchema(config: ValidationSchemaPluginConfig, visitor } } -function maybeLazy(type: TypeNode, schema: string): string { - if (isNamedType(type) && isInput(type.name.value)) - return `z.lazy(() => ${schema})`; +function maybeLazy(visitor: Visitor, type: TypeNode, schema: string): string { + if (!isNamedType(type)) { + return schema; + } - return schema; + const schemaType = visitor.getType(type.name.value); + const isComplexType = !isScalarType(schemaType) && !isEnumType(schemaType); + return isComplexType ? `z.lazy(() => ${schema})` : schema; } function zod4Scalar(config: ValidationSchemaPluginConfig, visitor: Visitor, scalarName: string): string {