Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-5108-standard-schema-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vee-validate": patch
---

Normalize standard schema error paths from dot notation to bracket notation (#5108)
4 changes: 2 additions & 2 deletions packages/vee-validate/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
GenericObject,
Path,
} from './types';
import { isCallable, FieldValidationMetaInfo } from '../../shared';
import { isCallable, FieldValidationMetaInfo, normalizeFormPath } from '../../shared';
import { StandardSchemaV1 } from '@standard-schema/spec';

/**
Expand Down Expand Up @@ -273,7 +273,7 @@ export async function validateStandardSchema<TValues extends GenericObject, TOut

for (const error of combinedIssues) {
const messages = error.messages;
const path = error.path as Path<TValues>;
const path = normalizeFormPath(error.path) as Path<TValues>;

results[path] = { valid: !messages.length, errors: messages };
if (messages.length) {
Expand Down
66 changes: 66 additions & 0 deletions packages/vee-validate/tests/validateStandardSchema.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { validateStandardSchema } from '../src/validate';
import { z } from 'zod';

// #5108
test('validateStandardSchema normalizes dot notation array paths to bracket notation', async () => {
const schema = z.object({
items: z.array(
z.object({
name: z.string().min(1, 'Name is required'),
id: z.string().min(1, 'ID is required'),
}),
),
});

const result = await validateStandardSchema(schema, {
items: [{ name: '', id: '' }],
});

expect(result.valid).toBe(false);

// Error paths should use bracket notation (items[0].name) not dot notation (items.0.name)
expect(result.errors['items[0].name' as keyof typeof result.errors]).toBe('Name is required');
expect(result.errors['items[0].id' as keyof typeof result.errors]).toBe('ID is required');

// Dot notation should NOT be present
expect(result.errors['items.0.name' as keyof typeof result.errors]).toBeUndefined();
expect(result.errors['items.0.id' as keyof typeof result.errors]).toBeUndefined();
});

test('validateStandardSchema handles nested array paths with multiple indices', async () => {
const schema = z.object({
groups: z.array(
z.object({
members: z.array(
z.object({
email: z.string().email('Invalid email'),
}),
),
}),
),
});

const result = await validateStandardSchema(schema, {
groups: [{ members: [{ email: 'bad' }] }],
});

expect(result.valid).toBe(false);
expect(result.errors['groups[0].members[0].email' as keyof typeof result.errors]).toBe('Invalid email');
expect(result.errors['groups.0.members.0.email' as keyof typeof result.errors]).toBeUndefined();
});

test('validateStandardSchema preserves non-array dot paths', async () => {
const schema = z.object({
user: z.object({
name: z.string().min(1, 'Name is required'),
}),
});

const result = await validateStandardSchema(schema, {
user: { name: '' },
});

expect(result.valid).toBe(false);
// Non-array paths should remain as dot notation
expect(result.errors['user.name' as keyof typeof result.errors]).toBe('Name is required');
});
Loading