Skip to content

Commit f9019b2

Browse files
authored
Merge pull request #132 from anhdd-kuro/main
Adding GraphQL type support to codegen - with test
2 parents e1e8e17 + 538fa5b commit f9019b2

File tree

13 files changed

+391
-34
lines changed

13 files changed

+391
-34
lines changed

codegen.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ generates:
99
- ./dist/main/index.js:
1010
schema: yup
1111
importFrom: ../types
12+
useObjectTypes: true
1213
directives:
1314
required:
1415
msg: required
@@ -42,6 +43,7 @@ generates:
4243
- ./dist/main/index.js:
4344
schema: zod
4445
importFrom: ../types
46+
useObjectTypes: true
4547
directives:
4648
# Write directives like
4749
#
@@ -62,6 +64,7 @@ generates:
6264
- ./dist/main/index.js:
6365
schema: myzod
6466
importFrom: ../types
67+
useObjectTypes: true
6568
directives:
6669
constraint:
6770
minLength: min

example/myzod/schemas.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as myzod from 'myzod'
2-
import { AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, HttpInput, HttpMethod, LayoutInput, PageInput, PageType } from '../types'
2+
import { AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, HttpInput, HttpMethod, LayoutInput, PageInput, PageType, User } from '../types'
33

44
export const definedNonNullAnySchema = myzod.object({});
55

@@ -77,3 +77,15 @@ export function PageInputSchema(): myzod.Type<PageInput> {
7777
}
7878

7979
export const PageTypeSchema = myzod.enum(PageType);
80+
81+
export function UserSchema(): myzod.Type<User> {
82+
return myzod.object({
83+
__typename: myzod.literal('User').optional(),
84+
createdAt: definedNonNullAnySchema.optional().nullable(),
85+
email: myzod.string().optional().nullable(),
86+
id: myzod.string().optional().nullable(),
87+
name: myzod.string().optional().nullable(),
88+
password: myzod.string().optional().nullable(),
89+
updatedAt: definedNonNullAnySchema.optional().nullable()
90+
})
91+
}

example/test.graphql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ enum PageType {
55
BASIC_AUTH
66
}
77

