Skip to content

support zod #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jan 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 52 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[GraphQL code generator](https://github.com/dotansimha/graphql-code-generator) plugin to generate form validation schema from your GraphQL schema.

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

## Quick Start

Expand All @@ -26,7 +26,7 @@ generates:
# see: https://www.graphql-code-generator.com/plugins/typescript
strictScalars: true
# You can also write the config for this plugin together
schema: yup
schema: yup # or zod
```

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.
Expand All @@ -39,6 +39,8 @@ type: `ValidationSchema` default: `'yup'`

Specify generete validation schema you want.

You can specify `yup` or `zod`.

```yml
generates:
path/to/graphql.ts:
Expand Down Expand Up @@ -87,6 +89,15 @@ type: `DirectiveConfig`

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

```graphql
input ExampleInput {
email: String! @required(msg: "Hello, World!") @constraint(minLength: 50, format: "email")
message: String! @constraint(startsWith: "Hello")
}
```

#### yup

```yml
generates:
path/to/graphql.ts:
Expand Down Expand Up @@ -114,13 +125,6 @@ generates:
email: email
```

```graphql
input ExampleInput {
email: String! @required(msg: "Hello, World!") @constraint(minLength: 50, format: "email")
message: String! @constraint(startsWith: "Hello")
}
```

Then generates yup validation schema like below.

```ts
Expand All @@ -131,3 +135,42 @@ export function ExampleInputSchema(): yup.SchemaOf<ExampleInput> {
})
}
```

#### zod


```yml
generates:
path/to/graphql.ts:
plugins:
- typescript
- graphql-codegen-validation-schema
config:
schema: zod
directives:
# Write directives like
#
# directive:
# arg1: schemaApi
# arg2: ["schemaApi2", "Hello $1"]
#
# See more examples in `./tests/directive.spec.ts`
# https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/blob/main/tests/directive.spec.ts
constraint:
minLength: min
# Replace $1 with specified `startsWith` argument value of the constraint directive
startsWith: ["regex", "/^$1/", "message"]
format:
email: email
```

Then generates yup validation schema like below.

```ts
export function ExampleInputSchema(): z.ZodSchema<ExampleInput> {
return z.object({
email: z.string().min(50).email(),
message: z.string().regex(/^Hello/, "message")
})
}
```
20 changes: 20 additions & 0 deletions codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,23 @@ generates:
max: ['max', '$1 + 1']
exclusiveMin: min
exclusiveMax: max
example/zod/schemas.ts:
plugins:
- ./dist/main/index.js:
schema: zod
importFrom: ../types
directives:
# Write directives like
#
# directive:
# arg1: schemaApi
# arg2: ["schemaApi2", "Hello $1"]
#
# See more examples in `./tests/directive.spec.ts`
# https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/blob/main/tests/directive.spec.ts
constraint:
minLength: min
# Replace $1 with specified `startsWith` argument value of the constraint directive
startsWith: ['regex', '/^$1/', 'message']
format:
email: email
83 changes: 83 additions & 0 deletions example/zod/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { z } from 'zod'
import { AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, HttpInput, HttpMethod, LayoutInput, PageInput, PageType } from '../types'

type definedNonNullAny = {};

export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null;

export const definedNonNullAnySchema: z.ZodSchema<definedNonNullAny> = z.any().refine((v) => isDefinedNonNullAny(v));

export function AttributeInputSchema(): z.ZodSchema<AttributeInput> {
return z.object({
key: z.string().nullish(),
val: z.string().nullish()
})
}

export const ButtonComponentTypeSchema = z.nativeEnum(ButtonComponentType);

export function ComponentInputSchema(): z.ZodSchema<ComponentInput> {
return z.object({
child: z.lazy(() => ComponentInputSchema().nullish()),
childrens: z.array(z.lazy(() => ComponentInputSchema().nullable())).nullish(),
event: z.lazy(() => EventInputSchema().nullish()),
name: z.string(),
type: ButtonComponentTypeSchema
})
}

