From a30764af1533bb098565c9c7660e867d83edbcdd Mon Sep 17 00:00:00 2001 From: Ali Sabzevari Date: Sun, 13 Nov 2022 23:02:07 +0100 Subject: [PATCH 1/5] support union types for zod generator --- src/zod/index.ts | 13 +++++++++++++ tests/zod.spec.ts | 31 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/zod/index.ts b/src/zod/index.ts index 093578fc..b3a07351 100644 --- a/src/zod/index.ts +++ b/src/zod/index.ts @@ -8,6 +8,7 @@ import { InputObjectTypeDefinitionNode, ObjectTypeDefinitionNode, EnumTypeDefinitionNode, + UnionTypeDefinitionNode, FieldDefinitionNode, } from 'graphql'; import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common'; @@ -100,6 +101,18 @@ export const ZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema .withName(`${enumname}Schema`) .withContent(`z.nativeEnum(${enumname})`).string; }, + UnionTypeDefinition: (node: UnionTypeDefinitionNode) => { + const unionName = tsVisitor.convertName(node.name.value); + const unionElements = node.types?.map(t => `${t.name.value}Schema()`).join(', '); + + const result = new DeclarationBlock({}) + .export() + .asKind('function') + .withName(`${unionName}Schema()`) + .withBlock(indent(`return z.union([${unionElements}])`)); + + return result.string; + }, }; }; diff --git a/tests/zod.spec.ts b/tests/zod.spec.ts index c48008aa..12a66b99 100644 --- a/tests/zod.spec.ts +++ b/tests/zod.spec.ts @@ -636,6 +636,37 @@ describe('zod', () => { expect(result.content).not.toContain(wantNotContain); } }); + + it('generate union types', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Square { + size: Int + } + type Circle { + radius: Int + } + union Shape = Circle | Square + `); + + const result = await plugin( + schema, + [], + { + schema: 'zod', + withObjectType: true, + }, + {} + ); + + const wantContains = [ + // Shape Schema + 'export function ShapeSchema() {', + 'z.union([CircleSchema(), SquareSchema()])', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); }); it('properly generates custom directive values', async () => { From 36ef0073f1e4eba7baebb4b18a4642c3dae03c60 Mon Sep 17 00:00:00 2001 From: Ali Sabzevari Date: Fri, 18 Nov 2022 17:22:32 +0100 Subject: [PATCH 2/5] Add support for union in myzod Co-authored-by: alexandruluca --- src/myzod/index.ts | 13 +++++++++++++ tests/myzod.spec.ts | 32 ++++++++++++++++++++++++++++++++ tests/zod.spec.ts | 1 + 3 files changed, 46 insertions(+) diff --git a/src/myzod/index.ts b/src/myzod/index.ts index fd975454..68edae66 100644 --- a/src/myzod/index.ts +++ b/src/myzod/index.ts @@ -9,6 +9,7 @@ import { ObjectTypeDefinitionNode, EnumTypeDefinitionNode, FieldDefinitionNode, + UnionTypeDefinitionNode, } from 'graphql'; import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common'; import { TsVisitor } from '@graphql-codegen/typescript'; @@ -89,6 +90,18 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche .withName(`${enumname}Schema`) .withContent(`myzod.enum(${enumname})`).string; }, + UnionTypeDefinition: (node: UnionTypeDefinitionNode) => { + const unionName = tsVisitor.convertName(node.name.value); + const unionElements = node.types?.map(t => `${t.name.value}Schema()`).join(', '); + + const result = new DeclarationBlock({}) + .export() + .asKind('function') + .withName(`${unionName}Schema()`) + .withBlock(indent(`return myzod.union([${unionElements}])`)); + + return result.string; + }, }; }; diff --git a/tests/myzod.spec.ts b/tests/myzod.spec.ts index b4e3d827..2d4e2a5c 100644 --- a/tests/myzod.spec.ts +++ b/tests/myzod.spec.ts @@ -538,6 +538,38 @@ describe('myzod', () => { expect(result.content).not.toContain(wantNotContain); } }); + + it('generate union types', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Square { + size: Int + } + type Circle { + radius: Int + } + union Shape = Circle | Square + `); + + const result = await plugin( + schema, + [], + { + schema: 'myzod', + withObjectType: true, + }, + {} + ); + + const wantContains = [ + // Shape Schema + 'export function ShapeSchema() {', + 'myzod.union([CircleSchema(), SquareSchema()])', + '}', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); }); it('properly generates custom directive values', async () => { diff --git a/tests/zod.spec.ts b/tests/zod.spec.ts index 12a66b99..bcadabd0 100644 --- a/tests/zod.spec.ts +++ b/tests/zod.spec.ts @@ -662,6 +662,7 @@ describe('zod', () => { // Shape Schema 'export function ShapeSchema() {', 'z.union([CircleSchema(), SquareSchema()])', + '}', ]; for (const wantContain of wantContains) { expect(result.content).toContain(wantContain); From 510d9905d280f9c9820feccbe91fcc3979db42d7 Mon Sep 17 00:00:00 2001 From: Ali Sabzevari Date: Fri, 18 Nov 2022 19:40:53 +0100 Subject: [PATCH 3/5] fix some edge cases --- src/myzod/index.ts | 13 +++++++-- src/zod/index.ts | 13 +++++++-- tests/myzod.spec.ts | 66 +++++++++++++++++++++++++++++++++++++++++++++ tests/zod.spec.ts | 66 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+), 4 deletions(-) diff --git a/src/myzod/index.ts b/src/myzod/index.ts index 68edae66..e36abcd6 100644 --- a/src/myzod/index.ts +++ b/src/myzod/index.ts @@ -92,13 +92,17 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche }, UnionTypeDefinition: (node: UnionTypeDefinitionNode) => { const unionName = tsVisitor.convertName(node.name.value); - const unionElements = node.types?.map(t => `${t.name.value}Schema()`).join(', '); + const unionElements = node.types?.map(t => `${tsVisitor.convertName(t.name.value)}Schema()`).join(', '); + const unionElementsCount = node.types?.length ?? 0; + + const union = + unionElementsCount > 1 ? indent(`return myzod.union([${unionElements}])`) : indent(`return ${unionElements}`); const result = new DeclarationBlock({}) .export() .asKind('function') .withName(`${unionName}Schema()`) - .withBlock(indent(`return myzod.union([${unionElements}])`)); + .withBlock(union); return result.string; }, @@ -194,6 +198,11 @@ const generateNameNodeMyZodSchema = ( return `${enumName}Schema`; } + if (typ?.astNode?.kind === 'UnionTypeDefinition') { + const enumName = tsVisitor.convertName(typ.astNode.name.value); + return `${enumName}Schema()`; + } + return myzod4Scalar(config, tsVisitor, node.value); }; diff --git a/src/zod/index.ts b/src/zod/index.ts index b3a07351..08bad3d5 100644 --- a/src/zod/index.ts +++ b/src/zod/index.ts @@ -103,13 +103,17 @@ export const ZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema }, UnionTypeDefinition: (node: UnionTypeDefinitionNode) => { const unionName = tsVisitor.convertName(node.name.value); - const unionElements = node.types?.map(t => `${t.name.value}Schema()`).join(', '); + const unionElements = node.types?.map(t => `${tsVisitor.convertName(t.name.value)}Schema()`).join(', '); + const unionElementsCount = node.types?.length ?? 0; + + const union = + unionElementsCount > 1 ? indent(`return z.union([${unionElements}])`) : indent(`return ${unionElements}`); const result = new DeclarationBlock({}) .export() .asKind('function') .withName(`${unionName}Schema()`) - .withBlock(indent(`return z.union([${unionElements}])`)); + .withBlock(union); return result.string; }, @@ -205,6 +209,11 @@ const generateNameNodeZodSchema = ( return `${enumName}Schema`; } + if (typ?.astNode?.kind === 'UnionTypeDefinition') { + const enumName = tsVisitor.convertName(typ.astNode.name.value); + return `${enumName}Schema()`; + } + return zod4Scalar(config, tsVisitor, node.value); }; diff --git a/tests/myzod.spec.ts b/tests/myzod.spec.ts index 2d4e2a5c..c4e950cd 100644 --- a/tests/myzod.spec.ts +++ b/tests/myzod.spec.ts @@ -570,6 +570,72 @@ describe('myzod', () => { expect(result.content).toContain(wantContain); } }); + + it('generate union types with single element', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Square { + size: Int + } + type Circle { + radius: Int + } + union Shape = Circle | Square + + type Geometry { + shape: Shape + } + `); + + const result = await plugin( + schema, + [], + { + schema: 'myzod', + withObjectType: true, + }, + {} + ); + + const wantContains = [ + 'export function GeometrySchema(): myzod.Type {', + 'return myzod.object({', + "__typename: myzod.literal('Geometry').optional(),", + 'shape: ShapeSchema().optional().nullable()', + '}', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); + + it('correctly reference generated union types', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Circle { + radius: Int + } + union Shape = Circle + `); + + const result = await plugin( + schema, + [], + { + schema: 'myzod', + withObjectType: true, + }, + {} + ); + + const wantContains = [ + // Shape Schema + 'export function ShapeSchema() {', + 'CircleSchema()', + '}', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); }); it('properly generates custom directive values', async () => { diff --git a/tests/zod.spec.ts b/tests/zod.spec.ts index bcadabd0..a4fc913c 100644 --- a/tests/zod.spec.ts +++ b/tests/zod.spec.ts @@ -668,6 +668,72 @@ describe('zod', () => { expect(result.content).toContain(wantContain); } }); + + it('generate union types with single element', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Square { + size: Int + } + type Circle { + radius: Int + } + union Shape = Circle | Square + + type Geometry { + shape: Shape + } + `); + + const result = await plugin( + schema, + [], + { + schema: 'zod', + withObjectType: true, + }, + {} + ); + + const wantContains = [ + 'export function GeometrySchema(): z.ZodObject> {', + 'return z.object({', + "__typename: z.literal('Geometry').optional(),", + 'shape: ShapeSchema().nullish()', + '}', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); + + it('correctly reference generated union types', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Circle { + radius: Int + } + union Shape = Circle + `); + + const result = await plugin( + schema, + [], + { + schema: 'zod', + withObjectType: true, + }, + {} + ); + + const wantContains = [ + // Shape Schema + 'export function ShapeSchema() {', + 'CircleSchema()', + '}', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); }); it('properly generates custom directive values', async () => { From 63b6a11299113945a99e424118583aa5fae092db Mon Sep 17 00:00:00 2001 From: Ali Sabzevari Date: Mon, 21 Nov 2022 12:39:15 +0100 Subject: [PATCH 4/5] Add support for unions in yup --- src/yup/index.ts | 31 ++++++++++++++- tests/yup.spec.ts | 98 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/src/yup/index.ts b/src/yup/index.ts index 807ac82c..401bcf3b 100644 --- a/src/yup/index.ts +++ b/src/yup/index.ts @@ -9,6 +9,7 @@ import { EnumTypeDefinitionNode, ObjectTypeDefinitionNode, FieldDefinitionNode, + UnionTypeDefinitionNode, } from 'graphql'; import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common'; import { TsVisitor } from '@graphql-codegen/typescript'; @@ -28,7 +29,17 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema } return [importYup]; }, - initialEmit: (): string => '', + initialEmit: (): string => + new DeclarationBlock({}) + .asKind('function') + .withName('union(...schemas: ReadonlyArray>): yup.BaseSchema') + .withBlock( + [ + indent('return yup.mixed().test({'), + indent('test: (value) => schemas.some((schema) => schema.isValidSync(value))', 2), + indent('})'), + ].join('\n') + ).string, InputObjectTypeDefinition: (node: InputObjectTypeDefinitionNode) => { const name = tsVisitor.convertName(node.name.value); importTypes.push(name); @@ -89,6 +100,24 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema .withName(`${enumname}Schema`) .withContent(`yup.mixed().oneOf([${values}])`).string; }, + UnionTypeDefinition: (node: UnionTypeDefinitionNode) => { + const unionName = tsVisitor.convertName(node.name.value); + const unionElements = node.types?.map(t => `${tsVisitor.convertName(t.name.value)}Schema()`).join(', '); + const unionElementsCount = node.types?.length ?? 0; + + const union = + unionElementsCount > 1 + ? indent(`return union<${unionName}>(${unionElements})`) + : indent(`return ${unionElements}`); + + const result = new DeclarationBlock({}) + .export() + .asKind('function') + .withName(`${unionName}Schema(): yup.BaseSchema<${unionName}>`) + .withBlock(union); + + return result.string; + }, // ScalarTypeDefinition: (node) => { // const decl = new DeclarationBlock({}) // .export() diff --git a/tests/yup.spec.ts b/tests/yup.spec.ts index 6a8d5462..e7697237 100644 --- a/tests/yup.spec.ts +++ b/tests/yup.spec.ts @@ -451,6 +451,104 @@ describe('yup', () => { expect(result.content).not.toContain(wantNotContain); } }); + + it('generate union types', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Square { + size: Int + } + type Circle { + radius: Int + } + union Shape = Circle | Square + `); + + const result = await plugin( + schema, + [], + { + schema: 'yup', + withObjectType: true, + }, + {} + ); + + const wantContains = [ + // Shape Schema + 'export function ShapeSchema(): yup.BaseSchema {', + 'union(CircleSchema(), SquareSchema())', + '}', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); + + it('generate union types with single element', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Square { + size: Int + } + type Circle { + radius: Int + } + union Shape = Circle | Square + + type Geometry { + shape: Shape + } + `); + + const result = await plugin( + schema, + [], + { + schema: 'yup', + withObjectType: true, + }, + {} + ); + + const wantContains = [ + 'export function GeometrySchema(): yup.SchemaOf {', + 'return yup.object({', + "__typename: yup.mixed().oneOf(['Geometry', undefined]),", + 'shape: yup.mixed()', + '})', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); + + it('correctly reference generated union types', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Circle { + radius: Int + } + union Shape = Circle + `); + + const result = await plugin( + schema, + [], + { + schema: 'yup', + withObjectType: true, + }, + {} + ); + + const wantContains = [ + // Shape Schema + 'export function ShapeSchema(): yup.BaseSchema {', + 'CircleSchema()', + '}', + ]; + for (const wantContain of wantContains) { + expect(result.content).toContain(wantContain); + } + }); }); it('properly generates custom directive values', async () => { From 06bbc909a10172485366d8328b0192966c8ebe89 Mon Sep 17 00:00:00 2001 From: Ali Sabzevari Date: Tue, 22 Nov 2022 21:27:33 +0100 Subject: [PATCH 5/5] fix test --- tests/zod.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/zod.spec.ts b/tests/zod.spec.ts index a4fc913c..e6f4556d 100644 --- a/tests/zod.spec.ts +++ b/tests/zod.spec.ts @@ -696,10 +696,8 @@ describe('zod', () => { const wantContains = [ 'export function GeometrySchema(): z.ZodObject> {', - 'return z.object({', "__typename: z.literal('Geometry').optional(),", 'shape: ShapeSchema().nullish()', - '}', ]; for (const wantContain of wantContains) { expect(result.content).toContain(wantContain);