Skip to content

Commit 6b33b7c

Browse files
authored
Merge pull request #6 from Code-Hex/add/support-zod
support zod
2 parents 4651460 + 6e8cddd commit 6b33b7c

File tree

14 files changed

+569
-22
lines changed

14 files changed

+569
-22
lines changed

README.md

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[GraphQL code generator](https://github.com/dotansimha/graphql-code-generator) plugin to generate form validation schema from your GraphQL schema.
66

77
- [x] support [yup](https://github.com/jquense/yup)
8-
- [ ] support [zod](https://github.com/colinhacks/zod)
8+
- [x] support [zod](https://github.com/colinhacks/zod)
99

1010
## Quick Start
1111

@@ -26,7 +26,7 @@ generates:
2626
# see: https://www.graphql-code-generator.com/plugins/typescript
2727
strictScalars: true
2828
# You can also write the config for this plugin together
29-
schema: yup
29+
schema: yup # or zod
3030
```
3131
3232
You can check [example directory](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/tree/main/example) if you want to see more complex config example or how is generated some files.
@@ -39,6 +39,8 @@ type: `ValidationSchema` default: `'yup'`
3939

4040
Specify generete validation schema you want.
4141

42+
You can specify `yup` or `zod`.
43+
4244
```yml
4345
generates:
4446
path/to/graphql.ts:
@@ -87,6 +89,15 @@ type: `DirectiveConfig`
8789

8890
Generates validation schema with more API based on directive schema. For example, yaml config and GraphQL schema is here.
8991

92+
```graphql
93+
input ExampleInput {
94+
email: String! @required(msg: "Hello, World!") @constraint(minLength: 50, format: "email")
95+
message: String! @constraint(startsWith: "Hello")
96+
}
97+
```
98+
99+
#### yup
100+
90101
```yml
91102
generates:
92103
path/to/graphql.ts:
@@ -114,13 +125,6 @@ generates:
114125
email: email
115126
```
116127
117-
```graphql
118-
input ExampleInput {
119-
email: String! @required(msg: "Hello, World!") @constraint(minLength: 50, format: "email")
120-
message: String! @constraint(startsWith: "Hello")
121-
}
122-
```
123-
124128
Then generates yup validation schema like below.
125129
126130
```ts
@@ -131,3 +135,42 @@ export function ExampleInputSchema(): yup.SchemaOf<ExampleInput> {
131135
})
132136
}
133137
```
138+
139+
#### zod
140+
141+
142+
```yml
143+
generates:
144+
path/to/graphql.ts:
145+
plugins:
146+
- typescript
147+
- graphql-codegen-validation-schema
148+
config:
149+
schema: zod
150+
directives:
151+
# Write directives like
152+
#
153+
# directive:
154+
# arg1: schemaApi
155+
# arg2: ["schemaApi2", "Hello $1"]
156+
#
157+
# See more examples in `./tests/directive.spec.ts`
158+
# https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/blob/main/tests/directive.spec.ts
159+
constraint:
160+
minLength: min
161+
# Replace $1 with specified `startsWith` argument value of the constraint directive
162+
startsWith: ["regex", "/^$1/", "message"]
163+
format:
164+
email: email
165+
```
166+
167+
Then generates yup validation schema like below.
168+
169+
```ts
170+
export function ExampleInputSchema(): z.ZodSchema<ExampleInput> {
171+
return z.object({
172+
email: z.string().min(50).email(),
173+
message: z.string().regex(/^Hello/, "message")
174+
})
175+
}
176+
```

codegen.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,23 @@ generates:
3737
max: ['max', '$1 + 1']
3838
exclusiveMin: min
3939
exclusiveMax: max
40+
example/zod/schemas.ts:
41+
plugins:
42+
- ./dist/main/index.js:
43+
schema: zod
44+
importFrom: ../types
45+
directives:
46+
# Write directives like
47+
#
48+
# directive:
49+
# arg1: schemaApi
50+
# arg2: ["schemaApi2", "Hello $1"]
51+
#
52+
# See more examples in `./tests/directive.spec.ts`
53+
# https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/blob/main/tests/directive.spec.ts
54+
constraint:
55+
minLength: min
56+
# Replace $1 with specified `startsWith` argument value of the constraint directive
57+
startsWith: ['regex', '/^$1/', 'message']
58+
format:
59+
email: email

example/zod/schemas.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { z } from 'zod'
2+
import { AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, HttpInput, HttpMethod, LayoutInput, PageInput, PageType } from '../types'
3+
4+
type definedNonNullAny = {};
5+
6+
export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null;
7+
8+
export const definedNonNullAnySchema: z.ZodSchema<definedNonNullAny> = z.any().refine((v) => isDefinedNonNullAny(v));
9+
10+
export function AttributeInputSchema(): z.ZodSchema<AttributeInput> {
11+
return z.object({
12+
key: z.string().nullish(),
13+
val: z.string().nullish()
14+
})
15+
}
16+
17+
export const ButtonComponentTypeSchema = z.nativeEnum(ButtonComponentType);
18+
19+
export function ComponentInputSchema(): z.ZodSchema<ComponentInput> {
20+
return z.object({
21+
child: z.lazy(() => ComponentInputSchema().nullish()),
22+
childrens: z.array(z.lazy(() => ComponentInputSchema().nullable())).nullish(),
23+
event: z.lazy(() => EventInputSchema().nullish()),
24+
name: z.string(),
25+
type: ButtonComponentTypeSchema
26+
})
27+
}
28+
29+
export function DropDownComponentInputSchema(): z.ZodSchema<DropDownComponentInput> {
30+
return z.object({
31+
dropdownComponent: z.lazy(() => ComponentInputSchema().nullish()),
32+
getEvent: z.lazy(() => EventInputSchema())
33+
})
34+
}
35+
36+
export function EventArgumentInputSchema(): z.ZodSchema<EventArgumentInput> {
37+
return z.object({
38+
name: z.string().min(5),
39+
value: z.string().regex(/^foo/, "message")
40+
})
41+
}
42+
43+
export function EventInputSchema(): z.ZodSchema<EventInput> {
44+
return z.object({
45+
arguments: z.array(z.lazy(() => EventArgumentInputSchema())),
46+
options: z.array(EventOptionTypeSchema).nullish()
47+
})
48+
}
49+
50+
export const EventOptionTypeSchema = z.nativeEnum(EventOptionType);
51+
52+
export function HttpInputSchema(): z.ZodSchema<HttpInput> {
53+
return z.object({
54+
method: HttpMethodSchema.nullish(),
55+
url: definedNonNullAnySchema
56+
})
57+
}
58+
59+
export const HttpMethodSchema = z.nativeEnum(HttpMethod);
60+
61+
export function LayoutInputSchema(): z.ZodSchema<LayoutInput> {
62+
return z.object({
63+
dropdown: z.lazy(() => DropDownComponentInputSchema().nullish())
64+
})
65+
}
66+
67+
export function PageInputSchema(): z.ZodSchema<PageInput> {
68+
return z.object({
69+
attributes: z.array(z.lazy(() => AttributeInputSchema())).nullish(),
70+
date: definedNonNullAnySchema.nullish(),
71+
height: z.number(),
72+
id: z.string(),
73+
layout: z.lazy(() => LayoutInputSchema()),
74+
pageType: PageTypeSchema,
75+
postIDs: z.array(z.string()).nullish(),
76+
show: z.boolean(),
77+
tags: z.array(z.string().nullable()).nullish(),
78+
title: z.string(),
79+
width: z.number()
80+
})
81+
}
82+
83+
export const PageTypeSchema = z.nativeEnum(PageType);

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@
5656
"prettier": "2.5.1",
5757
"ts-jest": "^27.1.3",
5858
"typescript": "^4.5.4",
59-
"yup": "^0.32.11"
59+
"yup": "^0.32.11",
60+
"zod": "^3.11.6"
6061
},
6162
"dependencies": {
6263
"@graphql-codegen/plugin-helpers": "^2.3.2",

src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TypeScriptPluginConfig } from '@graphql-codegen/typescript';
22

3-
export type ValidationSchema = 'yup';
3+
export type ValidationSchema = 'yup' | 'zod';
44

55
export interface DirectiveConfig {
66
[directive: string]: {

src/directive.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export const formatDirectiveObjectArguments = (args: DirectiveObjectArguments):
109109
// ```
110110
export const buildApi = (config: FormattedDirectiveConfig, directives: ReadonlyArray<ConstDirectiveNode>): string =>
111111
directives
112+
.filter(directive => config[directive.name.value] !== undefined)
112113
.map(directive => {
113114
const directiveName = directive.name.value;
114115
const argsConfig = config[directiveName];

src/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ZodSchemaVisitor } from './zod/index';
12
import { transformSchemaAST } from '@graphql-codegen/schema-ast';
23
import { YupSchemaVisitor } from './yup/index';
34
import { ValidationSchemaPluginConfig } from './config';
@@ -10,7 +11,7 @@ export const plugin: PluginFunction<ValidationSchemaPluginConfig, Types.ComplexP
1011
config: ValidationSchemaPluginConfig
1112
): Types.ComplexPluginOutput => {
1213
const { schema: _schema, ast } = transformSchemaAST(schema, config);
13-
const { buildImports, ...visitor } = YupSchemaVisitor(_schema, config);
14+
const { buildImports, initialEmit, ...visitor } = schemaVisitor(_schema, config);
1415

1516
const result = oldVisit(ast, {
1617
leave: visitor,
@@ -22,6 +23,13 @@ export const plugin: PluginFunction<ValidationSchemaPluginConfig, Types.ComplexP
2223

2324
return {
2425
prepend: buildImports(),
25-
content: '\n' + [...generated].join('\n'),
26+
content: [initialEmit(), ...generated].join('\n'),
2627
};
2728
};
29+
30+
const schemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig) => {
31+
if (config?.schema === 'zod') {
32+
return ZodSchemaVisitor(schema, config);
33+
}
34+
return YupSchemaVisitor(schema, config);
35+
};

src/yup/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
2626
}
2727
return [importYup];
2828
},
29+
initialEmit: (): string => '',
2930
InputObjectTypeDefinition: (node: InputObjectTypeDefinitionNode) => {
3031
const name = tsVisitor.convertName(node.name.value);
3132
importTypes.push(name);

0 commit comments

Comments
 (0)