Skip to content

Commit 2dde904

Browse files
committed
feat(yup): support validationSchemaExportType
1 parent bb516a2 commit 2dde904

File tree

2 files changed

+175
-59
lines changed

2 files changed

+175
-59
lines changed

src/yup/index.ts

Lines changed: 91 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const importYup = `import * as yup from 'yup'`;
2020

2121
export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig): SchemaVisitor => {
2222
const importTypes: string[] = [];
23+
const enumDeclarations: string[] = [];
2324

2425
return {
2526
buildImports: (): string[] => {
@@ -32,8 +33,10 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
3233
return [importYup];
3334
},
3435
initialEmit: (): string => {
35-
if (!config.withObjectType) return '';
36+
if (!config.withObjectType) return '\n' + enumDeclarations.join('\n');
3637
return (
38+
'\n' +
39+
enumDeclarations.join('\n') +
3740
'\n' +
3841
new DeclarationBlock({})
3942
.asKind('function')
@@ -60,11 +63,22 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
6063
})
6164
.join(',\n');
6265

63-
return new DeclarationBlock({})
64-
.export()
65-
.asKind('function')
66-
.withName(`${name}Schema(): yup.ObjectSchema<${name}>`)
67-
.withBlock([indent(`return yup.object({`), shape, indent('})')].join('\n')).string;
66+
switch (config.validationSchemaExportType) {
67+
case 'const':
68+
return new DeclarationBlock({})
69+
.export()
70+
.asKind('const')
71+
.withName(`${name}Schema: yup.ObjectSchema<${name}>`)
72+
.withContent(['yup.object({', shape, '})'].join('\n')).string;
73+
74+
case 'function':
75+
default:
76+
return new DeclarationBlock({})
77+
.export()
78+
.asKind('function')
79+
.withName(`${name}Schema(): yup.ObjectSchema<${name}>`)
80+
.withBlock([indent(`return yup.object({`), shape, indent('})')].join('\n')).string;
81+
}
6882
},
6983
},
7084
ObjectTypeDefinition: {
@@ -80,18 +94,36 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
8094
})
8195
.join(',\n');
8296

