Skip to content

fix(router-core): safely format Standard Schema validation issues#7782

Open
TemRevil wants to merge 1 commit into
TanStack:mainfrom
TemRevil:fix/safe-standard-schema-issue-formatting
Open

fix(router-core): safely format Standard Schema validation issues#7782
TemRevil wants to merge 1 commit into
TanStack:mainfrom
TemRevil:fix/safe-standard-schema-issue-formatting

Conversation

@TemRevil

@TemRevil TemRevil commented Jul 11, 2026

Copy link
Copy Markdown

Closes #7779.

Problem

Two places serialize Standard Schema validator issues the same way:

  • execValidator in start-client-core/src/createServerFn.ts did throw new Error(JSON.stringify(result.issues, undefined, 2)).
  • validateSearch in router-core/src/router.ts did throw new SearchParamError(JSON.stringify(result.issues, undefined, 2), { cause: result }).

The Standard Schema spec allows an issue path to contain any PropertyKey, and a segment can be either a raw key or a { key } object. JSON.stringify throws on a symbol key or a bigint, and on any circular reference an issue object happens to carry. When that happens the thrown error is the serializer's error, not the validation failure, so the actual reason the input was rejected is lost right at the point a developer needs it.

Approach

I started by looking at what a "safe" version of the existing output would be, and a plain JSON.stringify with a replacer that drops non-serializable values felt wrong: it keeps the JSON shape but still leaks the whole internal issue object, and the output is noisy. The useful information in an issue is really just the message and the path, so I settled on a small formatter that reads only those two fields and never touches anything else on the object. That also sidesteps the circular-reference case for free, because it never walks into arbitrary properties.

The path rendering took a couple of tries. A dot path like user.name reads well until a key legitimately contains a dot or a bracket, at which point a.b is ambiguous between one key "a.b" and the nested path a -> b. So each segment is bracketed and quoted with JSON.stringify of the key: ["user"]["name"][0]. Numbers render as [0] so array indices stay readable, and symbols render via String(key) instead of throwing. Because every string key goes through JSON.stringify rather than being used to index into an object, a key named __proto__ is only ever printed as text and can never walk the prototype chain. Root issues with no path render as (root).

The formatter lives in router-core (src/validationError.ts) so both call sites share one implementation. The issue asks to avoid growing the public router-core surface unless there is a supported use case. start-client-core consumes it across the package boundary through workspace:*, which resolves through the published exports map, so it does need to be reachable. I exported it from the main entry to match how existing shared helpers like isRedirect and parseRedirect are shared today. If you would rather keep it off the primary barrel and expose it through a dedicated subpath instead, I am happy to move it.

I also added an optional path field to AnyStandardSchemaValidateIssue in validators.ts so the formatter can read it in a typed way. It is optional, so it does not change any existing usage.

Tests

packages/router-core/tests/validationError.test.ts covers:

  • root issues with no path,
  • nested string paths,
  • numeric (array index) segments,
  • { key } object segments,
  • a literal dotted key vs a real nested path (to prove they do not collide),
  • __proto__ rendered as text without prototype access,
  • symbol segments,
  • multiple issues joined on separate lines,
  • a circular issue object, which previously threw and now formats cleanly.

I have not added the server-function E2E scenarios across multiple Standard Schema libraries or the bundle-size run mentioned in the issue, since those touch a wider surface. Happy to follow up in a separate PR if you would like them in here.

Summary by CodeRabbit

  • New Features

    • Added clearer validation error messages with readable field paths, including nested fields and list items.
    • Exposed a validation-error formatting utility for consistent error presentation.
  • Bug Fixes

    • Validation errors from server functions and asynchronous checks are now formatted consistently instead of displayed as raw JSON.
    • Improved handling for root-level, symbol-based, and unusual field names without causing additional errors.

Standard Schema validators can return issue objects that JSON.stringify
cannot serialize, for example when a path segment is a symbol or the
issue holds a circular reference. Both the server-function validator in
start-client-core and router search validation stringified the raw
issues array, so a value that could not be serialized threw and buried
the real validation failure.

Add a shared internal formatter in router-core that only reads each
issue's message and path, so it never throws on unserializable values.
Paths are rendered with bracketed, quoted segments (for example
["user"]["name"][0]) so a literal key such as "a.b" cannot be confused
with a nested path, and a prototype-named key such as "__proto__" is
only ever printed as text, never used to index into an object. Root
issues render as (root).

Wire the formatter into both call sites and cover root issues, nested
paths, numeric keys, symbol and prototype-named keys, and a circular
issue object with unit tests.
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a shared Standard Schema validation-error formatter, exposes it from router-core, and uses it for router search and server-function validation failures. The formatter supports structured paths, special keys, symbols, multiple issues, and non-serializable issue objects.

Changes

Validation error formatting

Layer / File(s) Summary
Formatter contract and implementation
packages/router-core/src/validators.ts, packages/router-core/src/validationError.ts, packages/router-core/src/index.ts, packages/router-core/tests/validationError.test.ts
Adds structured issue paths and formats root, nested, numeric, symbol, special-key, multiple, and circular issues into readable messages.
Router validation integration
packages/router-core/src/router.ts
Uses formatValidationError for asynchronous search-parameter validation failures.
Server-function validation integration
packages/start-client-core/src/createServerFn.ts
Uses formatValidationError when server-function validation returns issues.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: package: router-core

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Core formatter and call-site integration are present, but the PR adds a public router-core export and omits requested server-function E2E coverage. Keep the formatter internal unless an external use case exists, and add the requested server-function E2E tests for Standard Schema validators.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: safer formatting of Standard Schema validation issues.
Out of Scope Changes check ✅ Passed The changes stay focused on validation formatting, integrations, typings, and tests; no unrelated feature work stands out.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/start-client-core/src/createServerFn.ts`:
- Line 904: Update the if statement handling result.issues to use curly braces
around the throw statement, following the project’s control-flow style
guidelines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7833e68a-61cf-4f6c-8e09-e95351fc1014

📥 Commits

Reviewing files that changed from the base of the PR and between a3e24c3 and 86fde1c.

📒 Files selected for processing (6)
  • packages/router-core/src/index.ts
  • packages/router-core/src/router.ts
  • packages/router-core/src/validationError.ts
  • packages/router-core/src/validators.ts
  • packages/router-core/tests/validationError.test.ts
  • packages/start-client-core/src/createServerFn.ts


if (result.issues)
throw new Error(JSON.stringify(result.issues, undefined, 2))
if (result.issues) throw new Error(formatValidationError(result.issues))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add curly braces to the if statement.

Per the project's coding guidelines for **/*.{ts,tsx,js,jsx}: "Always use curly braces for if, else, loops, and similar control statements. Never write one-line bodies like if (foo) x = 1."

♻️ Proposed fix
-    if (result.issues) throw new Error(formatValidationError(result.issues))
+    if (result.issues) {
+      throw new Error(formatValidationError(result.issues))
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (result.issues) throw new Error(formatValidationError(result.issues))
if (result.issues) {
throw new Error(formatValidationError(result.issues))
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/start-client-core/src/createServerFn.ts` at line 904, Update the if
statement handling result.issues to use curly braces around the throw statement,
following the project’s control-flow style guidelines.

Source: Coding guidelines

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.

fix(start-client-core): safely serialize Standard Schema validation issues

1 participant