Skip to content

Commit 24518ad

Browse files
authored
Merge pull request #609 from Code-Hex/issue-474
Add support for InterfaceTypeDefinition (resolved conflict)
2 parents 3f806e4 + 08dbfa0 commit 24518ad

16 files changed

+753
-17
lines changed

Diff for: codegen.yml

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ generates:
1313
schema: yup
1414
importFrom: ../types
1515
withObjectType: true
16+
withInterfaceType: true
1617
directives:
1718
required:
1819
msg: required
@@ -49,6 +50,7 @@ generates:
4950
schema: zod
5051
importFrom: ../types
5152
withObjectType: true
53+
withInterfaceType: true
5254
directives:
5355
# Write directives like
5456
#
@@ -72,6 +74,7 @@ generates:
7274
schema: myzod
7375
importFrom: ../types
7476
withObjectType: true
77+
withInterfaceType: true
7578
directives:
7679
constraint:
7780
minLength: min

Diff for: example/myzod/schemas.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as myzod from 'myzod'
2-
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, PageInput, PageType, User } from '../types'
2+
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'
33

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

@@ -92,6 +92,12 @@ export function MyTypeFooArgsSchema(): myzod.Type<MyTypeFooArgs> {
9292
})
9393
}
9494

95+
export function NamerSchema(): myzod.Type<Namer> {
96+
return myzod.object({
97+
name: myzod.string().optional().nullable()
98+
})
99+
}
100+
95101
export function PageInputSchema(): myzod.Type<PageInput> {
96102
return myzod.object({
97103
attributes: myzod.array(myzod.lazy(() => AttributeInputSchema())).optional().nullable(),

Diff for: example/test.graphql

+5-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type Guest {
1515

1616
union UserKind = Admin | Guest
1717

18-
type User {
18+
type User implements Namer {
1919
id: ID
2020
name: String
2121
email: String
@@ -25,6 +25,10 @@ type User {
2525
updatedAt: Date
2626
}
2727

28+
interface Namer {
29+
name: String
30+
}
31+
2832
input PageInput {
2933
id: ID!
3034
title: String!

Diff for: example/types.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ export type MyTypeFooArgs = {
9191
d: Scalars['Float']['input'];
9292
};
9393

94+
export type Namer = {
95+
name?: Maybe<Scalars['String']['output']>;
96+
};
97+
9498
export type PageInput = {
9599
attributes?: InputMaybe<Array<AttributeInput>>;
96100
date?: InputMaybe<Scalars['Date']['input']>;
@@ -112,7 +116,7 @@ export enum PageType {
112116
Service = 'SERVICE'
113117
}
114118

115-
export type User = {
119+
export type User = Namer & {
116120
__typename?: 'User';
117121
createdAt?: Maybe<Scalars['Date']['output']>;
118122
email?: Maybe<Scalars['String']['output']>;

Diff for: example/yup/schemas.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as yup from 'yup'
2-
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, PageInput, PageType, User, UserKind } from '../types'
2+
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User, UserKind } from '../types'
33

44
export const ButtonComponentTypeSchema = yup.string<ButtonComponentType>().oneOf(Object.values(ButtonComponentType)).defined();
55

@@ -96,6 +96,12 @@ export function MyTypeFooArgsSchema(): yup.ObjectSchema<MyTypeFooArgs> {
9696
})
9797
}
9898

99+
export function NamerSchema(): yup.ObjectSchema<Namer> {
100+
return yup.object({
101+
name: yup.string().defined().nullable().optional()
102+
})
103+
}
104+
99105
export function PageInputSchema(): yup.ObjectSchema<PageInput> {
100106
return yup.object({
101107
attributes: yup.array(yup.lazy(() => AttributeInputSchema().nonNullable())).defined().nullable().optional(),

Diff for: example/zod/schemas.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { z } from 'zod'
2-
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, PageInput, PageType, User } from '../types'
2+
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'
33

44
type Properties<T> = Required<{
55
[K in keyof T]: z.ZodType<T[K], any, T[K]>;
@@ -100,6 +100,12 @@ export function MyTypeFooArgsSchema(): z.ZodObject<Properties<MyTypeFooArgs>> {
100100
})
101101
}
102102

103+
export function NamerSchema(): z.ZodObject<Properties<Namer>> {
104+
return z.object({
105+
name: z.string().nullish()
106+
})
107+
}
108+
103109
export function PageInputSchema(): z.ZodObject<Properties<PageInput>> {
104110
return z.object({
105111
attributes: z.array(z.lazy(() => AttributeInputSchema())).nullish(),

Diff for: src/config.ts

+18
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,24 @@ export interface ValidationSchemaPluginConfig extends TypeScriptPluginConfig {
194194
* ```
195195
*/
196196
withObjectType?: boolean
197+
/**
198+
* @description Generates validation schema with GraphQL type interfaces.
199+
*
200+
* @exampleMarkdown
201+
* ```yml
202+
* generates:
203+
* path/to/types.ts:
204+
* plugins:
205+
* - typescript
206+
* path/to/schemas.ts:
207+
* plugins:
208+
* - graphql-codegen-validation-schema
209+
* config:
210+
* schema: yup
211+
* withInterfaceType: true
212+
* ```
213+
*/
214+
withInterfaceType?: boolean
197215
/**
198216
* @description Specify validation schema export type.
199217
* @default function

Diff for: src/graphql.ts

+10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
DefinitionNode,
55
DocumentNode,
66
GraphQLSchema,
7+
InterfaceTypeDefinitionNode,
78
ListTypeNode,
89
NameNode,
910
NamedTypeNode,
@@ -23,6 +24,7 @@ export const isNamedType = (typ?: TypeNode): typ is NamedTypeNode => typ?.kind =
2324
export const isInput = (kind: string) => kind.includes('Input');
2425

2526
type ObjectTypeDefinitionFn = (node: ObjectTypeDefinitionNode) => any;
27+
type InterfaceTypeDefinitionFn = (node: InterfaceTypeDefinitionNode) => any;
2628

2729
export function ObjectTypeDefinitionBuilder(useObjectTypes: boolean | undefined, callback: ObjectTypeDefinitionFn): ObjectTypeDefinitionFn | undefined {
2830
if (!useObjectTypes)
@@ -35,6 +37,14 @@ export function ObjectTypeDefinitionBuilder(useObjectTypes: boolean | undefined,
3537
};
3638
}
3739

40+
export function InterfaceTypeDefinitionBuilder(useInterfaceTypes: boolean | undefined, callback: InterfaceTypeDefinitionFn): InterfaceTypeDefinitionFn | undefined {
41+
if (!useInterfaceTypes)
42+
return undefined;
43+
return (node) => {
44+
return callback(node);
45+
};
46+
}
47+
3848
export function topologicalSortAST(schema: GraphQLSchema, ast: DocumentNode): DocumentNode {
3949
const dependencyGraph = new Graph();
4050
const targetKinds = [

Diff for: src/myzod/index.ts

+54-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
GraphQLSchema,
66
InputObjectTypeDefinitionNode,
77
InputValueDefinitionNode,
8+
InterfaceTypeDefinitionNode,
89
NameNode,
910
ObjectTypeDefinitionNode,
1011
TypeNode,
@@ -18,7 +19,14 @@ import type { ValidationSchemaPluginConfig } from '../config';
1819
import { buildApi, formatDirectiveConfig } from '../directive';
1920
import { BaseSchemaVisitor } from '../schema_visitor';
2021
import type { Visitor } from '../visitor';
21-
import { ObjectTypeDefinitionBuilder, isInput, isListType, isNamedType, isNonNullType } from './../graphql';
22+
import {
23+
InterfaceTypeDefinitionBuilder,
24+
ObjectTypeDefinitionBuilder,
25+
isInput,
26+
isListType,
27+
isNamedType,
28+
isNonNullType,
29+
} from './../graphql';
2230

2331
const anySchema = `definedNonNullAnySchema`;
2432

@@ -53,6 +61,44 @@ export class MyZodSchemaVisitor extends BaseSchemaVisitor {
5361
};
5462
}
5563

64+
get InterfaceTypeDefinition() {
65+
return {
66+
leave: InterfaceTypeDefinitionBuilder(this.config.withInterfaceType, (node: InterfaceTypeDefinitionNode) => {
67+
const visitor = this.createVisitor('output');
68+
const name = visitor.convertName(node.name.value);
69+
this.importTypes.push(name);
70+
71+
// Building schema for field arguments.
72+
const argumentBlocks = this.buildTypeDefinitionArguments(node, visitor);
73+
const appendArguments = argumentBlocks ? `\n${argumentBlocks}` : '';
74+
75+
// Building schema for fields.
76+
const shape = node.fields?.map(field => generateFieldMyZodSchema(this.config, visitor, field, 2)).join(',\n');
77+
78+
switch (this.config.validationSchemaExportType) {
79+
case 'const':
80+
return (
81+
new DeclarationBlock({})
82+
.export()
83+
.asKind('const')
84+
.withName(`${name}Schema: myzod.Type<${name}>`)
85+
.withContent([`myzod.object({`, shape, '})'].join('\n')).string + appendArguments
86+
);
87+
88+
case 'function':
89+
default:
90+
return (
91+
new DeclarationBlock({})
92+
.export()
93+
.asKind('function')
94+
.withName(`${name}Schema(): myzod.Type<${name}>`)
95+
.withBlock([indent(`return myzod.object({`), shape, indent('})')].join('\n')).string + appendArguments
96+
);
97+
}
98+
}),
99+
};
100+
}
101+
56102
get ObjectTypeDefinition() {
57103
return {
58104
leave: ObjectTypeDefinitionBuilder(this.config.withObjectType, (node: ObjectTypeDefinitionNode) => {
@@ -61,7 +107,7 @@ export class MyZodSchemaVisitor extends BaseSchemaVisitor {
61107
this.importTypes.push(name);
62108

63109
// Building schema for field arguments.
64-
const argumentBlocks = this.buildObjectTypeDefinitionArguments(node, visitor);
110+
const argumentBlocks = this.buildTypeDefinitionArguments(node, visitor);
65111
const appendArguments = argumentBlocks ? `\n${argumentBlocks}` : '';
66112

67113
// Building schema for fields.
@@ -266,6 +312,7 @@ function generateNameNodeMyZodSchema(config: ValidationSchemaPluginConfig, visit
266312
const converter = visitor.getNameNodeConverter(node);
267313

268314
switch (converter?.targetKind) {
315+
case 'InterfaceTypeDefinition':
269316
case 'InputObjectTypeDefinition':
270317
case 'ObjectTypeDefinition':
271318
case 'UnionTypeDefinition':
@@ -279,7 +326,12 @@ function generateNameNodeMyZodSchema(config: ValidationSchemaPluginConfig, visit
279326
}
280327
case 'EnumTypeDefinition':
281328
return `${converter.convertName()}Schema`;
329+
case 'ScalarTypeDefinition':
330+
return myzod4Scalar(config, visitor, node.value);
282331
default:
332+
if (converter?.targetKind)
333+
console.warn('Unknown target kind', converter.targetKind);
334+
283335
return myzod4Scalar(config, visitor, node.value);
284336
}
285337
}

Diff for: src/schema_visitor.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import type { FieldDefinitionNode, GraphQLSchema, InputValueDefinitionNode, ObjectTypeDefinitionNode } from 'graphql';
1+
import type {
2+
FieldDefinitionNode,
3+
GraphQLSchema,
4+
InputValueDefinitionNode,
5+
InterfaceTypeDefinitionNode,
6+
ObjectTypeDefinitionNode,
7+
} from 'graphql';
28

39
import type { ValidationSchemaPluginConfig } from './config';
410
import type { SchemaVisitor } from './types';
@@ -39,7 +45,10 @@ export abstract class BaseSchemaVisitor implements SchemaVisitor {
3945
name: string
4046
): string;
4147

42-
protected buildObjectTypeDefinitionArguments(node: ObjectTypeDefinitionNode, visitor: Visitor) {
48+
protected buildTypeDefinitionArguments(
49+
node: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode,
50+
visitor: Visitor,
51+
) {
4352
return visitor.buildArgumentsSchemaBlock(node, (typeName, field) => {
4453
this.importTypes.push(typeName);
4554
return this.buildInputFields(field.arguments ?? [], visitor, typeName);

Diff for: src/visitor.ts

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { TsVisitor } from '@graphql-codegen/typescript';
2-
import type { FieldDefinitionNode, GraphQLSchema, NameNode, ObjectTypeDefinitionNode } from 'graphql';
3-
import { specifiedScalarTypes } from 'graphql';
2+
import type {
3+
FieldDefinitionNode,
4+
GraphQLSchema,
5+
InterfaceTypeDefinitionNode,
6+
NameNode,
7+
ObjectTypeDefinitionNode,
8+
} from 'graphql';
9+
import {
10+
specifiedScalarTypes,
11+
} from 'graphql';
412

513
import type { ValidationSchemaPluginConfig } from './config';
614

@@ -37,7 +45,11 @@ export class Visitor extends TsVisitor {
3745
if (this.scalarDirection === 'both')
3846
return null;
3947

40-
return this.scalars[scalarName][this.scalarDirection];
48+
const scalar = this.scalars[scalarName];
49+
if (!scalar)
50+
throw new Error(`Unknown scalar ${scalarName}`);
51+
52+
return scalar[this.scalarDirection];
4153
}
4254

4355
public shouldEmitAsNotAllowEmptyString(name: string): boolean {
@@ -53,7 +65,7 @@ export class Visitor extends TsVisitor {
5365
}
5466

5567
public buildArgumentsSchemaBlock(
56-
node: ObjectTypeDefinitionNode,
68+
node: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode,
5769
callback: (typeName: string, field: FieldDefinitionNode) => string,
5870
) {
5971
const fieldsWithArguments = node.fields?.filter(field => field.arguments && field.arguments.length > 0) ?? [];

0 commit comments

Comments
 (0)