diff --git a/src/config.ts b/src/config.ts index ca6d7502..ddc48b12 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,5 @@ import type { TypeScriptPluginConfig } from '@graphql-codegen/typescript'; +import type { NamingConventionMap } from '@graphql-codegen/visitor-plugin-common'; export type ValidationSchema = 'yup' | 'zod' | 'myzod' | 'valibot'; export type ValidationSchemaExportType = 'function' | 'const'; @@ -210,6 +211,42 @@ export interface ValidationSchemaPluginConfig extends TypeScriptPluginConfig { * ``` */ validationSchemaExportType?: ValidationSchemaExportType + /** + * @description Uses the full path of the enum type as the default value instead of the stringified value. + * @default false + * + * @exampleMarkdown + * ```yml + * generates: + * path/to/file.ts: + * plugins: + * - typescript + * - graphql-codegen-validation-schema + * config: + * useEnumTypeAsDefault: true + * ``` + */ + useEnumTypeAsDefaultValue?: boolean + /** + * @description Uses the full path of the enum type as the default value instead of the stringified value. + * @default { enumValues: "change-case-all#pascalCase" } + * + * Note: This option has not been tested with `namingConvention.transformUnderscore` and `namingConvention.typeNames` options, + * and may not work as expected. + * + * @exampleMarkdown + * ```yml + * generates: + * path/to/file.ts: + * plugins: + * - typescript + * - graphql-codegen-validation-schema + * config: + * namingConvention: + * enumValues: change-case-all#pascalCase + * ``` + */ + namingConvention?: NamingConventionMap /** * @description Generates validation schema with more API based on directive schema. * @exampleMarkdown diff --git a/src/myzod/index.ts b/src/myzod/index.ts index 0cfbdf15..d8f49f79 100644 --- a/src/myzod/index.ts +++ b/src/myzod/index.ts @@ -1,4 +1,4 @@ -import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common'; +import { DeclarationBlock, convertNameParts, indent } from '@graphql-codegen/visitor-plugin-common'; import type { EnumTypeDefinitionNode, FieldDefinitionNode, @@ -15,6 +15,7 @@ import { Kind, } from 'graphql'; +import { resolveExternalModuleAndFn } from '@graphql-codegen/plugin-helpers'; import type { ValidationSchemaPluginConfig } from '../config'; import { buildApi, formatDirectiveConfig } from '../directive'; import { BaseSchemaVisitor } from '../schema_visitor'; @@ -282,8 +283,19 @@ function generateFieldTypeMyZodSchema(config: ValidationSchemaPluginConfig, visi if (defaultValue?.kind === Kind.INT || defaultValue?.kind === Kind.FLOAT || defaultValue?.kind === Kind.BOOLEAN) appliedDirectivesGen = `${appliedDirectivesGen}.default(${defaultValue.value})`; - if (defaultValue?.kind === Kind.STRING || defaultValue?.kind === Kind.ENUM) - appliedDirectivesGen = `${appliedDirectivesGen}.default("${escapeGraphQLCharacters(defaultValue.value)}")`; + if (defaultValue?.kind === Kind.STRING || defaultValue?.kind === Kind.ENUM) { + if (config.useEnumTypeAsDefaultValue && defaultValue?.kind !== Kind.STRING) { + let value = convertNameParts(defaultValue.value, resolveExternalModuleAndFn('change-case-all#pascalCase')); + + if (config.namingConvention?.enumValues) + value = convertNameParts(defaultValue.value, resolveExternalModuleAndFn(config.namingConvention?.enumValues)); + + appliedDirectivesGen = `${appliedDirectivesGen}.default(${visitor.convertName(type.name.value)}.${value})`; + } + else { + appliedDirectivesGen = `${appliedDirectivesGen}.default("${escapeGraphQLCharacters(defaultValue.value)}")`; + } + } } if (isNonNullType(parentType)) { diff --git a/src/valibot/index.ts b/src/valibot/index.ts index 04841cd6..df0a80b8 100644 --- a/src/valibot/index.ts +++ b/src/valibot/index.ts @@ -205,9 +205,9 @@ function generateFieldTypeValibotSchema(config: ValidationSchemaPluginConfig, vi if (isListType(type)) { const gen = generateFieldTypeValibotSchema(config, visitor, field, type.type, type); const arrayGen = `v.array(${maybeLazy(type.type, gen)})`; - if (!isNonNullType(parentType)) { + if (!isNonNullType(parentType)) return `v.nullish(${arrayGen})`; - } + return arrayGen; } if (isNonNullType(type)) { diff --git a/src/yup/index.ts b/src/yup/index.ts index e5763dae..9984d918 100644 --- a/src/yup/index.ts +++ b/src/yup/index.ts @@ -1,4 +1,4 @@ -import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common'; +import { DeclarationBlock, convertNameParts, indent } from '@graphql-codegen/visitor-plugin-common'; import type { EnumTypeDefinitionNode, FieldDefinitionNode, @@ -15,6 +15,7 @@ import { Kind, } from 'graphql'; +import { resolveExternalModuleAndFn } from '@graphql-codegen/plugin-helpers'; import type { ValidationSchemaPluginConfig } from '../config'; import { buildApi, formatDirectiveConfig } from '../directive'; import { BaseSchemaVisitor } from '../schema_visitor'; @@ -284,8 +285,19 @@ function shapeFields(fields: readonly (FieldDefinitionNode | InputValueDefinitio fieldSchema = `${fieldSchema}.default(${defaultValue.value})`; } - if (defaultValue?.kind === Kind.STRING || defaultValue?.kind === Kind.ENUM) - fieldSchema = `${fieldSchema}.default("${escapeGraphQLCharacters(defaultValue.value)}")`; + if (defaultValue?.kind === Kind.STRING || defaultValue?.kind === Kind.ENUM) { + if (config.useEnumTypeAsDefaultValue && defaultValue?.kind !== Kind.STRING) { + let value = convertNameParts(defaultValue.value, resolveExternalModuleAndFn('change-case-all#pascalCase')); + + if (config.namingConvention?.enumValues) + value = convertNameParts(defaultValue.value, resolveExternalModuleAndFn(config.namingConvention?.enumValues)); + + fieldSchema = `${fieldSchema}.default(${visitor.convertName(field.name.value)}.${value})`; + } + else { + fieldSchema = `${fieldSchema}.default("${escapeGraphQLCharacters(defaultValue.value)}")`; + } + } } if (isNonNullType(field.type)) diff --git a/src/zod/index.ts b/src/zod/index.ts index 739c892b..edd1219f 100644 --- a/src/zod/index.ts +++ b/src/zod/index.ts @@ -1,4 +1,4 @@ -import { DeclarationBlock, indent } from '@graphql-codegen/visitor-plugin-common'; +import { DeclarationBlock, convertNameParts, indent } from '@graphql-codegen/visitor-plugin-common'; import type { EnumTypeDefinitionNode, FieldDefinitionNode, @@ -15,6 +15,7 @@ import { Kind, } from 'graphql'; +import { resolveExternalModuleAndFn } from '@graphql-codegen/plugin-helpers'; import type { ValidationSchemaPluginConfig } from '../config'; import { buildApi, formatDirectiveConfig } from '../directive'; import { BaseSchemaVisitor } from '../schema_visitor'; @@ -295,8 +296,19 @@ function generateFieldTypeZodSchema(config: ValidationSchemaPluginConfig, visito if (defaultValue?.kind === Kind.INT || defaultValue?.kind === Kind.FLOAT || defaultValue?.kind === Kind.BOOLEAN) appliedDirectivesGen = `${appliedDirectivesGen}.default(${defaultValue.value})`; - if (defaultValue?.kind === Kind.STRING || defaultValue?.kind === Kind.ENUM) - appliedDirectivesGen = `${appliedDirectivesGen}.default("${escapeGraphQLCharacters(defaultValue.value)}")`; + if (defaultValue?.kind === Kind.STRING || defaultValue?.kind === Kind.ENUM) { + if (config.useEnumTypeAsDefaultValue && defaultValue?.kind !== Kind.STRING) { + let value = convertNameParts(defaultValue.value, resolveExternalModuleAndFn('change-case-all#pascalCase')); + + if (config.namingConvention?.enumValues) + value = convertNameParts(defaultValue.value, resolveExternalModuleAndFn(config.namingConvention?.enumValues)); + + appliedDirectivesGen = `${appliedDirectivesGen}.default(${type.name.value}.${value})`; + } + else { + appliedDirectivesGen = `${appliedDirectivesGen}.default("${escapeGraphQLCharacters(defaultValue.value)}")`; + } + } } if (isNonNullType(parentType)) { diff --git a/tests/myzod.spec.ts b/tests/myzod.spec.ts index f5753b10..0497476e 100644 --- a/tests/myzod.spec.ts +++ b/tests/myzod.spec.ts @@ -1409,4 +1409,39 @@ describe('myzod', () => { " `) }); + + it('with default input values as enum types', async () => { + const schema = buildSchema(/* GraphQL */ ` + enum PageType { + PUBLIC + BASIC_AUTH + } + input PageInput { + pageType: PageType! = PUBLIC + greeting: String = "Hello" + score: Int = 100 + ratio: Float = 0.5 + isMember: Boolean = true + } + `); + const result = await plugin( + schema, + [], + { + schema: 'myzod', + importFrom: './types', + useEnumTypeAsDefaultValue: true, + }, + {}, + ); + + expect(result.content).toContain('export const PageTypeSchema = myzod.enum(PageType)'); + expect(result.content).toContain('export function PageInputSchema(): myzod.Type'); + + expect(result.content).toContain('pageType: PageTypeSchema.default(PageType.Public)'); + expect(result.content).toContain('greeting: myzod.string().default("Hello").optional().nullable()'); + expect(result.content).toContain('score: myzod.number().default(100).optional().nullable()'); + expect(result.content).toContain('ratio: myzod.number().default(0.5).optional().nullable()'); + expect(result.content).toContain('isMember: myzod.boolean().default(true).optional().nullable()'); + }); }); diff --git a/tests/yup.spec.ts b/tests/yup.spec.ts index c23d71b9..bad9c1f4 100644 --- a/tests/yup.spec.ts +++ b/tests/yup.spec.ts @@ -1433,4 +1433,41 @@ describe('yup', () => { " `) }); + + it('with default input values as enum types', async () => { + const schema = buildSchema(/* GraphQL */ ` + enum PageType { + PUBLIC + BASIC_AUTH + } + input PageInput { + pageType: PageType! = PUBLIC + greeting: String = "Hello" + score: Int = 100 + ratio: Float = 0.5 + isMember: Boolean = true + } + `); + const result = await plugin( + schema, + [], + { + schema: 'yup', + importFrom: './types', + useEnumTypeAsDefaultValue: true, + }, + {}, + ); + + expect(result.content).toContain( + 'export const PageTypeSchema = yup.string().oneOf(Object.values(PageType)).defined()', + ); + expect(result.content).toContain('export function PageInputSchema(): yup.ObjectSchema'); + + expect(result.content).toContain('pageType: PageTypeSchema.nonNullable().default(PageType.Public)'); + expect(result.content).toContain('greeting: yup.string().defined().nullable().default("Hello").optional()'); + expect(result.content).toContain('score: yup.number().defined().nullable().default(100).optional()'); + expect(result.content).toContain('ratio: yup.number().defined().nullable().default(0.5).optional()'); + expect(result.content).toContain('isMember: yup.boolean().defined().nullable().default(true).optional()'); + }); }); diff --git a/tests/zod.spec.ts b/tests/zod.spec.ts index 0fb1a324..28cbe369 100644 --- a/tests/zod.spec.ts +++ b/tests/zod.spec.ts @@ -530,6 +530,42 @@ describe('zod', () => { `) }); + it('with default input values as enum types', async () => { + const schema = buildSchema(/* GraphQL */ ` + enum PageType { + PUBLIC + BASIC_AUTH + } + input PageInput { + pageType: PageType! = PUBLIC + greeting: String = "Hello" + score: Int = 100 + ratio: Float = 0.5 + isMember: Boolean = true + } + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + importFrom: './types', + useEnumTypeAsDefaultValue: true, + }, + { + }, + ); + + expect(result.content).toContain('export const PageTypeSchema = z.nativeEnum(PageType)'); + expect(result.content).toContain('export function PageInputSchema(): z.ZodObject>'); + + expect(result.content).toContain('pageType: PageTypeSchema.default(PageType.Public)'); + expect(result.content).toContain('greeting: z.string().default("Hello").nullish()'); + expect(result.content).toContain('score: z.number().default(100).nullish()'); + expect(result.content).toContain('ratio: z.number().default(0.5).nullish()'); + expect(result.content).toContain('isMember: z.boolean().default(true).nullish()'); + }); + it('with default input values', async () => { const schema = buildSchema(/* GraphQL */ ` enum PageType {