83-
return new DeclarationBlock({})
84-
.export()
85-
.asKind('function')
86-
.withName(`${name}Schema(): yup.ObjectSchema<${name}>`)
87-
.withBlock(
88-
[
89-
indent(`return yup.object({`),
90-
indent(`__typename: yup.string<'${node.name.value}'>().optional(),`, 2),
91-
shape,
92-
indent('})'),
93-
].join('\n')
94-
).string;
97+
switch (config.validationSchemaExportType) {
98+
case 'const':
99+
return new DeclarationBlock({})
100+
.export()
101+
.asKind('const')
102+
.withName(`${name}Schema: yup.ObjectSchema<${name}>`)
103+
.withContent(
104+
[
105+
`yup.object({`,
106+
indent(`__typename: yup.string<'${node.name.value}'>().optional(),`, 2),
107+
shape,
108+
'})',
109+
].join('\n')
110+
).string;
111+
112+
case 'function':
113+
default:
114+
return new DeclarationBlock({})
115+
.export()
116+
.asKind('function')
117+
.withName(`${name}Schema(): yup.ObjectSchema<${name}>`)
118+
.withBlock(
119+
[
120+
indent(`return yup.object({`),
121+
indent(`__typename: yup.string<'${node.name.value}'>().optional(),`, 2),
122+
shape,
123+
indent('})'),
124+
].join('\n')
125+
).string;
126+
}
95127
}),
96128
},
97129
EnumTypeDefinition: {
@@ -100,30 +132,35 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
100132
const enumname = visitor.convertName(node.name.value);
101133
importTypes.push(enumname);
102134

135+
// hoise enum declarations
103136
if (config.enumsAsTypes) {
104137
const enums = node.values?.map(enumOption => `'${enumOption.name.value}'`);
105138

106-
return new DeclarationBlock({})
107-
.export()
108-
.asKind('const')
109-
.withName(`${enumname}Schema`)
110-
.withContent(`yup.string().oneOf([${enums?.join(', ')}]).defined()`).string;
139+
enumDeclarations.push(
140+
new DeclarationBlock({})
141+
.export()
142+
.asKind('const')
143+
.withName(`${enumname}Schema`)
144+
.withContent(`yup.string().oneOf([${enums?.join(', ')}]).defined()`).string
145+
);
146+
} else {
147+
const values = node.values
148+
?.map(
149+
enumOption =>
150+
`${enumname}.${visitor.convertName(enumOption.name, {
151+
useTypesPrefix: false,
152+
transformUnderscore: true,
153+
})}`
154+
)
155+
.join(', ');
156+
enumDeclarations.push(
157+
new DeclarationBlock({})
158+
.export()
159+
.asKind('const')
160+
.withName(`${enumname}Schema`)
161+
.withContent(`yup.string<${enumname}>().oneOf([${values}]).defined()`).string
162+
);
111163
}
112-
113-
const values = node.values
114-
?.map(
115-
enumOption =>
116-
`${enumname}.${visitor.convertName(enumOption.name, {
117-
useTypesPrefix: false,
118-
transformUnderscore: true,
119-
})}`
120-
)
121-
.join(', ');
122-
return new DeclarationBlock({})
123-
.export()
124-
.asKind('const')
125-
.withName(`${enumname}Schema`)
126-
.withContent(`yup.string<${enumname}>().oneOf([${values}]).defined()`).string;
127164
},
128165
},
129166
UnionTypeDefinition: {
@@ -228,28 +265,23 @@ const generateFieldTypeYupSchema = (
228265
const generateNameNodeYupSchema = (config: ValidationSchemaPluginConfig, visitor: Visitor, node: NameNode): string => {
229266
const converter = visitor.getNameNodeConverter(node);
230267

231-
if (converter?.targetKind === 'InputObjectTypeDefinition') {
232-
const name = converter.convertName();
233-
return `${name}Schema()`;
234-
}
235-
236-
if (converter?.targetKind === 'ObjectTypeDefinition') {
237-
const name = converter.convertName();
238-
return `${name}Schema()`;
239-
}
240-
241-
if (converter?.targetKind === 'EnumTypeDefinition') {
242-
const name = converter.convertName();
243-
return `${name}Schema`;
244-
}
245-
246-
if (converter?.targetKind === 'UnionTypeDefinition') {
247-
const name = converter.convertName();
248-
return `${name}Schema()`;
268+
switch (converter?.targetKind) {
269+
case 'InputObjectTypeDefinition':
270+
case 'ObjectTypeDefinition':
271+
case 'UnionTypeDefinition':
272+
// using switch-case rather than if-else to allow for future expansion
273+
switch (config.validationSchemaExportType) {
274+
case 'const':
275+
return `${converter.convertName()}Schema`;
276+
case 'function':
277+
default:
278+
return `${converter.convertName()}Schema()`;
279+
}
280+
case 'EnumTypeDefinition':
281+
return `${converter.convertName()}Schema`;
282+
default:
283+
return yup4Scalar(config, visitor, node.value);
249284
}
250-
251-
const primitive = yup4Scalar(config, visitor, node.value);
252-
return primitive;
253285
};
254286

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

tests/yup.spec.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,4 +682,88 @@ describe('yup', () => {
682682
expect(result.content).toContain(wantContain);
683683
}
684684
});
685+
686+
it('exports as const instead of func', async () => {
687+
const schema = buildSchema(/* GraphQL */ `
688+
input Say {
689+
phrase: String!
690+
}
691+
`);
692+
const result = await plugin(
693+
schema,
694+
[],
695+
{
696+
schema: 'yup',
697+
validationSchemaExportType: 'const',
698+
},
699+
{}
700+
);
701+
expect(result.content).toContain('export const SaySchema: yup.ObjectSchema<Say> = yup.object({');
702+
});
703+
704+
it('generate both input & type, export as const', async () => {
705+
const schema = buildSchema(/* GraphQL */ `
706+
scalar Date
707+
scalar Email
708+
input UserCreateInput {
709+
name: String!
710+
date: Date!
711+
email: Email!
712+
}
713+
type User {
714+
id: ID!
715+
name: String
716+
age: Int
717+
email: Email
718+
isMember: Boolean
719+
createdAt: Date!
720+
}
721+
type Mutation {
722+
_empty: String
723+
}
724+
type Query {
725+
_empty: String
726+
}
727+
type Subscription {
728+
_empty: String
729+
}
730+
`);
731+
const result = await plugin(
732+
schema,
733+
[],
734+
{
735+
schema: 'yup',
736+
withObjectType: true,
737+
scalarSchemas: {
738+
Date: 'yup.date()',
739+
Email: 'yup.string().email()',
740+
},
741+
validationSchemaExportType: 'const',
742+
},
743+
{}
744+
);
745+
const wantContains = [
746+
// User Create Input
747+
'export const UserCreateInputSchema: yup.ObjectSchema<UserCreateInput> = yup.object({',
748+
'name: yup.string().defined().nonNullable(),',
749+
'date: yup.date().defined().nonNullable(),',
750+
'email: yup.string().email().defined().nonNullable()',
751+
// User
752+
'export const UserSchema: yup.ObjectSchema<User> = yup.object({',
753+
"__typename: yup.string<'User'>().optional(),",
754+
'id: yup.string().defined().nonNullable(),',
755+
'name: yup.string().defined().nullable().optional(),',
756+
'age: yup.number().defined().nullable().optional(),',
757+
'email: yup.string().email().defined().nullable().optional(),',
758+
'isMember: yup.boolean().defined().nullable().optional(),',
759+
'createdAt: yup.date().defined().nonNullable()',
760+
];
761+
for (const wantContain of wantContains) {
762+
expect(result.content).toContain(wantContain);
763+
}
764+
765+
for (const wantNotContain of ['Query', 'Mutation', 'Subscription']) {
766+
expect(result.content).not.toContain(wantNotContain);
767+
}
768+
});
685769
});

0 commit comments

Comments
 (0)