Skip to content

Commit efe8628

Browse files
authored
Merge pull request #237 from alisabzevari/zod-unions
Support union types for zod generator
2 parents ba00c44 + 06bbc90 commit efe8628

File tree

6 files changed

+366
-1
lines changed

6 files changed

+366
-1
lines changed

src/myzod/index.ts

+22
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ObjectTypeDefinitionNode,
1010
EnumTypeDefinitionNode,
1111
FieldDefinitionNode,
12+
UnionTypeDefinitionNode,
1213
} from 'graphql';
1314
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
1415
import { TsVisitor } from '@graphql-codegen/typescript';
@@ -89,6 +90,22 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
8990
.withName(`${enumname}Schema`)
9091
.withContent(`myzod.enum(${enumname})`).string;
9192
},
93+
UnionTypeDefinition: (node: UnionTypeDefinitionNode) => {
94+
const unionName = tsVisitor.convertName(node.name.value);
95+
const unionElements = node.types?.map(t => `${tsVisitor.convertName(t.name.value)}Schema()`).join(', ');
96+
const unionElementsCount = node.types?.length ?? 0;
97+
98+
const union =
99+
unionElementsCount > 1 ? indent(`return myzod.union([${unionElements}])`) : indent(`return ${unionElements}`);
100+
101+
const result = new DeclarationBlock({})
102+
.export()
103+
.asKind('function')
104+
.withName(`${unionName}Schema()`)
105+
.withBlock(union);
106+
107+
return result.string;
108+
},
92109
};
93110
};
94111

@@ -181,6 +198,11 @@ const generateNameNodeMyZodSchema = (
181198
return `${enumName}Schema`;
182199
}
183200

201+
if (typ?.astNode?.kind === 'UnionTypeDefinition') {
202+
const enumName = tsVisitor.convertName(typ.astNode.name.value);
203+
return `${enumName}Schema()`;
204+
}
205+
184206
return myzod4Scalar(config, tsVisitor, node.value);
185207
};
186208

src/yup/index.ts

+30-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
EnumTypeDefinitionNode,
1010
ObjectTypeDefinitionNode,
1111
FieldDefinitionNode,
12+
UnionTypeDefinitionNode,
1213
} from 'graphql';
1314
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
1415
import { TsVisitor } from '@graphql-codegen/typescript';
@@ -28,7 +29,17 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
2829
}
2930
return [importYup];
3031
},
31-
initialEmit: (): string => '',
32+
initialEmit: (): string =>
33+
new DeclarationBlock({})
34+
.asKind('function')
35+
.withName('union<T>(...schemas: ReadonlyArray<yup.SchemaOf<T>>): yup.BaseSchema<T>')
36+
.withBlock(
37+
[
38+
indent('return yup.mixed().test({'),
39+
indent('test: (value) => schemas.some((schema) => schema.isValidSync(value))', 2),
40+
indent('})'),
41+
].join('\n')
42+
).string,
3243
InputObjectTypeDefinition: (node: InputObjectTypeDefinitionNode) => {
3344
const name = tsVisitor.convertName(node.name.value);
3445
importTypes.push(name);
@@ -89,6 +100,24 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
89100
.withName(`${enumname}Schema`)
90101
.withContent(`yup.mixed().oneOf([${values}])`).string;
91102
},
103+
UnionTypeDefinition: (node: UnionTypeDefinitionNode) => {
104+
const unionName = tsVisitor.convertName(node.name.value);
105+
const unionElements = node.types?.map(t => `${tsVisitor.convertName(t.name.value)}Schema()`).join(', ');
106+
const unionElementsCount = node.types?.length ?? 0;
107+
108+
const union =
109+
unionElementsCount > 1
110+
? indent(`return union<${unionName}>(${unionElements})`)
111+
: indent(`return ${unionElements}`);
112+
113+
const result = new DeclarationBlock({})
114+
.export()
115+
.asKind('function')
116+
.withName(`${unionName}Schema(): yup.BaseSchema<${unionName}>`)
117+
.withBlock(union);
118+
119+
return result.string;
120+
},
92121
// ScalarTypeDefinition: (node) => {
93122
// const decl = new DeclarationBlock({})
94123
// .export()

src/zod/index.ts

+22
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
InputObjectTypeDefinitionNode,
99
ObjectTypeDefinitionNode,
1010
EnumTypeDefinitionNode,
11+
UnionTypeDefinitionNode,
1112
FieldDefinitionNode,
1213
} from 'graphql';
1314
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
@@ -100,6 +101,22 @@ export const ZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
100101
.withName(`${enumname}Schema`)
101102
.withContent(`z.nativeEnum(${enumname})`).string;
102103
},
104+
UnionTypeDefinition: (node: UnionTypeDefinitionNode) => {
105+
const unionName = tsVisitor.convertName(node.name.value);
106+
const unionElements = node.types?.map(t => `${tsVisitor.convertName(t.name.value)}Schema()`).join(', ');
107+
const unionElementsCount = node.types?.length ?? 0;
108+
109+
const union =
110+
unionElementsCount > 1 ? indent(`return z.union([${unionElements}])`) : indent(`return ${unionElements}`);
111+
112+
const result = new DeclarationBlock({})
113+
.export()
114+
.asKind('function')
115+
.withName(`${unionName}Schema()`)
116+
.withBlock(union);
117+
118+
return result.string;
119+
},
103120
};
104121
};
105122

@@ -192,6 +209,11 @@ const generateNameNodeZodSchema = (
192209
return `${enumName}Schema`;
193210
}
194211

