diff --git a/src/zod/index.ts b/src/zod/index.ts index 1e1d030f..21b90747 100644 --- a/src/zod/index.ts +++ b/src/zod/index.ts @@ -91,11 +91,7 @@ const generateInputObjectFieldYupSchema = ( field: InputValueDefinitionNode, indentCount: number ): string => { - let gen = generateInputObjectFieldTypeZodSchema(config, tsVisitor, schema, field.type); - if (config.directives && field.directives) { - const formatted = formatDirectiveConfig(config.directives); - gen += buildApi(formatted, field.directives); - } + const gen = generateInputObjectFieldTypeZodSchema(config, tsVisitor, schema, field, field.type); return indent(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount); }; @@ -103,38 +99,57 @@ const generateInputObjectFieldTypeZodSchema = ( config: ValidationSchemaPluginConfig, tsVisitor: TsVisitor, schema: GraphQLSchema, + field: InputValueDefinitionNode, type: TypeNode, parentType?: TypeNode ): string => { if (isListType(type)) { - const gen = generateInputObjectFieldTypeZodSchema(config, tsVisitor, schema, type.type, type); + const gen = generateInputObjectFieldTypeZodSchema(config, tsVisitor, schema, field, type.type, type); if (!isNonNullType(parentType)) { - return `z.array(${maybeLazy(type.type, gen)}).nullish()`; + const arrayGen = `z.array(${maybeLazy(type.type, gen)})`; + const maybeLazyGen = applyDirectives(config, field, arrayGen); + return `${maybeLazyGen}.nullish()`; } return `z.array(${maybeLazy(type.type, gen)})`; } if (isNonNullType(type)) { - const gen = generateInputObjectFieldTypeZodSchema(config, tsVisitor, schema, type.type, type); + const gen = generateInputObjectFieldTypeZodSchema(config, tsVisitor, schema, field, type.type, type); return maybeLazy(type.type, gen); } if (isNamedType(type)) { const gen = generateNameNodeZodSchema(config, tsVisitor, schema, type.name); + if (isListType(parentType)) { + return `${gen}.nullable()`; + } + const appliedDirectivesGen = applyDirectives(config, field, gen); if (isNonNullType(parentType)) { if (config.notAllowEmptyString === true) { const tsType = tsVisitor.scalars[type.name.value]; if (tsType === 'string') return `${gen}.min(1)`; } - return gen; + return appliedDirectivesGen; } if (isListType(parentType)) { - return `${gen}.nullable()`; + return `${appliedDirectivesGen}.nullable()`; } - return `${gen}.nullish()`; + return `${appliedDirectivesGen}.nullish()`; } console.warn('unhandled type:', type); return ''; }; +const applyDirectives = ( + config: ValidationSchemaPluginConfig, + field: InputValueDefinitionNode, + gen: string +): string => { + if (config.directives && field.directives) { + const formatted = formatDirectiveConfig(config.directives); + return gen + buildApi(formatted, field.directives); + } + return gen; +}; + const generateNameNodeZodSchema = ( config: ValidationSchemaPluginConfig, tsVisitor: TsVisitor, diff --git a/tests/zod.spec.ts b/tests/zod.spec.ts index ba21e604..7f9be47c 100644 --- a/tests/zod.spec.ts +++ b/tests/zod.spec.ts @@ -280,4 +280,96 @@ describe('zod', () => { expect(result.content).toContain(wantContain); } }); + describe('issues #19', () => { + it('string field', async () => { + const schema = buildSchema(/* GraphQL */ ` + input UserCreateInput { + profile: String @constraint(minLength: 1, maxLength: 5000) + } + + directive @constraint(minLength: Int!, maxLength: Int!) on INPUT_FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + directives: { + constraint: { + minLength: ['min', '$1', 'Please input more than $1'], + maxLength: ['max', '$1', 'Please input less than $1'], + }, + }, + }, + {} + ); + const wantContains = [ + 'export function UserCreateInputSchema(): z.ZodObject>', + 'profile: z.string().min(1, "Please input more than 1").max(5000, "Please input less than 5000").nullish()', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); + it('not null field', async () => { + const schema = buildSchema(/* GraphQL */ ` + input UserCreateInput { + profile: String! @constraint(minLength: 1, maxLength: 5000) + } + + directive @constraint(minLength: Int!, maxLength: Int!) on INPUT_FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + directives: { + constraint: { + minLength: ['min', '$1', 'Please input more than $1'], + maxLength: ['max', '$1', 'Please input less than $1'], + }, + }, + }, + {} + ); + const wantContains = [ + 'export function UserCreateInputSchema(): z.ZodObject>', + 'profile: z.string().min(1, "Please input more than 1").max(5000, "Please input less than 5000")', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); + it('list field', async () => { + const schema = buildSchema(/* GraphQL */ ` + input UserCreateInput { + profile: [String] @constraint(minLength: 1, maxLength: 5000) + } + + directive @constraint(minLength: Int!, maxLength: Int!) on INPUT_FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + directives: { + constraint: { + minLength: ['min', '$1', 'Please input more than $1'], + maxLength: ['max', '$1', 'Please input less than $1'], + }, + }, + }, + {} + ); + const wantContains = [ + 'export function UserCreateInputSchema(): z.ZodObject>', + 'profile: z.array(z.string().nullable()).min(1, "Please input more than 1").max(5000, "Please input less than 5000").nullish()', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); + }); });