From 848b42d54e1f40cf449e0a63eb4ddb1864825a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kretek?= Date: Wed, 23 Sep 2026 15:05:46 +0200 Subject: [PATCH] fix(valibot): apply directives to list fields instead of their items Ports the isRoot handling of the Zod generators (#1423) to valibot, so a list field's directives pipe the outermost v.array(...) and its items get none. Replaces the list field todo with a test. Co-Authored-By: Claude Opus 5.5 --- src/valibot/index.ts | 13 +++++++------ tests/valibot.spec.ts | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/valibot/index.ts b/src/valibot/index.ts index 058dc799..68e73dc2 100644 --- a/src/valibot/index.ts +++ b/src/valibot/index.ts @@ -211,17 +211,18 @@ function generateFieldValibotSchema(config: ValidationSchemaPluginConfig, visito return indent(`${field.name.value}: ${gen}`, indentCount); } -function generateFieldTypeValibotSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, type: TypeNode, parentType?: TypeNode): string { +function generateFieldTypeValibotSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, type: TypeNode, parentType?: TypeNode, isRoot = true): string { if (isListType(type)) { - const gen = generateFieldTypeValibotSchema(config, visitor, field, type.type, type); + const gen = generateFieldTypeValibotSchema(config, visitor, field, type.type, type, false); const arrayGen = `v.array(${gen})`; + const maybeDirectivesGen = isRoot ? pipeSchemaAndActions(arrayGen, actionsFromDirectives(config, field)) : arrayGen; if (!isNonNullType(parentType)) - return `v.nullish(${arrayGen})`; + return `v.nullish(${maybeDirectivesGen})`; - return arrayGen; + return maybeDirectivesGen; } if (isNonNullType(type)) { - const gen = generateFieldTypeValibotSchema(config, visitor, field, type.type, type); + const gen = generateFieldTypeValibotSchema(config, visitor, field, type.type, type, isRoot); return gen; } if (isNamedType(type)) { @@ -229,7 +230,7 @@ function generateFieldTypeValibotSchema(config: ValidationSchemaPluginConfig, vi if (isListType(parentType)) return `v.nullable(${maybeLazy(visitor, type, gen)})`; - const actions = actionsFromDirectives(config, field); + const actions = isRoot ? actionsFromDirectives(config, field) : []; const schema = maybeLazy(visitor, type, pipeSchemaAndActions(gen, actions)); if (isNonNullType(parentType)) { diff --git a/tests/valibot.spec.ts b/tests/valibot.spec.ts index 9cd51eb1..108d27a0 100644 --- a/tests/valibot.spec.ts +++ b/tests/valibot.spec.ts @@ -736,7 +736,44 @@ describe('valibot', () => { " `) }); - it.todo('list field') + it('list field', async () => { + const schema = buildSchema(/* GraphQL */ ` + input UserCreateInput { + tags: [String!]! @constraint(minLength: 1, maxLength: 10) + labels: [String!] @constraint(minLength: 1, maxLength: 10) + ids: [[String!]!]! @constraint(minLength: 1, maxLength: 10) + } + directive @constraint(minLength: Int!, maxLength: Int!) on INPUT_FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'valibot', + notAllowEmptyString: true, + directives: { + constraint: { + minLength: ['minLength', '$1', 'Please input more than $1'], + maxLength: ['maxLength', '$1', 'Please input less than $1'], + }, + }, + }, + {}, + ); + + expect(result.content).toMatchInlineSnapshot(` + " + + export function UserCreateInputSchema(): v.GenericSchema { + return v.object({ + tags: v.pipe(v.array(v.pipe(v.string(), v.minLength(1))), v.minLength(1, "Please input more than 1"), v.maxLength(10, "Please input less than 10")), + labels: v.nullish(v.pipe(v.array(v.pipe(v.string(), v.minLength(1))), v.minLength(1, "Please input more than 1"), v.maxLength(10, "Please input less than 10"))), + ids: v.pipe(v.array(v.array(v.pipe(v.string(), v.minLength(1)))), v.minLength(1, "Please input more than 1"), v.maxLength(10, "Please input less than 10")) + }) + } + " + `) + }); describe('pR #112', () => { it.todo('with notAllowEmptyString') it.todo('without notAllowEmptyString')