Skip to content

Comments

feat(add): allow escaping : and * using backslash#175

Merged
pi0 merged 7 commits intomainfrom
feat/add-escape
Dec 15, 2025
Merged

feat(add): allow escaping : and * using backslash#175
pi0 merged 7 commits intomainfrom
feat/add-escape

Conversation

@pi0
Copy link
Member

@pi0 pi0 commented Dec 15, 2025

resolves h3js/h3#1266

: and * in patterns has special meaning. It is possible to register routes url encoded like /some%3A3 to match /some:3 but it is less intuative.

This PR allows using backslash escape (like regex) limited to register patterns like /some\\:3

Summary by CodeRabbit

  • Bug Fixes

    • Escaped ":" and "*" in route patterns are now recognized as literal characters so static routes match, cache, and display correctly (with encoded display).
  • Tests

    • Added and updated tests to cover routes with escaped characters and verify matching and snapshot rendering.
  • Documentation

    • Added a README tip showing how to escape ":" and "*" in route patterns.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 15, 2025

Warning

Rate limit exceeded

@pi0 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 7 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 3021fe9 and 395a161.

📒 Files selected for processing (7)
  • README.md (1 hunks)
  • src/operations/add.ts (4 hunks)
  • test/.snapshot/compiled-aot.mjs (2 hunks)
  • test/.snapshot/compiled-jit.mjs (1 hunks)
  • test/find.test.ts (4 hunks)
  • test/regexp.test.ts (1 hunks)
  • test/router.test.ts (2 hunks)
📝 Walkthrough

Walkthrough

addRoute now normalizes backslash-escaped literals before segment splitting: escaped colons are percent-encoded, escaped single/double asterisks are unescaped to literal */**, and fully-static routes are cached under ctx.static with a leading slash. Tests, snapshots, and README were updated to reflect encoded/static-route keys.

Changes

Cohort / File(s) Change Summary
Route normalization & static caching
src/operations/add.ts
Pre-segment preprocessing: replace \: with %3A, convert \** and \*\*** before classification; use a mutable let for segment transformations; when a route has no params, store the final static key as "/" + segments.join("/") under ctx.static.
Unit tests (router/find/regexp)
test/router.test.ts, test/find.test.ts, test/regexp.test.ts
Added route entries and assertions for an escaped-literal route (source /static\\:path/\\*\\*), updated expectations and snapshots to show percent-encoded colon (/static%3Apath) and nested /** branch; adjusted matching expectations for the new static route.
Compiled snapshots (AOT / JIT)
test/.snapshot/compiled-aot.mjs, test/.snapshot/compiled-jit.mjs
Snapshot outputs updated to include the new route branch (/static%3Apath/*/**) and to shift route handler indices/branches accordingly.
Documentation
README.md
Added a TIP explaining how to escape literal : and * in patterns (use double backslashes) with examples showing the encoded representation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to src/operations/add.ts for edge-cases in the normalization (only intended escapes are transformed, ordering of replacements).
  • Review static-route cache key formation and any impact on lookup/matching/path display.
  • Verify updated snapshots/compiled outputs correspond exactly to the new matching behavior.

Possibly related PRs

Poem

🐇 I hopped where colons feared to roam,
A backslash hummed and found them home.
Stars unmasked, percented light,
Paths now safe in routed sight,
I nibble bugs and code by night. 🥕

Pre-merge checks and finishing touches

✅ 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 and accurately summarizes the main change: allowing backslash escaping of : and * characters in route patterns.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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 and usage tips.

@codecov
Copy link

codecov bot commented Dec 15, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pi0 pi0 changed the title feat: allow escaping : using backslash feat: allow escaping literal : and * using backslash Dec 15, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
test/router.test.ts (1)

489-489: Add test coverage for routing behavior, not just insertion.

The test validates that the escaped route is inserted correctly into the tree, but doesn't verify the routing behavior. Consider adding tests to confirm:

  1. The route matches the literal path /test:special (with encoded colon in the request)
  2. The route does NOT match a path like /test/special or treat :special as a parameter
  3. Edge cases like /test%3Aspecial (pre-encoded) match correctly

Add a dedicated test in the "Router lookup" section:

