Skip to content

refactor: tighten path validation when parsing formdata #983

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 20, 2025

Conversation

edmundhung
Copy link
Owner

To prepare for upcoming changes, we have overhauled the way paths are parsed and formatted:

  • The parser now detects invalid syntax sooner and throws an error immediately on any malformed path.
  • When parsing formdata, parse errors are caught and suppressed—any entry whose path is invalid will simply be omitted from the final submission.

This shouldn't have any impact to existing users. Users who rely on inferred names from field metadata, or who supply explicit names using dot-and-bracket notation, will see no change in behavior.

Copy link

changeset-bot bot commented Jul 11, 2025

🦋 Changeset detected

Latest commit: 6eb1d04

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@conform-to/react Patch
@conform-to/dom Patch
@conform-to/valibot Patch
@conform-to/yup Patch
@conform-to/zod Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

pkg-pr-new bot commented Jul 11, 2025

More templates

@conform-to/dom

npm i https://pkg.pr.new/@conform-to/dom@983

@conform-to/react

npm i https://pkg.pr.new/@conform-to/react@983

@conform-to/valibot

npm i https://pkg.pr.new/@conform-to/valibot@983

@conform-to/validitystate

npm i https://pkg.pr.new/@conform-to/validitystate@983

@conform-to/yup

npm i https://pkg.pr.new/@conform-to/yup@983

@conform-to/zod

npm i https://pkg.pr.new/@conform-to/zod@983

commit: 6eb1d04

const output2 = parseWithValibot(formData2, { schema: schema2 });
expect(output2).toMatchObject({
status: 'success',
value: { nest: [{ name: 'test name' }] },
});

const errorFormData = createFormData('nest[].name', '');
const errorFormData = createFormData('nest[0].name', '');
Copy link
Owner Author

@edmundhung edmundhung Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chimame FYI: you may need to update the tests in your repo. We originally added empty-bracket support to make it easier to resolve validation attributes from the derived constraints, but empty brackets aren't officially supported when parsing formdata as it is ambiguous which item you are referring to in a nested object.

With this refactor, we have added limited empty-bracket support: if we see empty bracket at the end of a path (e.g. items[]), we will initialize an array and append the value. But empty brackets anywhere else in the path remain unsupported.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for letting me know. 👍
I'll keep that in mind when I create tests in the future. I'll also try to change any existing tests that seem to need changing.

Comment on lines +34 to +35
getPathSegments as getPaths,
formatPathSegments as formatPaths,
Copy link
Owner Author

@edmundhung edmundhung Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have renamed several helpers internally. But still exported in the old names with the exact same signature. So it shouldn't break anything.

Comment on lines -21 to -24
createGlobalFormsObserver as unstable_createGlobalFormsObserver,
focus as unstable_focus,
change as unstable_change,
blur as unstable_blur,
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved all these exports to the future export so it's easier to manage as I put up more helpers that are only used by those future APIs.

Comment on lines +25 to +64
test('getPathSegments', () => {
expect(getPathSegments(undefined)).toEqual([]);
expect(getPathSegments('')).toEqual([]);
expect(getPathSegments('title')).toEqual(['title']);
expect(getPathSegments('123')).toEqual(['123']);
expect(getPathSegments('amount.currency')).toEqual(['amount', 'currency']);
expect(getPathSegments('[0]')).toEqual([0]);
expect(getPathSegments('tasks[0]')).toEqual(['tasks', 0]);
expect(getPathSegments('tasks[1].completed')).toEqual([
'tasks',
1,
'completed',
]);
expect(getPathSegments('multiple[0][1][2]')).toEqual(['multiple', 0, 1, 2]);
expect(getPathSegments('books[0].chapters[1]')).toEqual([
'books',
0,
'chapters',
1,
]);
expect(getPathSegments('[].array[].prop')).toEqual(['', 'array', '', 'prop']);
expect(() => getPathSegments('a.b..c...d')).toThrow(
'Invalid path syntax at position 3 in "a.b..c...d"',
);
expect(() => getPathSegments('.')).toThrow(
'Invalid path syntax at position 0 in "."',
);
expect(() => getPathSegments('a[b]')).toThrow(
'Invalid path syntax at position 1 in "a[b]"',
);
expect(() => getPathSegments('[a.b]')).toThrow(
'Invalid path syntax at position 0 in "[a.b]"',
);
expect(() => getPathSegments('a[b].b')).toThrow(
'Invalid path syntax at position 1 in "a[b].b"',
);
expect(() => getPathSegments('a[[]]')).toThrow(
'Invalid path syntax at position 1 in "a[[]]"',
);
});
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should make it clear what kind of paths we accept.

Comment on lines +333 to +338
expect(() =>
setValueAtPath(target, 'foo.bar[].baz', 'test'),
).toThrowError();
expect(() =>
setValueAtPath(target, 'foo.bar[].baz', 'test', { silent: true }),
).not.toThrowError();
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: empty bracket support

@edmundhung edmundhung force-pushed the edmundhung/refactor-dom-utils branch from 1e482a7 to d2c9519 Compare July 13, 2025 09:49
Copy link

cloudflare-workers-and-pages bot commented Jul 13, 2025

Deploying conform with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6eb1d04
Status: ✅  Deploy successful!
Preview URL: https://5d2d7970.conform.pages.dev
Branch Preview URL: https://edmundhung-refactor-dom-util.conform.pages.dev

View logs

@edmundhung edmundhung force-pushed the edmundhung/refactor-dom-utils branch from d2c9519 to 1bf3788 Compare July 20, 2025 13:56
@edmundhung edmundhung merged commit 14c4e52 into main Jul 20, 2025
25 checks passed
@edmundhung edmundhung deleted the edmundhung/refactor-dom-utils branch July 20, 2025 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants