Skip to content

Commit bb516a2

Browse files
committed
feat(myzod): support validationSchemaExportType
1 parent 0c5768c commit bb516a2

File tree

2 files changed

+164
-51
lines changed

2 files changed

+164
-51
lines changed

src/myzod/index.ts

Lines changed: 80 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const anySchema = `definedNonNullAnySchema`;
2121

2222
export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig): SchemaVisitor => {
2323
const importTypes: string[] = [];
24+
const enumDeclarations: string[] = [];
2425

2526
return {
2627
buildImports: (): string[] => {
@@ -37,6 +38,7 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
3738
[
3839
new DeclarationBlock({}).export().asKind('const').withName(`${anySchema}`).withContent(`myzod.object({})`)
3940
.string,
41+
...enumDeclarations,
4042
].join('\n'),
4143
InputObjectTypeDefinition: {
4244
leave: (node: InputObjectTypeDefinitionNode) => {
@@ -46,11 +48,22 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
4648

4749
const shape = node.fields?.map(field => generateFieldMyZodSchema(config, visitor, field, 2)).join(',\n');
4850

49-
return new DeclarationBlock({})
50-
.export()
51-
.asKind('function')
52-
.withName(`${name}Schema(): myzod.Type<${name}>`)
53-
.withBlock([indent(`return myzod.object({`), shape, indent('})')].join('\n')).string;
51+
switch (config.validationSchemaExportType) {
52+
case 'const':
53+
return new DeclarationBlock({})
54+
.export()
55+
.asKind('const')
56+
.withName(`${name}Schema: myzod.Type<${name}>`)
57+
.withContent(['myzod.object({', shape, '})'].join('\n')).string;
58+
59+
case 'function':
60+
default:
61+
return new DeclarationBlock({})
62+
.export()
63+
.asKind('function')
64+
.withName(`${name}Schema(): myzod.Type<${name}>`)
65+
.withBlock([indent(`return myzod.object({`), shape, indent('})')].join('\n')).string;
66+
}
5467
},
5568
},
5669
ObjectTypeDefinition: {
@@ -61,18 +74,36 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
6174

6275
const shape = node.fields?.map(field => generateFieldMyZodSchema(config, visitor, field, 2)).join(',\n');
6376

64-
return new DeclarationBlock({})
65-
.export()
66-
.asKind('function')
67-
.withName(`${name}Schema(): myzod.Type<${name}>`)
68-
.withBlock(
69-
[
70-
indent(`return myzod.object({`),
71-
indent(`__typename: myzod.literal('${node.name.value}').optional(),`, 2),
72-
shape,
73-
indent('})'),
74-
].join('\n')
75-
).string;
77+
switch (config.validationSchemaExportType) {
78+
case 'const':
79+
return new DeclarationBlock({})
80+
.export()
81+
.asKind('const')
82+
.withName(`${name}Schema: myzod.Type<${name}>`)
83+
.withContent(
84+
[
85+
`myzod.object({`,
86+
indent(`__typename: myzod.literal('${node.name.value}').optional(),`, 2),
87+
shape,
88+
'})',
89+
].join('\n')
90+
).string;
91+
92+
case 'function':
93+
default:
94+
return new DeclarationBlock({})
95+
.export()
96+
.asKind('function')
97+
.withName(`${name}Schema(): myzod.Type<${name}>`)
98+
.withBlock(
99+
[
100+
indent(`return myzod.object({`),
101+
indent(`__typename: myzod.literal('${node.name.value}').optional(),`, 2),
102+
shape,
103+
indent('})'),
104+
].join('\n')
105+
).string;
106+
}
76107
}),
77108
},
78109
EnumTypeDefinition: {
@@ -81,20 +112,22 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
81112
const enumname = visitor.convertName(node.name.value);
82113
importTypes.push(enumname);
83114
// z.enum are basically myzod.literals
84-
if (config.enumsAsTypes) {
85-
return new DeclarationBlock({})
86-
.export()
87-
.asKind('type')
88-
.withName(`${enumname}Schema`)
89-
.withContent(`myzod.literals(${node.values?.map(enumOption => `'${enumOption.name.value}'`).join(', ')})`)
90-
.string;
91-
}
92-
93-
return new DeclarationBlock({})
94-
.export()
95-
.asKind('const')
96-
.withName(`${enumname}Schema`)
97-
.withContent(`myzod.enum(${enumname})`).string;
115+
// hoist enum declarations
116+
enumDeclarations.push(
117+
config.enumsAsTypes
118+
? new DeclarationBlock({})
119+
.export()
120+
.asKind('type')
121+
.withName(`${enumname}Schema`)
122+
.withContent(
123+
`myzod.literals(${node.values?.map(enumOption => `'${enumOption.name.value}'`).join(', ')})`
124+
).string
125+
: new DeclarationBlock({})
126+
.export()
127+
.asKind('const')
128+
.withName(`${enumname}Schema`)
129+
.withContent(`myzod.enum(${enumname})`).string
130+
);
98131
},
99132
},
100133
UnionTypeDefinition: {
@@ -197,27 +230,23 @@ const generateNameNodeMyZodSchema = (
197230
): string => {
198231
const converter = visitor.getNameNodeConverter(node);
199232

200-
if (converter?.targetKind === 'InputObjectTypeDefinition') {
201-
const name = converter.convertName();
202-
return `${name}Schema()`;
203-
}
204-
205-
if (converter?.targetKind === 'ObjectTypeDefinition') {
206-
const name = converter.convertName();
207-
return `${name}Schema()`;
208-
}
209-
210-
if (converter?.targetKind === 'EnumTypeDefinition') {
211-
const name = converter.convertName();
212-
return `${name}Schema`;
213-
}
214-
215-
if (converter?.targetKind === 'UnionTypeDefinition') {
216-
const name = converter.convertName();
217-
return `${name}Schema()`;
233+
switch (converter?.targetKind) {
234+
case 'InputObjectTypeDefinition':
235+
case 'ObjectTypeDefinition':
236+
case 'UnionTypeDefinition':
237+
// using switch-case rather than if-else to allow for future expansion
238+
switch (config.validationSchemaExportType) {
239+
case 'const':
240+
return `${converter.convertName()}Schema`;
241+
case 'function':
242+
default:
243+
return `${converter.convertName()}Schema()`;
244+
}
245+
case 'EnumTypeDefinition':
246+
return `${converter.convertName()}Schema`;
247+
default:
248+
return myzod4Scalar(config, visitor, node.value);
218249
}
219-
220-
return myzod4Scalar(config, visitor, node.value);
221250
};
222251

223252
const maybeLazy = (type: TypeNode, schema: string): string => {

tests/myzod.spec.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,4 +768,88 @@ describe('myzod', () => {
768768
expect(result.content).toContain(wantContain);
769769
}
770770
});
771+
772+
it('exports as const instead of func', async () => {
773+
const schema = buildSchema(/* GraphQL */ `
774+
input Say {
775+
phrase: String!
776+
}
777+
`);
778+
const result = await plugin(
779+
schema,
780+
[],
781+
{
782+
schema: 'myzod',
783+
validationSchemaExportType: 'const',
784+
},
785+
{}
786+
);
787+
expect(result.content).toContain('export const SaySchema: myzod.Type<Say> = myzod.object({');
788+
});
789+
790+
it('generate both input & type, export as const', async () => {
791+
const schema = buildSchema(/* GraphQL */ `
792+
scalar Date
793+
scalar Email
794+
input UserCreateInput {
795+
name: String!
796+
date: Date!
797+
email: Email!
798+
}
799+
type User {
800+
id: ID!
801+
name: String
802+
age: Int
803+
email: Email
804+
isMember: Boolean
805+
createdAt: Date!
806+
}
807+
type Mutation {
808+
_empty: String
809+
}
810+
type Query {
811+
_empty: String
812+
}
813+
type Subscription {
814+
_empty: String
815+
}
816+
`);
817+
const result = await plugin(
818+
schema,
819+
[],
820+
{
821+
schema: 'myzod',
822+
withObjectType: true,
823+
scalarSchemas: {
824+
Date: 'myzod.date()',
825+
Email: 'myzod.string().email()',
826+
},
827+
validationSchemaExportType: 'const',
828+
},
829+
{}
830+
);
831+
const wantContains = [
832+
// User Create Input
833+
'export const UserCreateInputSchema: myzod.Type<UserCreateInput> = myzod.object({',
834+
'name: myzod.string(),',
835+
'date: myzod.date(),',
836+
'email: myzod.string().email()',
837+
// User
838+
'export const UserSchema: myzod.Type<User> = myzod.object({',
839+
"__typename: myzod.literal('User').optional(),",
840+
'id: myzod.string(),',
841+
'name: myzod.string().optional().nullable(),',
842+
'age: myzod.number().optional().nullable(),',
843+
'email: myzod.string().email().optional().nullable(),',
844+
'isMember: myzod.boolean().optional().nullable(),',
845+
'createdAt: myzod.date()',
846+
];
847+
for (const wantContain of wantContains) {
848+
expect(result.content).toContain(wantContain);
849+
}
850+
851+
for (const wantNotContain of ['Query', 'Mutation', 'Subscription']) {
852+
expect(result.content).not.toContain(wantNotContain);
853+
}
854+
});
771855
});

0 commit comments

Comments
 (0)