Skip to content

Commit

Permalink
🐛 Fix Code-Hex#599: add lazy support for potential self-referential t…
Browse files Browse the repository at this point in the history
…ypes
  • Loading branch information
nickthegroot authored and btoo committed Dec 8, 2024
1 parent b6c483a commit 0649842
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 81 deletions.
2 changes: 1 addition & 1 deletion example/myzod/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export function UserSchema(): myzod.Type<User> {
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()
Expand Down
2 changes: 1 addition & 1 deletion example/valibot/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export function UserSchema(): v.GenericSchema<User> {
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())
Expand Down
2 changes: 1 addition & 1 deletion example/yup/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export function UserSchema(): yup.ObjectSchema<User> {
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()
Expand Down
2 changes: 1 addition & 1 deletion example/zod/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export function UserSchema(): z.ZodObject<Properties<User>> {
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()
Expand Down
22 changes: 13 additions & 9 deletions src/myzod/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -262,22 +263,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);
Expand Down Expand Up @@ -358,11 +359,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 {
Expand Down
19 changes: 11 additions & 8 deletions src/valibot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import type {
TypeNode,
UnionTypeDefinitionNode,
} from 'graphql';
import { isEnumType, isScalarType } from 'graphql';
import type { ValidationSchemaPluginConfig } from '../config.js';
import type { Visitor } from '../visitor.js';

import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
import { buildApiForValibot, formatDirectiveConfig } from '../directive.js';
import {
InterfaceTypeDefinitionBuilder,
isInput,
isListType,
isNamedType,
isNonNullType,
Expand Down Expand Up @@ -205,21 +205,21 @@ 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})`;

return arrayGen;
}
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);
Expand Down Expand Up @@ -283,11 +283,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 {
Expand Down
23 changes: 13 additions & 10 deletions src/yup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -324,20 +325,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);
Expand Down Expand Up @@ -380,12 +381,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 {
Expand Down
22 changes: 13 additions & 9 deletions src/zod/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -278,22 +279,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);
Expand Down Expand Up @@ -374,11 +375,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 {
Expand Down
20 changes: 10 additions & 10 deletions tests/myzod.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -830,15 +830,15 @@ describe('myzod', () => {
export function BookSchema(): myzod.Type<Book> {
return myzod.object({
__typename: myzod.literal('Book').optional(),
author: AuthorSchema().optional().nullable(),
author: myzod.lazy(() => AuthorSchema().optional().nullable()),
title: myzod.string().optional().nullable()
})
}
export function AuthorSchema(): myzod.Type<Author> {
return myzod.object({
__typename: myzod.literal('Author').optional(),
books: myzod.array(BookSchema().nullable()).optional().nullable(),
books: myzod.array(myzod.lazy(() => BookSchema().nullable())).optional().nullable(),
name: myzod.string().optional().nullable()
})
}
Expand Down Expand Up @@ -1075,7 +1075,7 @@ describe('myzod', () => {
export function GeometrySchema(): myzod.Type<Geometry> {
return myzod.object({
__typename: myzod.literal('Geometry').optional(),
shape: ShapeSchema().optional().nullable()
shape: myzod.lazy(() => ShapeSchema().optional().nullable())
})
}
"
Expand Down Expand Up @@ -1196,7 +1196,7 @@ describe('myzod', () => {
export const GeometrySchema: myzod.Type<Geometry> = myzod.object({
__typename: myzod.literal('Geometry').optional(),
shape: ShapeSchema.optional().nullable()
shape: myzod.lazy(() => ShapeSchema.optional().nullable())
});
"
`)
Expand Down Expand Up @@ -1317,14 +1317,14 @@ describe('myzod', () => {
"
export function BookSchema(): myzod.Type<Book> {
return myzod.object({
author: AuthorSchema().optional().nullable(),
author: myzod.lazy(() => AuthorSchema().optional().nullable()),
title: myzod.string().optional().nullable()
})
}
export function AuthorSchema(): myzod.Type<Author> {
return myzod.object({
books: myzod.array(BookSchema().nullable()).optional().nullable(),
books: myzod.array(myzod.lazy(() => BookSchema().nullable())).optional().nullable(),
name: myzod.string().optional().nullable()
})
}
Expand Down Expand Up @@ -1369,15 +1369,15 @@ describe('myzod', () => {
export function BookSchema(): myzod.Type<Book> {
return myzod.object({
title: myzod.string(),
author: AuthorSchema()
author: myzod.lazy(() => AuthorSchema())
})
}
export function TextbookSchema(): myzod.Type<Textbook> {
return myzod.object({
__typename: myzod.literal('Textbook').optional(),
title: myzod.string(),
author: AuthorSchema(),
author: myzod.lazy(() => AuthorSchema()),
courses: myzod.array(myzod.string())
})
}
Expand All @@ -1386,15 +1386,15 @@ describe('myzod', () => {
return myzod.object({
__typename: myzod.literal('ColoringBook').optional(),
title: myzod.string(),
author: AuthorSchema(),
author: myzod.lazy(() => AuthorSchema()),
colors: myzod.array(myzod.string())
})
}
export function AuthorSchema(): myzod.Type<Author> {
return myzod.object({
__typename: myzod.literal('Author').optional(),
books: myzod.array(BookSchema()).optional().nullable(),
books: myzod.array(myzod.lazy(() => BookSchema())).optional().nullable(),
name: myzod.string().optional().nullable()
})
}
Expand Down
Loading

0 comments on commit 0649842

Please sign in to comment.