8+
type User {
9+
id: ID
10+
name: String
11+
email: String
12+
password: String
13+
createdAt: Date
14+
updatedAt: Date
15+
}
16+
817
input PageInput {
918
id: ID!
1019
title: String!

example/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,13 @@ export enum PageType {
8686
Restricted = 'RESTRICTED',
8787
Service = 'SERVICE'
8888
}
89+
90+
export type User = {
91+
__typename?: 'User';
92+
createdAt?: Maybe<Scalars['Date']>;
93+
email?: Maybe<Scalars['String']>;
94+
id?: Maybe<Scalars['ID']>;
95+
name?: Maybe<Scalars['String']>;
96+
password?: Maybe<Scalars['String']>;
97+
updatedAt?: Maybe<Scalars['Date']>;
98+
};

example/yup/schemas.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as yup from 'yup'
2-
import { AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, HttpInput, HttpMethod, LayoutInput, PageInput, PageType } from '../types'
2+
import { AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, HttpInput, HttpMethod, LayoutInput, PageInput, PageType, User } from '../types'
33

44
export function AttributeInputSchema(): yup.SchemaOf<AttributeInput> {
55
return yup.object({
@@ -75,3 +75,15 @@ export function PageInputSchema(): yup.SchemaOf<PageInput> {
7575
}
7676

7777
export const PageTypeSchema = yup.mixed().oneOf([PageType.BasicAuth, PageType.Lp, PageType.Restricted, PageType.Service]);
78+
79+
export function UserSchema(): yup.SchemaOf<User> {
80+
return yup.object({
81+
__typename: yup.mixed().oneOf(['User', undefined]),
82+
createdAt: yup.mixed(),
83+
email: yup.string(),
84+
id: yup.string(),
85+
name: yup.string(),
86+
password: yup.string(),
87+
updatedAt: yup.mixed()
88+
})
89+
}

example/zod/schemas.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { z } from 'zod'
2-
import { AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, HttpInput, HttpMethod, LayoutInput, PageInput, PageType } from '../types'
2+
import { AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, HttpInput, HttpMethod, LayoutInput, PageInput, PageType, User } from '../types'
33

44
type Properties<T> = Required<{
55
[K in keyof T]: z.ZodType<T[K], any, T[K]>;
@@ -85,3 +85,15 @@ export function PageInputSchema(): z.ZodObject<Properties<PageInput>> {
8585
}
8686

8787
export const PageTypeSchema = z.nativeEnum(PageType);
88+
89+
export function UserSchema(): z.ZodObject<Properties<User>> {
90+
return z.object({
91+
__typename: z.literal('User').optional(),
92+
createdAt: definedNonNullAnySchema.nullish(),
93+
email: z.string().nullish(),
94+
id: z.string().nullish(),
95+
name: z.string().nullish(),
96+
password: z.string().nullish(),
97+
updatedAt: definedNonNullAnySchema.nullish()
98+
})
99+
}

src/config.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export interface ValidationSchemaPluginConfig extends TypeScriptPluginConfig {
3737
* @description import types from generated typescript type path
3838
* if not given, omit import statement.
3939
*
40-
* @exampeMarkdown
40+
* @exampleMarkdown
4141
* ```yml
4242
* generates:
4343
* path/to/types.ts:
@@ -193,4 +193,22 @@ export interface ValidationSchemaPluginConfig extends TypeScriptPluginConfig {
193193
* ```
194194
*/
195195
directives?: DirectiveConfig;
196+
/**
197+
* @description Converts the regular graphql type into a zod validation function.
198+
*
199+
* @exampleMarkdown
200+
* ```yml
201+
* generates:
202+
* path/to/types.ts:
203+
* plugins:
204+
* - typescript
205+
* path/to/schemas.ts:
206+
* plugins:
207+
* - graphql-codegen-validation-schema
208+
* config:
209+
* schema: yup
210+
* useObjectTypes: true
211+
* ```
212+
*/
213+
useObjectTypes?: boolean;
196214
}

src/myzod/index.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {
66
TypeNode,
77
GraphQLSchema,
88
InputObjectTypeDefinitionNode,
9+
ObjectTypeDefinitionNode,
910
EnumTypeDefinitionNode,
11+
FieldDefinitionNode,
1012
} from 'graphql';
1113
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
1214
import { TsVisitor } from '@graphql-codegen/typescript';
@@ -38,7 +40,7 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
3840
importTypes.push(name);
3941

4042
const shape = node.fields
41-
?.map(field => generateInputObjectFieldMyZodSchema(config, tsVisitor, schema, field, 2))
43+
?.map(field => generateFieldMyZodSchema(config, tsVisitor, schema, field, 2))
4244
.join(',\n');
4345

4446
return new DeclarationBlock({})
@@ -47,6 +49,27 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
4749
.withName(`${name}Schema(): myzod.Type<${name}>`)
4850
.withBlock([indent(`return myzod.object({`), shape, indent('})')].join('\n')).string;
4951
},
52+
ObjectTypeDefinition: (node: ObjectTypeDefinitionNode) => {
53+
if (!config.useObjectTypes) return;
54+
const name = tsVisitor.convertName(node.name.value);
55+
importTypes.push(name);
56+
57+
const shape = node.fields
58+
?.map(field => generateFieldMyZodSchema(config, tsVisitor, schema, field, 2))
59+
.join(',\n');
60+
61+
return new DeclarationBlock({})
62+
.export()
63+
.asKind('function')
64+
.withName(`${name}Schema(): myzod.Type<${name}>`)
65+
.withBlock(
66+
[
67+
indent(`return myzod.object({`),
68+
` __typename: myzod.literal('${node.name.value}').optional(),\n${shape}`,
69+
indent('})'),
70+
].join('\n')
71+
).string;
72+
},
5073
EnumTypeDefinition: (node: EnumTypeDefinitionNode) => {
5174
const enumname = tsVisitor.convertName(node.name.value);
5275
importTypes.push(enumname);
@@ -69,27 +92,27 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
6992
};
7093
};
7194

72-
const generateInputObjectFieldMyZodSchema = (
95+
const generateFieldMyZodSchema = (
7396
config: ValidationSchemaPluginConfig,
7497
tsVisitor: TsVisitor,
7598
schema: GraphQLSchema,
76-
field: InputValueDefinitionNode,
99+
field: InputValueDefinitionNode | FieldDefinitionNode,
77100
indentCount: number
78101
): string => {
79-
const gen = generateInputObjectFieldTypeMyZodSchema(config, tsVisitor, schema, field, field.type);
102+
const gen = generateFieldTypeMyZodSchema(config, tsVisitor, schema, field, field.type);
80103
return indent(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount);
81104
};
82105

83-
const generateInputObjectFieldTypeMyZodSchema = (
106+
const generateFieldTypeMyZodSchema = (
84107
config: ValidationSchemaPluginConfig,
85108
tsVisitor: TsVisitor,
86109
schema: GraphQLSchema,
87-
field: InputValueDefinitionNode,
110+
field: InputValueDefinitionNode | FieldDefinitionNode,
88111
type: TypeNode,
89112
parentType?: TypeNode
90113
): string => {
91114
if (isListType(type)) {
92-
const gen = generateInputObjectFieldTypeMyZodSchema(config, tsVisitor, schema, field, type.type, type);
115+
const gen = generateFieldTypeMyZodSchema(config, tsVisitor, schema, field, type.type, type);
93116
if (!isNonNullType(parentType)) {
94117
const arrayGen = `myzod.array(${maybeLazy(type.type, gen)})`;
95118
const maybeLazyGen = applyDirectives(config, field, arrayGen);
@@ -98,7 +121,7 @@ const generateInputObjectFieldTypeMyZodSchema = (
98121
return `myzod.array(${maybeLazy(type.type, gen)})`;
99122
}
100123
if (isNonNullType(type)) {
101-
const gen = generateInputObjectFieldTypeMyZodSchema(config, tsVisitor, schema, field, type.type, type);
124+
const gen = generateFieldTypeMyZodSchema(config, tsVisitor, schema, field, type.type, type);
102125
return maybeLazy(type.type, gen);
103126
}
104127
if (isNamedType(type)) {
@@ -125,7 +148,7 @@ const generateInputObjectFieldTypeMyZodSchema = (
125148

126149
const applyDirectives = (
127150
config: ValidationSchemaPluginConfig,
128-
field: InputValueDefinitionNode,
151+
field: InputValueDefinitionNode | FieldDefinitionNode,
129152
gen: string
130153
): string => {
131154
if (config.directives && field.directives) {

src/yup/index.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
GraphQLSchema,
88
InputObjectTypeDefinitionNode,
99
EnumTypeDefinitionNode,
10+
ObjectTypeDefinitionNode,
11+
FieldDefinitionNode,
1012
} from 'graphql';
1113
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
1214
import { TsVisitor } from '@graphql-codegen/typescript';
@@ -31,16 +33,33 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
3133
const name = tsVisitor.convertName(node.name.value);
3234
importTypes.push(name);
3335

34-
const shape = node.fields
35-
?.map(field => generateInputObjectFieldYupSchema(config, tsVisitor, schema, field, 2))
36-
.join(',\n');
36+
const shape = node.fields?.map(field => generateFieldYupSchema(config, tsVisitor, schema, field, 2)).join(',\n');
3737

3838
return new DeclarationBlock({})
3939
.export()
4040
.asKind('function')
4141
.withName(`${name}Schema(): yup.SchemaOf<${name}>`)
4242
.withBlock([indent(`return yup.object({`), shape, indent('})')].join('\n')).string;
4343
},
44+
ObjectTypeDefinition: (node: ObjectTypeDefinitionNode) => {
45+
if (!config.useObjectTypes) return;
46+
const name = tsVisitor.convertName(node.name.value);
47+
importTypes.push(name);
48+
49+
const shape = node.fields?.map(field => generateFieldYupSchema(config, tsVisitor, schema, field, 2)).join(',\n');
50+
51+
return new DeclarationBlock({})
52+
.export()
53+
.asKind('function')
54+
.withName(`${name}Schema(): yup.SchemaOf<${name}>`)
55+
.withBlock(
56+
[
57+
indent(`return yup.object({`),
58+
` __typename: yup.mixed().oneOf(['${node.name.value}', undefined]),\n${shape}`,
59+
indent('})'),
60+
].join('\n')
61+
).string;
62+
},
4463
EnumTypeDefinition: (node: EnumTypeDefinitionNode) => {
4564
const enumname = tsVisitor.convertName(node.name.value);
4665
importTypes.push(enumname);
@@ -92,37 +111,37 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
92111
};
93112
};
94113

95-
const generateInputObjectFieldYupSchema = (
114+
const generateFieldYupSchema = (
96115
config: ValidationSchemaPluginConfig,
97116
tsVisitor: TsVisitor,
98117
schema: GraphQLSchema,
99-
field: InputValueDefinitionNode,
118+
field: InputValueDefinitionNode | FieldDefinitionNode,
100119
indentCount: number
101120
): string => {
102-
let gen = generateInputObjectFieldTypeYupSchema(config, tsVisitor, schema, field.type);
121+
let gen = generateFieldTypeYupSchema(config, tsVisitor, schema, field.type);
103122
if (config.directives && field.directives) {
104123
const formatted = formatDirectiveConfig(config.directives);
105124
gen += buildApi(formatted, field.directives);
106125
}
107126
return indent(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount);
108127
};
109128

110-
const generateInputObjectFieldTypeYupSchema = (
129+
const generateFieldTypeYupSchema = (
111130
config: ValidationSchemaPluginConfig,
112131
tsVisitor: TsVisitor,
113132
schema: GraphQLSchema,
114133
type: TypeNode,
115134
parentType?: TypeNode
116135
): string => {
117136
if (isListType(type)) {
118-
const gen = generateInputObjectFieldTypeYupSchema(config, tsVisitor, schema, type.type, type);
137+
const gen = generateFieldTypeYupSchema(config, tsVisitor, schema, type.type, type);
119138
if (!isNonNullType(parentType)) {
120139
return `yup.array().of(${maybeLazy(type.type, gen)}).optional()`;
121140
}
122141
return `yup.array().of(${maybeLazy(type.type, gen)})`;
123142
}
124143
if (isNonNullType(type)) {
125-
const gen = generateInputObjectFieldTypeYupSchema(config, tsVisitor, schema, type.type, type);
144+
const gen = generateFieldTypeYupSchema(config, tsVisitor, schema, type.type, type);
126145
const nonNullGen = maybeNonEmptyString(config, tsVisitor, gen, type.type);
127146
return maybeLazy(type.type, nonNullGen);
128147
}

0 commit comments

Comments
 (0)