describe("escaped special characters", function () {
  testRouter(
    ["/test\\:special", "/test/:param"],
    (router) =>
      expect(formatTree(router.root)).toMatchInlineSnapshot(`...`),
    {
      "/test:special": {
        data: { path: "/test\\:special" },
      },
      "/test%3Aspecial": {
        data: { path: "/test\\:special" },
      },
      "/test/value": {
        data: { path: "/test/:param" },
        params: { param: "value" },
      },
    },
  );
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da797c4 and 5f9db3b.

📒 Files selected for processing (2)
  • src/operations/add.ts (1 hunks)
  • test/router.test.ts (2 hunks)
🔇 Additional comments (2)
test/router.test.ts (1)

514-515: LGTM - Snapshot correctly reflects the encoding behavior.

The snapshot update correctly shows:

  • Line 514: Incidental formatting fix (removed extraneous quote)
  • Line 515: The escaped route is stored as /test%3Aspecial (URL-encoded) in the tree while displaying the original /test\\:special form

This validates that the escape sequence is processed as intended: the backslash-escaped colon becomes a literal %3A in the route tree, preventing it from being interpreted as a parameter delimiter.

src/operations/add.ts (1)

29-32: Correct regex pattern: the implementation escapes both : and *, not just :.

The actual regex at line 30 is /\\([:*])/g, which encodes both escaped colons (\:%3A) and escaped asterisks (\*%2A). A test case confirms the colon escape functionality (/test\:special/test%3Aspecial).

However, there are gaps in test coverage:

  • Asterisk escaping (\*) has no test case despite being included in the regex pattern
  • Edge cases remain untested: multiple backslashes (\\:, \\\:), interaction with regex params like /:id:regexp\:escaped
  • No escape sequence behavior is documented in the README

Consider adding test cases for asterisk escaping and edge cases to ensure the broader escape handling is reliable.

Likely an incorrect or invalid review comment.

@pi0 pi0 changed the title feat: allow escaping literal : and * using backslash feat: allow escaping :, * and / using backslash Dec 15, 2025
@pi0 pi0 changed the title feat: allow escaping :, * and / using backslash feat: allow escaping : and * using backslash Dec 15, 2025
@pi0 pi0 changed the title feat: allow escaping : and * using backslash feat(add): allow escaping : and * using backslash Dec 15, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
README.md (1)

94-95: Clarify escapeable characters and simplify the example

The TIP is great, but the wording says only : and * are escapeable while the example also escapes / (\\/), and the combined /static\\:path\\/\\*\\* example is quite dense to parse.

Consider explicitly listing all supported escapeable chars (e.g. :, *, /) and using one or two simpler examples, for instance:

  • /static\\:path → matches /static:path
  • /files\\/download → matches /files/download
  • /literal\\*stars → matches /literal*stars

This would make the feature and the required double backslashes in JS string literals easier to grasp.

test/router.test.ts (1)

489-517: Escaped-route insertion snapshot looks good; consider adding direct matching tests

The new "/static\\:path\\/\\*\\*" entry and corresponding snapshot for /static%3Apath\\/** correctly exercise the insertion and tree formatting for an escaped pattern.

To fully lock in the intended behavior, it would help to add explicit findRoute (and ideally compiled matcher) assertions for this case, e.g. along the lines of:

const router = createRouter([
  "/static\\:path\\/\\*\\*",
]);

expect(findRoute(router, "GET", "/static:path/**")).toMatchObject({
  data: { path: "/static\\:path\\/\\*\\*" },
});

// And possibly clarify via a second assertion whether this should or should not match:
expect(findRoute(router, "GET", "/static:path/other")).toBeUndefined(); // or adjust to desired behavior

That way, regressions in how escaped :, *, and / are interpreted would surface even if the internal tree formatting changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 260e3b9 and 3021fe9.

📒 Files selected for processing (3)
  • README.md (1 hunks)
  • src/operations/add.ts (1 hunks)
  • test/router.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/operations/add.ts
🧰 Additional context used
🪛 LanguageTool
README.md

[style] ~94-~94: Using many exclamation marks might seem excessive (in this case: 5 exclamation marks for a text that’s 1625 characters long)
Context: ...hod should always be UPPERCASE. > [!TIP] > If you need to register a pattern...

(EN_EXCESSIVE_EXCLAMATION)

@pi0 pi0 marked this pull request as draft December 15, 2025 16:02
@pi0 pi0 marked this pull request as ready for review December 15, 2025 16:33
@pi0 pi0 merged commit 86e07f3 into main Dec 15, 2025
3 of 4 checks passed
@pi0 pi0 deleted the feat/add-escape branch December 15, 2025 16:36
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.

cannot register path containing :

1 participant