diff --git a/src/graphql.ts b/src/graphql.ts index 51416209..58b3b922 100644 --- a/src/graphql.ts +++ b/src/graphql.ts @@ -4,6 +4,7 @@ import { DefinitionNode, DocumentNode, GraphQLSchema, + isSpecifiedScalarType, ListTypeNode, NamedTypeNode, NameNode, @@ -169,3 +170,8 @@ export const topsort = (g: Graph): string[] => { return results.reverse(); }; + +export const isGeneratedByIntrospection = (schema: GraphQLSchema): boolean => + Object.entries(schema.getTypeMap()) + .filter(([name, type]) => !name.startsWith('__') && !isSpecifiedScalarType(type)) + .every(([, type]) => type.astNode === undefined); diff --git a/src/index.ts b/src/index.ts index a970eec1..0f05b117 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,9 @@ import { PluginFunction, Types } from '@graphql-codegen/plugin-helpers'; import { transformSchemaAST } from '@graphql-codegen/schema-ast'; -import { GraphQLSchema, visit } from 'graphql'; +import { buildSchema, GraphQLSchema, printSchema, visit } from 'graphql'; import { ValidationSchemaPluginConfig } from './config'; -import { topologicalSortAST } from './graphql'; +import { isGeneratedByIntrospection, topologicalSortAST } from './graphql'; import { MyZodSchemaVisitor } from './myzod/index'; import { SchemaVisitor } from './types'; import { YupSchemaVisitor } from './yup/index'; @@ -40,16 +40,20 @@ const schemaVisitor = (schema: GraphQLSchema, config: ValidationSchemaPluginConf const _transformSchemaAST = (schema: GraphQLSchema, config: ValidationSchemaPluginConfig) => { const { schema: _schema, ast } = transformSchemaAST(schema, config); + + // See: https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/issues/394 + const __schema = isGeneratedByIntrospection(_schema) ? buildSchema(printSchema(_schema)) : _schema; + // This affects the performance of code generation, so it is // enabled only when this option is selected. if (config.validationSchemaExportType === 'const') { return { - schema: _schema, - ast: topologicalSortAST(_schema, ast), + schema: __schema, + ast: topologicalSortAST(__schema, ast), }; } return { - schema: _schema, + schema: __schema, ast, }; }; diff --git a/tests/graphql.spec.ts b/tests/graphql.spec.ts index 8fa75572..4e34ebcb 100644 --- a/tests/graphql.spec.ts +++ b/tests/graphql.spec.ts @@ -1,8 +1,16 @@ import { Graph } from 'graphlib'; -import { buildSchema, Kind, ObjectTypeDefinitionNode, parse, print } from 'graphql'; +import { + buildClientSchema, + buildSchema, + introspectionFromSchema, + Kind, + ObjectTypeDefinitionNode, + parse, + print, +} from 'graphql'; import dedent from 'ts-dedent'; -import { ObjectTypeDefinitionBuilder, topologicalSortAST, topsort } from '../src/graphql'; +import { isGeneratedByIntrospection, ObjectTypeDefinitionBuilder, topologicalSortAST, topsort } from '../src/graphql'; describe('graphql', () => { describe('ObjectTypeDefinitionBuilder', () => { @@ -238,3 +246,53 @@ describe('topologicalSortAST', () => { expect(sortedSchema).toBe(expectedSortedSchema); }); }); + +describe('isGeneratedByIntrospection function', () => { + const schemaDefinition = /* GraphQL */ ` + scalar CustomScalar + + interface Node { + id: ID! + } + + type UserType implements Node { + id: ID! + name: String! + email: String! + } + + union SearchResult = UserType + + enum Role { + ADMIN + USER + } + + input UserInput { + name: String! + email: String! + role: Role! + } + + type Query { + user(id: ID!): UserType! + search(text: String!): [SearchResult] + } + + type Mutation { + createUser(input: UserInput!): UserType! + } + `; + + test('returns false for a schema not generated by introspection', () => { + const schema = buildSchema(schemaDefinition); + expect(isGeneratedByIntrospection(schema)).toBe(false); + }); + + test('returns true for a schema generated by introspection', () => { + const schema = buildSchema(schemaDefinition); + const query = introspectionFromSchema(schema); + const clientSchema = buildClientSchema(query); + expect(isGeneratedByIntrospection(clientSchema)).toBe(true); + }); +}); diff --git a/tests/myzod.spec.ts b/tests/myzod.spec.ts index 0bbf7e8a..3c9826d8 100644 --- a/tests/myzod.spec.ts +++ b/tests/myzod.spec.ts @@ -1,4 +1,4 @@ -import { buildSchema } from 'graphql'; +import { buildClientSchema, buildSchema, introspectionFromSchema } from 'graphql'; import dedent from 'ts-dedent'; import { plugin } from '../src/index'; @@ -922,4 +922,41 @@ describe('myzod', () => { expect(result.content).not.toContain(wantNotContain); } }); + + it('issue #394', async () => { + const schema = buildSchema(/* GraphQL */ ` + enum Test { + A + B + } + + type Query { + _dummy: Test + } + + input QueryInput { + _dummy: Test + } + `); + const query = introspectionFromSchema(schema); + const clientSchema = buildClientSchema(query); + const result = await plugin( + clientSchema, + [], + { + schema: 'myzod', + scalars: { + ID: 'string', + }, + }, + {} + ); + const wantContain = dedent` + export function QueryInputSchema(): myzod.Type { + return myzod.object({ + _dummy: TestSchema.optional().nullable() + }) + }`; + expect(result.content).toContain(wantContain); + }); }); diff --git a/tests/yup.spec.ts b/tests/yup.spec.ts index 86fd10d7..5112f8a2 100644 --- a/tests/yup.spec.ts +++ b/tests/yup.spec.ts @@ -1,4 +1,4 @@ -import { buildSchema } from 'graphql'; +import { buildClientSchema, buildSchema, introspectionFromSchema } from 'graphql'; import dedent from 'ts-dedent'; import { plugin } from '../src/index'; @@ -836,4 +836,41 @@ describe('yup', () => { expect(result.content).not.toContain(wantNotContain); } }); + + it('issue #394', async () => { + const schema = buildSchema(/* GraphQL */ ` + enum Test { + A + B + } + + type Query { + _dummy: Test + } + + input QueryInput { + _dummy: Test + } + `); + const query = introspectionFromSchema(schema); + const clientSchema = buildClientSchema(query); + const result = await plugin( + clientSchema, + [], + { + schema: 'yup', + scalars: { + ID: 'string', + }, + }, + {} + ); + const wantContain = dedent` + export function QueryInputSchema(): yup.ObjectSchema { + return yup.object({ + _dummy: TestSchema.nullable().optional() + }) + }`; + expect(result.content).toContain(wantContain); + }); }); diff --git a/tests/zod.spec.ts b/tests/zod.spec.ts index 68f223da..67a676c2 100644 --- a/tests/zod.spec.ts +++ b/tests/zod.spec.ts @@ -1,4 +1,5 @@ -import { buildSchema } from 'graphql'; +import { getCachedDocumentNodeFromSchema } from '@graphql-codegen/plugin-helpers'; +import { buildClientSchema, buildSchema, introspectionFromSchema, isSpecifiedScalarType } from 'graphql'; import { dedent } from 'ts-dedent'; import { plugin } from '../src/index'; @@ -989,4 +990,41 @@ describe('zod', () => { expect(result.content).not.toContain(wantNotContain); } }); + + it('issue #394', async () => { + const schema = buildSchema(/* GraphQL */ ` + enum Test { + A + B + } + + type Query { + _dummy: Test + } + + input QueryInput { + _dummy: Test + } + `); + const query = introspectionFromSchema(schema); + const clientSchema = buildClientSchema(query); + const result = await plugin( + clientSchema, + [], + { + schema: 'zod', + scalars: { + ID: 'string', + }, + }, + {} + ); + const wantContain = dedent` + export function QueryInputSchema(): z.ZodObject> { + return z.object({ + _dummy: TestSchema.nullish() + }) + }`; + expect(result.content).toContain(wantContain); + }); });