212+
if (typ?.astNode?.kind === 'UnionTypeDefinition') {
213+
const enumName = tsVisitor.convertName(typ.astNode.name.value);
214+
return `${enumName}Schema()`;
215+
}
216+
195217
return zod4Scalar(config, tsVisitor, node.value);
196218
};
197219

tests/myzod.spec.ts

+98
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,104 @@ describe('myzod', () => {
538538
expect(result.content).not.toContain(wantNotContain);
539539
}
540540
});
541+
542+
it('generate union types', async () => {
543+
const schema = buildSchema(/* GraphQL */ `
544+
type Square {
545+
size: Int
546+
}
547+
type Circle {
548+
radius: Int
549+
}
550+
union Shape = Circle | Square
551+
`);
552+
553+
const result = await plugin(
554+
schema,
555+
[],
556+
{
557+
schema: 'myzod',
558+
withObjectType: true,
559+
},
560+
{}
561+
);
562+
563+
const wantContains = [
564+
// Shape Schema
565+
'export function ShapeSchema() {',
566+
'myzod.union([CircleSchema(), SquareSchema()])',
567+
'}',
568+
];
569+
for (const wantContain of wantContains) {
570+
expect(result.content).toContain(wantContain);
571+
}
572+
});
573+
574+
it('generate union types with single element', async () => {
575+
const schema = buildSchema(/* GraphQL */ `
576+
type Square {
577+
size: Int
578+
}
579+
type Circle {
580+
radius: Int
581+
}
582+
union Shape = Circle | Square
583+
584+
type Geometry {
585+
shape: Shape
586+
}
587+
`);
588+
589+
const result = await plugin(
590+
schema,
591+
[],
592+
{
593+
schema: 'myzod',
594+
withObjectType: true,
595+
},
596+
{}
597+
);
598+
599+
const wantContains = [
600+
'export function GeometrySchema(): myzod.Type<Geometry> {',
601+
'return myzod.object({',
602+
"__typename: myzod.literal('Geometry').optional(),",
603+
'shape: ShapeSchema().optional().nullable()',
604+
'}',
605+
];
606+
for (const wantContain of wantContains) {
607+
expect(result.content).toContain(wantContain);
608+
}
609+
});
610+
611+
it('correctly reference generated union types', async () => {
612+
const schema = buildSchema(/* GraphQL */ `
613+
type Circle {
614+
radius: Int
615+
}
616+
union Shape = Circle
617+
`);
618+
619+
const result = await plugin(
620+
schema,
621+
[],
622+
{
623+
schema: 'myzod',
624+
withObjectType: true,
625+
},
626+
{}
627+
);
628+
629+
const wantContains = [
630+
// Shape Schema
631+
'export function ShapeSchema() {',
632+
'CircleSchema()',
633+
'}',
634+
];
635+
for (const wantContain of wantContains) {
636+
expect(result.content).toContain(wantContain);
637+
}
638+
});
541639
});
542640

543641
it('properly generates custom directive values', async () => {

tests/yup.spec.ts

+98
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,104 @@ describe('yup', () => {
451451
expect(result.content).not.toContain(wantNotContain);
452452
}
453453
});
454+
455+
it('generate union types', async () => {
456+
const schema = buildSchema(/* GraphQL */ `
457+
type Square {
458+
size: Int
459+
}
460+
type Circle {
461+
radius: Int
462+
}
463+
union Shape = Circle | Square
464+
`);
465+
466+
const result = await plugin(
467+
schema,
468+
[],
469+
{
470+
schema: 'yup',
471+
withObjectType: true,
472+
},
473+
{}
474+
);
475+
476+
const wantContains = [
477+
// Shape Schema
478+
'export function ShapeSchema(): yup.BaseSchema<Shape> {',
479+
'union<Shape>(CircleSchema(), SquareSchema())',
480+
'}',
481+
];
482+
for (const wantContain of wantContains) {
483+
expect(result.content).toContain(wantContain);
484+
}
485+
});
486+
487+
it('generate union types with single element', async () => {
488+
const schema = buildSchema(/* GraphQL */ `
489+
type Square {
490+
size: Int
491+
}
492+
type Circle {
493+
radius: Int
494+
}
495+
union Shape = Circle | Square
496+
497+
type Geometry {
498+
shape: Shape
499+
}
500+
`);
501+
502+
const result = await plugin(
503+
schema,
504+
[],
505+
{
506+
schema: 'yup',
507+
withObjectType: true,
508+
},
509+
{}
510+
);
511+
512+
const wantContains = [
513+
'export function GeometrySchema(): yup.SchemaOf<Geometry> {',
514+
'return yup.object({',
515+
"__typename: yup.mixed().oneOf(['Geometry', undefined]),",
516+
'shape: yup.mixed()',
517+
'})',
518+
];
519+
for (const wantContain of wantContains) {
520+
expect(result.content).toContain(wantContain);
521+
}
522+
});
523+
524+
it('correctly reference generated union types', async () => {
525+
const schema = buildSchema(/* GraphQL */ `
526+
type Circle {
527+
radius: Int
528+
}
529+
union Shape = Circle
530+
`);
531+
532+
const result = await plugin(
533+
schema,
534+
[],
535+
{
536+
schema: 'yup',
537+
withObjectType: true,
538+
},
539+
{}
540+
);
541+
542+
const wantContains = [
543+
// Shape Schema
544+
'export function ShapeSchema(): yup.BaseSchema<Shape> {',
545+
'CircleSchema()',
546+
'}',
547+
];
548+
for (const wantContain of wantContains) {
549+
expect(result.content).toContain(wantContain);
550+
}
551+
});
454552
});
455553

456554
it('properly generates custom directive values', async () => {

0 commit comments

Comments
 (0)