Skip to content

Commit 140c776

Browse files
logaretmclaude
andcommitted
fix: normalize standard schema error paths to bracket notation (#5108)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7d8cc52 commit 140c776

3 files changed

Lines changed: 73 additions & 2 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"vee-validate": patch
3+
---
4+
5+
Normalize standard schema error paths from dot notation to bracket notation (#5108)

packages/vee-validate/src/validate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
GenericObject,
1212
Path,
1313
} from './types';
14-
import { isCallable, FieldValidationMetaInfo } from '../../shared';
14+
import { isCallable, FieldValidationMetaInfo, normalizeFormPath } from '../../shared';
1515
import { StandardSchemaV1 } from '@standard-schema/spec';
1616

1717
/**
@@ -273,7 +273,7 @@ export async function validateStandardSchema<TValues extends GenericObject, TOut
273273

274274
for (const error of combinedIssues) {
275275
const messages = error.messages;
276-
const path = error.path as Path<TValues>;
276+
const path = normalizeFormPath(error.path) as Path<TValues>;
277277

278278
results[path] = { valid: !messages.length, errors: messages };
279279
if (messages.length) {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { validateStandardSchema } from '../src/validate';
2+
import { z } from 'zod';
3+
4+
// #5108
5+
test('validateStandardSchema normalizes dot notation array paths to bracket notation', async () => {
6+
const schema = z.object({
7+
items: z.array(
8+
z.object({
9+
name: z.string().min(1, 'Name is required'),
10+
id: z.string().min(1, 'ID is required'),
11+
}),
12+
),
13+
});
14+
15+
const result = await validateStandardSchema(schema, {
16+
items: [{ name: '', id: '' }],
17+
});
18+
19+
expect(result.valid).toBe(false);
20+
21+
// Error paths should use bracket notation (items[0].name) not dot notation (items.0.name)
22+
expect(result.errors['items[0].name' as keyof typeof result.errors]).toBe('Name is required');
23+
expect(result.errors['items[0].id' as keyof typeof result.errors]).toBe('ID is required');
24+
25+
// Dot notation should NOT be present
26+
expect(result.errors['items.0.name' as keyof typeof result.errors]).toBeUndefined();
27+
expect(result.errors['items.0.id' as keyof typeof result.errors]).toBeUndefined();
28+
});
29+
30+
test('validateStandardSchema handles nested array paths with multiple indices', async () => {
31+
const schema = z.object({
32+
groups: z.array(
33+
z.object({
34+
members: z.array(
35+
z.object({
36+
email: z.string().email('Invalid email'),
37+
}),
38+
),
39+
}),
40+
),
41+
});
42+
43+
const result = await validateStandardSchema(schema, {
44+
groups: [{ members: [{ email: 'bad' }] }],
45+
});
46+
47+
expect(result.valid).toBe(false);
48+
expect(result.errors['groups[0].members[0].email' as keyof typeof result.errors]).toBe('Invalid email');
49+
expect(result.errors['groups.0.members.0.email' as keyof typeof result.errors]).toBeUndefined();
50+
});
51+
52+
test('validateStandardSchema preserves non-array dot paths', async () => {
53+
const schema = z.object({
54+
user: z.object({
55+
name: z.string().min(1, 'Name is required'),
56+
}),
57+
});
58+
59+
const result = await validateStandardSchema(schema, {
60+
user: { name: '' },
61+
});
62+
63+
expect(result.valid).toBe(false);
64+
// Non-array paths should remain as dot notation
65+
expect(result.errors['user.name' as keyof typeof result.errors]).toBe('Name is required');
66+
});

0 commit comments

Comments
 (0)