export function DropDownComponentInputSchema(): z.ZodSchema<DropDownComponentInput> {
return z.object({
dropdownComponent: z.lazy(() => ComponentInputSchema().nullish()),
getEvent: z.lazy(() => EventInputSchema())
})
}

export function EventArgumentInputSchema(): z.ZodSchema<EventArgumentInput> {
return z.object({
name: z.string().min(5),
value: z.string().regex(/^foo/, "message")
})
}

export function EventInputSchema(): z.ZodSchema<EventInput> {
return z.object({
arguments: z.array(z.lazy(() => EventArgumentInputSchema())),
options: z.array(EventOptionTypeSchema).nullish()
})
}

export const EventOptionTypeSchema = z.nativeEnum(EventOptionType);

export function HttpInputSchema(): z.ZodSchema<HttpInput> {
return z.object({
method: HttpMethodSchema.nullish(),
url: definedNonNullAnySchema
})
}

export const HttpMethodSchema = z.nativeEnum(HttpMethod);

export function LayoutInputSchema(): z.ZodSchema<LayoutInput> {
return z.object({
dropdown: z.lazy(() => DropDownComponentInputSchema().nullish())
})
}

export function PageInputSchema(): z.ZodSchema<PageInput> {
return z.object({
attributes: z.array(z.lazy(() => AttributeInputSchema())).nullish(),
date: definedNonNullAnySchema.nullish(),
height: z.number(),
id: z.string(),
layout: z.lazy(() => LayoutInputSchema()),
pageType: PageTypeSchema,
postIDs: z.array(z.string()).nullish(),
show: z.boolean(),
tags: z.array(z.string().nullable()).nullish(),
title: z.string(),
width: z.number()
})
}

export const PageTypeSchema = z.nativeEnum(PageType);
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"prettier": "2.5.1",
"ts-jest": "^27.1.3",
"typescript": "^4.5.4",
"yup": "^0.32.11"
"yup": "^0.32.11",
"zod": "^3.11.6"
},
"dependencies": {
"@graphql-codegen/plugin-helpers": "^2.3.2",
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TypeScriptPluginConfig } from '@graphql-codegen/typescript';

export type ValidationSchema = 'yup';
export type ValidationSchema = 'yup' | 'zod';

export interface DirectiveConfig {
[directive: string]: {
Expand Down
1 change: 1 addition & 0 deletions src/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const formatDirectiveObjectArguments = (args: DirectiveObjectArguments):
// ```
export const buildApi = (config: FormattedDirectiveConfig, directives: ReadonlyArray<ConstDirectiveNode>): string =>
directives
.filter(directive => config[directive.name.value] !== undefined)
.map(directive => {
const directiveName = directive.name.value;
const argsConfig = config[directiveName];
Expand Down
12 changes: 10 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ZodSchemaVisitor } from './zod/index';
import { transformSchemaAST } from '@graphql-codegen/schema-ast';
import { YupSchemaVisitor } from './yup/index';
import { ValidationSchemaPluginConfig } from './config';
Expand All @@ -10,7 +11,7 @@ export const plugin: PluginFunction<ValidationSchemaPluginConfig, Types.ComplexP
config: ValidationSchemaPluginConfig
): Types.ComplexPluginOutput => {
const { schema: _schema, ast } = transformSchemaAST(schema, config);
const { buildImports, ...visitor } = YupSchemaVisitor(_schema, config);
const { buildImports, initialEmit, ...visitor } = schemaVisitor(_schema, config);

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

return {
prepend: buildImports(),
content: '\n' + [...generated].join('\n'),
content: [initialEmit(), ...generated].join('\n'),
};
};

const schemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig) => {
if (config?.schema === 'zod') {
return ZodSchemaVisitor(schema, config);
}
return YupSchemaVisitor(schema, config);
};
1 change: 1 addition & 0 deletions src/yup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const YupSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchema
}
return [importYup];
},
initialEmit: (): string => '',
InputObjectTypeDefinition: (node: InputObjectTypeDefinitionNode) => {
const name = tsVisitor.convertName(node.name.value);
importTypes.push(name);
Expand Down
Loading