Skip to content

Commit 8d2b58a

Browse files
authored
Merge pull request #375 from Code-Hex/fix/373
fix 373
2 parents 4b80770 + 0684828 commit 8d2b58a

File tree

10 files changed

+683
-555
lines changed

10 files changed

+683
-555
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,16 @@ generates:
2626
# You can put the config for typescript plugin here
2727
# see: https://www.graphql-code-generator.com/plugins/typescript
2828
strictScalars: true
29+
# Overrides built-in ID scalar to both input and output types as string.
30+
# see: https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#scalars
31+
scalars:
32+
ID: string
2933
# You can also write the config for this plugin together
3034
schema: yup # or zod
3135
```
3236
37+
It is recommended to write `scalars` config for built-in type `ID`, as in the yaml example shown above. For more information: [#375](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/pull/375)
38+
3339
You can check [example](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/tree/main/example) directory if you want to see more complex config example or how is generated some files.
3440

3541
The Q&A for each schema is written in the README in the respective example directory.

codegen.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ generates:
44
example/types.ts:
55
plugins:
66
- typescript
7+
config:
8+
scalars:
9+
ID: string
710
example/yup/schemas.ts:
811
plugins:
912
- ./dist/main/index.js:
@@ -38,6 +41,8 @@ generates:
3841
max: ['max', '$1 + 1']
3942
exclusiveMin: min
4043
exclusiveMax: max
44+
scalars:
45+
ID: string
4146
example/zod/schemas.ts:
4247
plugins:
4348
- ./dist/main/index.js:
@@ -59,6 +64,8 @@ generates:
5964
startsWith: ['regex', '/^$1/', 'message']
6065
format:
6166
email: email
67+
scalars:
68+
ID: string
6269
example/myzod/schemas.ts:
6370
plugins:
6471
- ./dist/main/index.js:
@@ -72,3 +79,5 @@ generates:
7279
startsWith: ['pattern', '/^$1/']
7380
format:
7481
email: email
82+
scalars:
83+
ID: string

example/types.ts

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,27 @@ export type InputMaybe<T> = Maybe<T>;
33
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
44
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
55
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
6+
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
7+
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
68
/** All built-in and custom scalars, mapped to their actual values */
79
export type Scalars = {
8-
ID: string;
9-
String: string;
10-
Boolean: boolean;
11-
Int: number;
12-
Float: number;
13-
Date: any;
14-
URL: any;
10+
ID: { input: string; output: string; }
11+
String: { input: string; output: string; }
12+
Boolean: { input: boolean; output: boolean; }
13+
Int: { input: number; output: number; }
14+
Float: { input: number; output: number; }
15+
Date: { input: any; output: any; }
16+
URL: { input: any; output: any; }
1517
};
1618

1719
export type Admin = {
1820
__typename?: 'Admin';
19-
lastModifiedAt?: Maybe<Scalars['Date']>;
21+
lastModifiedAt?: Maybe<Scalars['Date']['output']>;
2022
};
2123

2224
export type AttributeInput = {
23-
key?: InputMaybe<Scalars['String']>;
24-
val?: InputMaybe<Scalars['String']>;
25+
key?: InputMaybe<Scalars['String']['input']>;
26+
val?: InputMaybe<Scalars['String']['input']>;
2527
};
2628

2729
export enum ButtonComponentType {
@@ -33,7 +35,7 @@ export type ComponentInput = {
3335
child?: InputMaybe<ComponentInput>;
3436
childrens?: InputMaybe<Array<InputMaybe<ComponentInput>>>;
3537
event?: InputMaybe<EventInput>;
36-
name: Scalars['String'];
38+
name: Scalars['String']['input'];
3739
type: ButtonComponentType;
3840
};
3941

@@ -43,8 +45,8 @@ export type DropDownComponentInput = {
4345
};
4446

4547
export type EventArgumentInput = {
46-
name: Scalars['String'];
47-
value: Scalars['String'];
48+
name: Scalars['String']['input'];
49+
value: Scalars['String']['input'];
4850
};
4951

5052
export type EventInput = {
@@ -59,12 +61,12 @@ export enum EventOptionType {
5961

6062
export type Guest = {
6163
__typename?: 'Guest';
62-
lastLoggedIn?: Maybe<Scalars['Date']>;
64+
lastLoggedIn?: Maybe<Scalars['Date']['output']>;
6365
};
6466

6567
export type HttpInput = {
6668
method?: InputMaybe<HttpMethod>;
67-
url: Scalars['URL'];
69+
url: Scalars['URL']['input'];
6870
};
6971

7072
export enum HttpMethod {
@@ -78,16 +80,16 @@ export type LayoutInput = {
7880

7981
export type PageInput = {
8082
attributes?: InputMaybe<Array<AttributeInput>>;
81-
date?: InputMaybe<Scalars['Date']>;
82-
height: Scalars['Float'];
83-
id: Scalars['ID'];
83+
date?: InputMaybe<Scalars['Date']['input']>;
84+
height: Scalars['Float']['input'];
85+
id: Scalars['ID']['input'];
8486
layout: LayoutInput;
8587
pageType: PageType;
86-
postIDs?: InputMaybe<Array<Scalars['ID']>>;
87-
show: Scalars['Boolean'];
88-
tags?: InputMaybe<Array<InputMaybe<Scalars['String']>>>;
89-
title: Scalars['String'];
90-
width: Scalars['Int'];
88+
postIDs?: InputMaybe<Array<Scalars['ID']['input']>>;
89+
show: Scalars['Boolean']['input'];
90+
tags?: InputMaybe<Array<InputMaybe<Scalars['String']['input']>>>;
91+
title: Scalars['String']['input'];
92+
width: Scalars['Int']['input'];
9193
};
9294

9395
export enum PageType {
@@ -99,13 +101,13 @@ export enum PageType {
99101

100102
export type User = {
101103
__typename?: 'User';
102-
createdAt?: Maybe<Scalars['Date']>;
103-
email?: Maybe<Scalars['String']>;
104-
id?: Maybe<Scalars['ID']>;
104+
createdAt?: Maybe<Scalars['Date']['output']>;
105+
email?: Maybe<Scalars['String']['output']>;
106+
id?: Maybe<Scalars['ID']['output']>;
105107
kind?: Maybe<UserKind>;
106-
name?: Maybe<Scalars['String']>;
107-
password?: Maybe<Scalars['String']>;
108-
updatedAt?: Maybe<Scalars['Date']>;
108+
name?: Maybe<Scalars['String']['output']>;
109+
password?: Maybe<Scalars['String']['output']>;
110+
updatedAt?: Maybe<Scalars['Date']['output']>;
109111
};
110112

111113
export type UserKind = Admin | Guest;

src/myzod/index.ts

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,14 @@ import {
1212
UnionTypeDefinitionNode,
1313
} from 'graphql';
1414
import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common';
15-
import { TsVisitor } from '@graphql-codegen/typescript';
15+
import { Visitor } from '../visitor';
1616
import { buildApi, formatDirectiveConfig } from '../directive';
1717
import { SchemaVisitor } from '../types';
1818

1919
const importZod = `import * as myzod from 'myzod'`;
2020
const anySchema = `definedNonNullAnySchema`;
2121

2222
export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig): SchemaVisitor => {
23-
const tsVisitor = new TsVisitor(schema, config);
24-
2523
const importTypes: string[] = [];
2624

2725
return {
@@ -39,12 +37,11 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
3937
].join('\n'),
4038
InputObjectTypeDefinition: {
4139
leave: (node: InputObjectTypeDefinitionNode) => {
42-
const name = tsVisitor.convertName(node.name.value);
40+
const visitor = new Visitor('input', schema, config);
41+
const name = visitor.convertName(node.name.value);
4342
importTypes.push(name);
4443

45-
const shape = node.fields
46-
?.map(field => generateFieldMyZodSchema(config, tsVisitor, schema, field, 2))
47-
.join(',\n');
44+
const shape = node.fields?.map(field => generateFieldMyZodSchema(config, visitor, field, 2)).join(',\n');
4845

4946
return new DeclarationBlock({})
5047
.export()
@@ -55,12 +52,11 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
5552
},
5653
ObjectTypeDefinition: {
5754
leave: ObjectTypeDefinitionBuilder(config.withObjectType, (node: ObjectTypeDefinitionNode) => {
58-
const name = tsVisitor.convertName(node.name.value);
55+
const visitor = new Visitor('output', schema, config);
56+
const name = visitor.convertName(node.name.value);
5957
importTypes.push(name);
6058

61-
const shape = node.fields
62-
?.map(field => generateFieldMyZodSchema(config, tsVisitor, schema, field, 2))
63-
.join(',\n');
59+
const shape = node.fields?.map(field => generateFieldMyZodSchema(config, visitor, field, 2)).join(',\n');
6460

6561
return new DeclarationBlock({})
6662
.export()
@@ -78,7 +74,8 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
7874
},
7975
EnumTypeDefinition: {
8076
leave: (node: EnumTypeDefinitionNode) => {
81-
const enumname = tsVisitor.convertName(node.name.value);
77+
const visitor = new Visitor('both', schema, config);
78+
const enumname = visitor.convertName(node.name.value);
8279
importTypes.push(enumname);
8380
// z.enum are basically myzod.literals
8481
if (config.enumsAsTypes) {
@@ -101,11 +98,13 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
10198
leave: (node: UnionTypeDefinitionNode) => {
10299
if (!node.types || !config.withObjectType) return;
103100

104-
const unionName = tsVisitor.convertName(node.name.value);
101+
const visitor = new Visitor('output', schema, config);
102+
103+
const unionName = visitor.convertName(node.name.value);
105104
const unionElements = node.types
106105
?.map(t => {
107-
const element = tsVisitor.convertName(t.name.value);
108-
const typ = schema.getType(t.name.value);
106+
const element = visitor.convertName(t.name.value);
107+
const typ = visitor.getType(t.name.value);
109108
if (typ?.astNode?.kind === 'EnumTypeDefinition') {
110109
return `${element}Schema`;
111110
}
@@ -126,25 +125,23 @@ export const MyZodSchemaVisitor = (schema: GraphQLSchema, config: ValidationSche
126125

127126
const generateFieldMyZodSchema = (
128127
config: ValidationSchemaPluginConfig,
129-
tsVisitor: TsVisitor,
130-
schema: GraphQLSchema,
128+
visitor: Visitor,
131129
field: InputValueDefinitionNode | FieldDefinitionNode,
132130
indentCount: number
133131
): string => {
134-
const gen = generateFieldTypeMyZodSchema(config, tsVisitor, schema, field, field.type);
132+
const gen = generateFieldTypeMyZodSchema(config, visitor, field, field.type);
135133
return indent(`${field.name.value}: ${maybeLazy(field.type, gen)}`, indentCount);
136134
};
137135

138136
const generateFieldTypeMyZodSchema = (
139137
config: ValidationSchemaPluginConfig,
140-
tsVisitor: TsVisitor,
141-
schema: GraphQLSchema,
138+
visitor: Visitor,
142139
field: InputValueDefinitionNode | FieldDefinitionNode,
143140
type: TypeNode,
144141
parentType?: TypeNode
145142
): string => {
146143
if (isListType(type)) {
147-
const gen = generateFieldTypeMyZodSchema(config, tsVisitor, schema, field, type.type, type);
144+
const gen = generateFieldTypeMyZodSchema(config, visitor, field, type.type, type);
148145
if (!isNonNullType(parentType)) {
149146
const arrayGen = `myzod.array(${maybeLazy(type.type, gen)})`;
150147
const maybeLazyGen = applyDirectives(config, field, arrayGen);
@@ -153,18 +150,18 @@ const generateFieldTypeMyZodSchema = (
153150
return `myzod.array(${maybeLazy(type.type, gen)})`;
154151
}
155152
if (isNonNullType(type)) {
156-
const gen = generateFieldTypeMyZodSchema(config, tsVisitor, schema, field, type.type, type);
153+
const gen = generateFieldTypeMyZodSchema(config, visitor, field, type.type, type);
157154
return maybeLazy(type.type, gen);
158155
}
159156
if (isNamedType(type)) {
160-
const gen = generateNameNodeMyZodSchema(config, tsVisitor, schema, type.name);
157+
const gen = generateNameNodeMyZodSchema(config, visitor, type.name);
161158
if (isListType(parentType)) {
162159
return `${gen}.nullable()`;
163160
}
164161
const appliedDirectivesGen = applyDirectives(config, field, gen);
165162
if (isNonNullType(parentType)) {
166163
if (config.notAllowEmptyString === true) {
167-
const tsType = tsVisitor.scalars[type.name.value];
164+
const tsType = visitor.getScalarType(type.name.value);
168165
if (tsType === 'string') return `${gen}.min(1)`;
169166
}
170167
return appliedDirectivesGen;
@@ -192,33 +189,32 @@ const applyDirectives = (
192189

193190
const generateNameNodeMyZodSchema = (
194191
config: ValidationSchemaPluginConfig,
195-
tsVisitor: TsVisitor,
196-
schema: GraphQLSchema,
192+
visitor: Visitor,
197193
node: NameNode
198194
): string => {
199-
const typ = schema.getType(node.value);
195+
const converter = visitor.getNameNodeConverter(node);
200196

201-
if (typ?.astNode?.kind === 'InputObjectTypeDefinition') {
202-
const enumName = tsVisitor.convertName(typ.astNode.name.value);
203-
return `${enumName}Schema()`;
197+
if (converter?.targetKind === 'InputObjectTypeDefinition') {
198+
const name = converter.convertName();
199+
return `${name}Schema()`;
204200
}
205201

206-
if (typ?.astNode?.kind === 'ObjectTypeDefinition') {
207-
const enumName = tsVisitor.convertName(typ.astNode.name.value);
208-
return `${enumName}Schema()`;
202+
if (converter?.targetKind === 'ObjectTypeDefinition') {
203+
const name = converter.convertName();
204+
return `${name}Schema()`;
209205
}
210206

211-
if (typ?.astNode?.kind === 'EnumTypeDefinition') {
212-
const enumName = tsVisitor.convertName(typ.astNode.name.value);
213-
return `${enumName}Schema`;
207+
if (converter?.targetKind === 'EnumTypeDefinition') {
208+
const name = converter.convertName();
209+
return `${name}Schema`;
214210
}
215211

216-
if (typ?.astNode?.kind === 'UnionTypeDefinition') {
217-
const enumName = tsVisitor.convertName(typ.astNode.name.value);
218-
return `${enumName}Schema()`;
212+
if (converter?.targetKind === 'UnionTypeDefinition') {
213+
const name = converter.convertName();
214+
return `${name}Schema()`;
219215
}
220216

221-
return myzod4Scalar(config, tsVisitor, node.value);
217+
return myzod4Scalar(config, visitor, node.value);
222218
};
223219

224220
const maybeLazy = (type: TypeNode, schema: string): string => {
@@ -228,11 +224,11 @@ const maybeLazy = (type: TypeNode, schema: string): string => {
228224
return schema;
229225
};
230226

231-
const myzod4Scalar = (config: ValidationSchemaPluginConfig, tsVisitor: TsVisitor, scalarName: string): string => {
227+
const myzod4Scalar = (config: ValidationSchemaPluginConfig, visitor: Visitor, scalarName: string): string => {
232228
if (config.scalarSchemas?.[scalarName]) {
233229
return config.scalarSchemas[scalarName];
234230
}
235-
const tsType = tsVisitor.scalars[scalarName];
231+
const tsType = visitor.getScalarType(scalarName);
236232
switch (tsType) {
237233
case 'string':
238234
return `myzod.string()`;

src/visitor.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { ValidationSchemaPluginConfig } from './config';
2+
import { TsVisitor } from '@graphql-codegen/typescript';
3+
import { NameNode, GraphQLSchema } from 'graphql';
4+
5+
export class Visitor extends TsVisitor {
6+
constructor(
7+
private scalarDirection: 'input' | 'output' | 'both',
8+
private schema: GraphQLSchema,
9+
config: ValidationSchemaPluginConfig
10+
) {
11+
super(schema, config);
12+
}
13+
14+
public getType(name: string) {
15+
return this.schema.getType(name);
16+
}
17+
18+
public getNameNodeConverter(node: NameNode) {
19+
const typ = this.schema.getType(node.value);
20+
const astNode = typ?.astNode;
21+
if (astNode === undefined || astNode === null) {
22+
return undefined;
23+
}
24+
return {
25+
targetKind: astNode.kind,
26+
convertName: () => this.convertName(astNode.name.value),
27+
};
28+
}
29+
30+
public getScalarType(scalarName: string): string | null {
31+
if (this.scalarDirection === 'both') {
32+
return null;
33+
}
34+
return this.scalars[scalarName][this.scalarDirection];
35+
}
36+
}

0 commit comments

Comments
 (0)