Ensure middleware prefix matching requires slash boundary#3755
Ensure middleware prefix matching requires slash boundary#3755ReneWerner87 merged 7 commits intomainfrom
Conversation
WalkthroughBoundary validation was added for partial/prefix route matches: partial matches must be exact or end at a slash. Matching logic now computes consumed length and enforces slash-or-exact boundaries; tests, benchmarks, and docs were updated to verify and describe the stricter semantics. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Client
participant R as Router
participant P as PathMatcher
participant H as Handler/Middleware
C->>R: HTTP request (e.g. /api/foo/bar or /apiv1)
R->>P: match(route, detectingPath, partialCheck)
Note over P: compute consumedLength\nif partialCheck -> hasPartialMatchBoundary(detectingPath, plen)
alt Boundary-valid partial or full match
P-->>R: Match OK (params if any)
R->>H: Invoke handler/middleware
H-->>C: 200 OK
else No boundary or no match
P-->>R: No match
R-->>C: 404 Not Found
end
rect rgba(200,235,255,0.18)
Note right of P: New: partial/prefix matches now require\nslash boundary or exact endpoint
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate 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. Comment |
Summary of ChangesHello @ReneWerner87, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request refines the routing logic to ensure that middleware prefixes are matched more precisely. Previously, a middleware defined for Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Pull Request Overview
Tighten middleware prefix matching so that prefixes must end at a slash boundary or fully match, and share a helper between the router and path parser to enforce this consistently.
- Introduce hasPartialMatchBoundary and use it in both the router and route parser.
- Add tests for router, groups, and path parsing to validate the new boundary behavior.
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| router.go | Enforces slash-boundary checks for middleware prefix matches. |
| path.go | Adds hasPartialMatchBoundary and applies it in the parser’s partial match logic. |
| router_test.go | Adds a test ensuring non-boundary prefixes do not match. |
| app_test.go | Adds group middleware tests for boundary and non-boundary scenarios. |
| path_testcases_test.go | Adds parser fixtures ensuring partial matches respect slash boundaries. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3755 +/- ##
==========================================
- Coverage 91.93% 91.44% -0.50%
==========================================
Files 113 113
Lines 11700 11824 +124
==========================================
+ Hits 10756 10812 +56
- Misses 684 745 +61
- Partials 260 267 +7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Code Review
This pull request tightens middleware prefix matching to require a slash boundary, which is a great improvement for routing predictability and aligns Fiber's behavior with other popular web frameworks. The changes are well-implemented by introducing a shared hasPartialMatchBoundary helper function and applying it consistently in both the router and the path parser. The new logic is sound and is thoroughly covered by new tests for application-level middleware, group middleware, and the path parser itself. The code is clear and of high quality. I couldn't find any issues.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
path.go (1)
501-520: Helper looks correct; simplify and add brief docLogic is sound. Tiny simplification and a short doc comment improve readability without changing behavior.
+// hasPartialMatchBoundary reports whether matchedLength either consumes the full path +// or ends at a '/' boundary (before or after). func hasPartialMatchBoundary(path string, matchedLength int) bool { if matchedLength < 0 || matchedLength > len(path) { return false } - if matchedLength == len(path) { - return true - } - if matchedLength == 0 { - return false - } - if path[matchedLength-1] == slashDelimiter { - return true - } - if matchedLength < len(path) && path[matchedLength] == slashDelimiter { - return true - } - - return false + if matchedLength == 0 || matchedLength == len(path) { + return matchedLength == len(path) + } + return path[matchedLength-1] == slashDelimiter || path[matchedLength] == slashDelimiter }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
app_test.go(1 hunks)path.go(2 hunks)path_testcases_test.go(1 hunks)router.go(1 hunks)router_test.go(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go
📄 CodeRabbit inference engine (AGENTS.md)
**/*.go: Format Go code using gofumpt (enforced viamake format)
Ensure code passes golangci-lint checks (enforced viamake lint)
Optimize struct field alignment using betteralign (enforced viamake betteralign)
Modernize Go code using gopls modernize (enforced viamake modernize)
Files:
router_test.gopath.gorouter.goapp_test.gopath_testcases_test.go
🧬 Code graph analysis (2)
router_test.go (2)
app.go (1)
New(515-628)constants.go (3)
StatusOK(52-52)MethodGet(5-5)StatusNotFound(77-77)
app_test.go (2)
group.go (1)
Group(14-21)constants.go (3)
StatusOK(52-52)MethodGet(5-5)StatusNotFound(77-77)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: unit (1.25.x, windows-latest)
- GitHub Check: repeated
- GitHub Check: Compare
🔇 Additional comments (5)
router.go (1)
97-99: Correct: enforce slash-boundary for middleware prefixesThis fixes the long-standing “/foo” matching “/foobar” issue by requiring a boundary. Behavior remains consistent for "/foo", "/foo/", and "/foo/…". Nice alignment with path parser logic.
Please confirm expected behavior with StrictRouting=true remains: "/foo" middleware still matches "/foo/" (slash boundary) but not "/foobar".
path.go (1)
523-571: Boundary-enforced partial matches are handled correctlyStoring originalDetectionPath and validating the consumedLength with the boundary helper is the right approach. Keeps “partialCheck” semantics tight and consistent with router middleware prefixes.
Consider adding one more parser test ensuring a non-slash boundary fails for partialCheck (e.g., pattern "/partialCheck/foo" vs "/partialCheck/foox"). The router tests cover a similar case already, but a direct parser case helps guard regressions.
path_testcases_test.go (1)
347-364: Good coverage for partialCheck boundariesThe exact, trailing slash, nested, and negative (“foobar”) cases clearly validate the boundary rule. Looks good.
router_test.go (1)
312-325: LGTM: regression test preventing “/foo” from matching “/foobar”Clear and minimal. Ensures the middleware boundary rule stays intact.
app_test.go (1)
712-730: LGTM: group middleware boundary behavior validatedConfirms grouped prefix "/api" with subpath "/foo" behaves correctly for "/api/foo/bar" and not for "/api/foobar".
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
docs/guide/routing.md (1)
327-329: Clarify boundary semantics; avoid time-relative wording.Spell out that the boundary is "/" or end-of-path and include the trailing-slash case; also drop "now" to keep docs timeless.
Apply this edit to the note:
-:::note -Prefix matches must now end at a slash boundary (or be an exact match). For example, `/api` runs for `/api` and `/api/users` but no longer for `/apiv2`. Parameter tokens such as `:name`, `:name?`, `*`, and `+` are still expanded before this boundary check runs. -::: +:::note +Prefix matches end at a slash boundary ("/") or are an exact match. For example, `/api` matches `/api`, `/api/`, and `/api/users`, but not `/apiv2`. Parameter tokens such as `:name`, `:name?`, `*`, and `+` are expanded before this boundary check runs. +:::Please confirm that
/apiindeed matches/api/under the new rule; if not, we should adjust the example accordingly and explicitly state the trailing-slash behavior.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
docs/guide/grouping.md(1 hunks)docs/guide/routing.md(1 hunks)docs/partials/routing/handler.md(1 hunks)docs/whats_new.md(1 hunks)
✅ Files skipped from review due to trivial changes (3)
- docs/whats_new.md
- docs/partials/routing/handler.md
- docs/guide/grouping.md
🧰 Additional context used
📓 Path-based instructions (1)
docs/**
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Review and update the contents of the
docsfolder if necessary when modifying code
Files:
docs/guide/routing.md
🧠 Learnings (1)
📚 Learning: 2024-12-15T19:56:45.935Z
Learnt from: ckoch786
PR: gofiber/fiber#3230
File: docs/whats_new.md:944-951
Timestamp: 2024-12-15T19:56:45.935Z
Learning: Detailed usage examples and explanations for new methods like `RemoveRoute` and `RemoveRouteByName` are documented in `docs/api/app.md`, so it's unnecessary to duplicate them in `docs/whats_new.md`.
Applied to files:
docs/guide/routing.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Compare
- GitHub Check: unit (1.25.x, windows-latest)
- GitHub Check: repeated
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
router_test.go (2)
312-324: Good negative case; add a positive control for exact boundary.Keep this test; also assert that exact "/foo" matches to guard against regressions.
func Test_Route_Match_Middleware_NoBoundary(t *testing.T) { t.Parallel() app := New() app.Use("/foo", func(c Ctx) error { return c.SendStatus(StatusOK) }) resp, err := app.Test(httptest.NewRequest(MethodGet, "/foobar", nil)) require.NoError(t, err, "app.Test(req)") require.Equal(t, StatusNotFound, resp.StatusCode, "Status code") + + // positive control: exact boundary must match + resp, err = app.Test(httptest.NewRequest(MethodGet, "/foo", nil)) + require.NoError(t, err) + require.Equal(t, StatusOK, resp.StatusCode) }
708-801: Drop redundanttt := ttin these subtestsRemove both occurrences in router_test.go (lines 690 and 777); the subtests do not call t.Parallel(), so the extra capture is unnecessary and unblocks the linter.
- for _, tt := range testCases { - tt := tt + for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { app := New(Config{StrictRouting: tt.strictRouting})
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
router_test.go(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go
📄 CodeRabbit inference engine (AGENTS.md)
**/*.go: Format Go code using gofumpt (enforced viamake format)
Ensure code passes golangci-lint checks (enforced viamake lint)
Optimize struct field alignment using betteralign (enforced viamake betteralign)
Modernize Go code using gopls modernize (enforced viamake modernize)
Files:
router_test.go
🧬 Code graph analysis (1)
router_test.go (2)
app.go (2)
New(515-628)Config(113-418)group.go (1)
Group(14-21)
🪛 GitHub Check: lint
router_test.go
[failure] 777-777:
The copy of the 'for' variable "tt" can be deleted (Go 1.22+) (copyloopvar)
[failure] 690-690:
The copy of the 'for' variable "tt" can be deleted (Go 1.22+) (copyloopvar)
🪛 GitHub Actions: golangci-lint
router_test.go
[error] 690-690: golangci-lint: The copy of the 'for' variable "tt" can be deleted (Go 1.22+) (copyloopvar)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: unit (1.25.x, macos-latest)
- GitHub Check: unit (1.25.x, windows-latest)
- GitHub Check: unit (1.25.x, macos-13)
- GitHub Check: Compare
- GitHub Check: Analyse
- GitHub Check: repeated
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
path_test.go (1)
176-224: Add a few more edge cases to harden boundary semantics.Consider covering start-of-path, trailing-slash exact match, and UTF‑8 adjacent to the boundary.
Suggested additions:
testCases := []struct { @@ { name: "no boundary", path: "/demo/child", matchedLength: len("/dem"), expected: false, }, + { + name: "boundary at start (previous is implicit start slash)", + path: "/child", + matchedLength: 1, // after leading '/' + expected: true, + }, + { + name: "exact match with trailing slash", + path: "/demo/", + matchedLength: len("/demo/"), + expected: true, + }, + { + name: "UTF-8 next rune slash", + path: "/dęmo/child", + matchedLength: len("/dęmo"), // next byte is '/' + expected: true, + },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
path_test.go(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go
📄 CodeRabbit inference engine (AGENTS.md)
**/*.go: Format Go code using gofumpt (enforced viamake format)
Ensure code passes golangci-lint checks (enforced viamake lint)
Optimize struct field alignment using betteralign (enforced viamake betteralign)
Modernize Go code using gopls modernize (enforced viamake modernize)
Files:
path_test.go
🪛 GitHub Check: lint
path_test.go
[failure] 227-227:
The copy of the 'for' variable "testCase" can be deleted (Go 1.22+) (copyloopvar)
🪛 GitHub Actions: golangci-lint
path_test.go
[error] 227-227: golangci-lint: copyloopvar - The copy of the 'for' variable "testCase" can be deleted (Go 1.22+) (copyloopvar).
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: unit (1.25.x, windows-latest)
- GitHub Check: unit (1.25.x, macos-latest)
- GitHub Check: unit (1.25.x, macos-13)
- GitHub Check: repeated
- GitHub Check: Compare
🔇 Additional comments (1)
path_test.go (1)
173-181: Good focused test for the new boundary helper.Covers negative/overflow, zero, exact, and both-side slash boundaries; subtests run in parallel. Nice.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
path_test.go (1)
173-233: Optional: align naming with file convention and clarify subtest titles; add root-path edge case.
- For consistency with nearby tests (e.g., Test_Path_*), consider renaming the function.
- “rune” → “char” in titles is clearer since checks are byte-wise against '/'.
- Add a root “/” exact-match case.
Apply:
-func TestHasPartialMatchBoundary(t *testing.T) { +func Test_Path_hasPartialMatchBoundary(t *testing.T) { @@ - { - name: "previous rune slash", + { + name: "previous char is slash", path: "/demo/child", matchedLength: len("/demo/"), expected: true, }, { - name: "next rune slash", + name: "next char is slash", path: "/demo/child", matchedLength: len("/demo"), expected: true, }, @@ { name: "no boundary", path: "/demo/child", matchedLength: len("/dem"), expected: false, }, + { + name: "root exact", + path: "/", + matchedLength: len("/"), + expected: true, + },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
path_test.go(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go
📄 CodeRabbit inference engine (AGENTS.md)
**/*.go: Format Go code using gofumpt (enforced viamake format)
Ensure code passes golangci-lint checks (enforced viamake lint)
Optimize struct field alignment using betteralign (enforced viamake betteralign)
Modernize Go code using gopls modernize (enforced viamake modernize)
Files:
path_test.go
🔇 Additional comments (1)
path_test.go (1)
173-233: LGTM: solid boundary coverage; no copyloopvar.
- Table-driven cases hit negatives, overflows, exact, and slash-adjacent boundaries.
- Good use of Go 1.22+ range semantics; no redundant loop-var self-assign, so lint should be clean.
Summary
Testing
https://chatgpt.com/codex/tasks/task_e_68cd6b1fb1648326ae0646c347dbb672