diff --git a/src/myzod/index.ts b/src/myzod/index.ts index ce3bdae2..6b47c454 100644 --- a/src/myzod/index.ts +++ b/src/myzod/index.ts @@ -212,9 +212,8 @@ const generateFieldTypeMyZodSchema = ( } const appliedDirectivesGen = applyDirectives(config, field, gen); if (isNonNullType(parentType)) { - if (config.notAllowEmptyString === true) { - const tsType = visitor.getScalarType(type.name.value); - if (tsType === 'string') return `${gen}.min(1)`; + if (visitor.shouldEmitAsNotAllowEmptyString(type.name.value)) { + return `${gen}.min(1)`; } return appliedDirectivesGen; } diff --git a/src/visitor.ts b/src/visitor.ts index 574cce3c..d95b1892 100644 --- a/src/visitor.ts +++ b/src/visitor.ts @@ -1,5 +1,5 @@ import { TsVisitor } from '@graphql-codegen/typescript'; -import { GraphQLSchema, NameNode } from 'graphql'; +import { GraphQLSchema, NameNode, specifiedScalarTypes } from 'graphql'; import { ValidationSchemaPluginConfig } from './config'; @@ -7,9 +7,13 @@ export class Visitor extends TsVisitor { constructor( private scalarDirection: 'input' | 'output' | 'both', private schema: GraphQLSchema, - config: ValidationSchemaPluginConfig + private pluginConfig: ValidationSchemaPluginConfig ) { - super(schema, config); + super(schema, pluginConfig); + } + + private isSpecifiedScalarName(scalarName: string) { + return specifiedScalarTypes.some(({ name }) => name === scalarName); } public getType(name: string) { @@ -34,4 +38,16 @@ export class Visitor extends TsVisitor { } return this.scalars[scalarName][this.scalarDirection]; } + + public shouldEmitAsNotAllowEmptyString(name: string): boolean { + if (this.pluginConfig.notAllowEmptyString !== true) { + return false; + } + const typ = this.getType(name); + if (typ?.astNode?.kind !== 'ScalarTypeDefinition' && !this.isSpecifiedScalarName(name)) { + return false; + } + const tsType = this.getScalarType(name); + return tsType === 'string'; + } } diff --git a/src/yup/index.ts b/src/yup/index.ts index a9a335fe..4aeb38b8 100644 --- a/src/yup/index.ts +++ b/src/yup/index.ts @@ -262,9 +262,8 @@ const generateFieldTypeYupSchema = ( if (isNamedType(type)) { const gen = generateNameNodeYupSchema(config, visitor, type.name); if (isNonNullType(parentType)) { - if (config.notAllowEmptyString === true) { - const tsType = visitor.getScalarType(type.name.value); - if (tsType === 'string') return `${gen}.required()`; + if (visitor.shouldEmitAsNotAllowEmptyString(type.name.value)) { + return `${gen}.required()`; } return `${gen}.nonNullable()`; } diff --git a/src/zod/index.ts b/src/zod/index.ts index 96321313..3d01931c 100644 --- a/src/zod/index.ts +++ b/src/zod/index.ts @@ -222,9 +222,8 @@ const generateFieldTypeZodSchema = ( } const appliedDirectivesGen = applyDirectives(config, field, gen); if (isNonNullType(parentType)) { - if (config.notAllowEmptyString === true) { - const tsType = visitor.getScalarType(type.name.value); - if (tsType === 'string') return `${appliedDirectivesGen}.min(1)`; + if (visitor.shouldEmitAsNotAllowEmptyString(type.name.value)) { + return `${appliedDirectivesGen}.min(1)`; } return appliedDirectivesGen; } diff --git a/tests/myzod.spec.ts b/tests/myzod.spec.ts index 62170d00..0bbf7e8a 100644 --- a/tests/myzod.spec.ts +++ b/tests/myzod.spec.ts @@ -1,4 +1,5 @@ import { buildSchema } from 'graphql'; +import dedent from 'ts-dedent'; import { plugin } from '../src/index'; @@ -297,6 +298,37 @@ describe('myzod', () => { } }); + it('with notAllowEmptyString issue #386', async () => { + const schema = buildSchema(/* GraphQL */ ` + input InputOne { + field: InputNested! + } + + input InputNested { + field: String! + } + `); + const result = await plugin( + schema, + [], + { + schema: 'myzod', + notAllowEmptyString: true, + scalars: { + ID: 'string', + }, + }, + {} + ); + const wantContain = dedent` + export function InputNestedSchema(): myzod.Type { + return myzod.object({ + field: myzod.string().min(1) + }) + }`; + expect(result.content).toContain(wantContain); + }); + it('with scalarSchemas', async () => { const schema = buildSchema(/* GraphQL */ ` input ScalarsInput { diff --git a/tests/yup.spec.ts b/tests/yup.spec.ts index 419b10d4..86fd10d7 100644 --- a/tests/yup.spec.ts +++ b/tests/yup.spec.ts @@ -1,4 +1,5 @@ import { buildSchema } from 'graphql'; +import dedent from 'ts-dedent'; import { plugin } from '../src/index'; @@ -294,6 +295,37 @@ describe('yup', () => { } }); + it('with notAllowEmptyString issue #386', async () => { + const schema = buildSchema(/* GraphQL */ ` + input InputOne { + field: InputNested! + } + + input InputNested { + field: String! + } + `); + const result = await plugin( + schema, + [], + { + schema: 'yup', + notAllowEmptyString: true, + scalars: { + ID: 'string', + }, + }, + {} + ); + const wantContain = dedent` + export function InputNestedSchema(): yup.ObjectSchema { + return yup.object({ + field: yup.string().defined().required() + }) + }`; + expect(result.content).toContain(wantContain); + }); + it('with scalarSchemas', async () => { const schema = buildSchema(/* GraphQL */ ` input ScalarsInput { diff --git a/tests/zod.spec.ts b/tests/zod.spec.ts index 74615604..68f223da 100644 --- a/tests/zod.spec.ts +++ b/tests/zod.spec.ts @@ -1,4 +1,5 @@ import { buildSchema } from 'graphql'; +import { dedent } from 'ts-dedent'; import { plugin } from '../src/index'; @@ -297,6 +298,37 @@ describe('zod', () => { } }); + it('with notAllowEmptyString issue #386', async () => { + const schema = buildSchema(/* GraphQL */ ` + input InputOne { + field: InputNested! + } + + input InputNested { + field: String! + } + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + notAllowEmptyString: true, + scalars: { + ID: 'string', + }, + }, + {} + ); + const wantContain = dedent` + export function InputNestedSchema(): z.ZodObject> { + return z.object({ + field: z.string().min(1) + }) + }`; + expect(result.content).toContain(wantContain); + }); + it('with scalarSchemas', async () => { const schema = buildSchema(/* GraphQL */ ` input ScalarsInput {