Skip to content

🔥 feat: add MatchedRoute for middleware look-ahead - #4601

Open
RubenPari wants to merge 1 commit into
gofiber:mainfrom
RubenPari:fix/2195-matched-route
Open

🔥 feat: add MatchedRoute for middleware look-ahead#4601
RubenPari wants to merge 1 commit into
gofiber:mainfrom
RubenPari:fix/2195-matched-route

Conversation

@RubenPari

Copy link
Copy Markdown

Summary

Adds c.MatchedRoute() so global middleware can inspect the next non-middleware matching route (path/name) before c.Next(), without advancing the handler chain.

This unblocks RBAC and similar middleware patterns that currently see an empty c.Route().Name before the endpoint runs (#2195). Builds on the approach sketched in the add-MatchedRoute-method-to-context branch, adapted to the current treeIndex router and SkipUnmatchedRoutes look-ahead.

Changes

  • MatchedRoute() on Ctx / DefaultCtx (cached, path-rewrite invalidation)
  • Uses scratch params so look-ahead does not clobber c.values
  • Fast path when firstMatchIndex is set
  • Tests: middleware, not found, SkipUnmatchedRoutes
  • Docs for Route / MatchedRoute

Linked issue

Closes #2195

Checklist

  • make generate
  • make format
  • make lint — 0 issues
  • targeted tests pass

Expose the next non-middleware matching route without advancing the
handler chain so global middleware can inspect Path/Name for RBAC.

Closes gofiber#2195
Copilot AI lite review requested due to automatic review settings August 9, 2026 20:50
@RubenPari
RubenPari requested a review from a team as a code owner August 9, 2026 20:50
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Ctx.MatchedRoute() exposes the next matching non-middleware route without advancing execution. DefaultCtx caches and invalidates lookahead results. Tests cover matched, unmatched, and skipped-route cases. Documentation defines the new method and clarifies Route() behavior.

Changes

MatchedRoute API

Layer / File(s) Summary
MatchedRoute API contract
ctx_interface_gen.go, docs/api/ctx.md
Adds MatchedRoute() *Route to Ctx. Documents non-advancing lookup and nil results. Clarifies that Route() returns the last executed route.
Route lookahead and cache invalidation
ctx.go
Resolves the next matching non-middleware route, caches the result, skips rejected candidates, and clears cached state when routing paths or contexts change.
MatchedRoute behavior validation
ctx_test.go
Tests route path and name lookup, unmatched requests, and SkipUnmatchedRoutes behavior.

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

Suggested reviewers: renewerner87

Poem

A rabbit peers down the route,
Finds the path before handlers sprout.
The cache stays clear,
When resets appear,
And middleware knows what’s about.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the new MatchedRoute feature and its middleware look-ahead purpose.
Description check ✅ Passed The description explains the feature, motivation, implementation, tests, documentation, linked issue, and validation steps.
Linked Issues check ✅ Passed The implementation satisfies issue #2195 by exposing the next matching route name and path before middleware calls c.Next().
Out of Scope Changes check ✅ Passed The code, tests, interface, and documentation changes directly support the MatchedRoute feature and issue #2195.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 `@ctx.go`:
- Around line 405-409: Clear c.matchedRoute in DefaultReq.Method() whenever
changing c.methodInt, alongside resetting firstMatchIndex. Add a regression test
that obtains MatchedRoute(), overrides the method through Request().Method(...),
then verifies the subsequent MatchedRoute() does not return the previously
cached route.
🪄 Autofix

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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8abea0f3-6eb5-4de2-9435-179b07dee224

📥 Commits

Reviewing files that changed from the base of the PR and between a2a6d29 and ef681a2.

📒 Files selected for processing (4)
  • ctx.go
  • ctx_interface_gen.go
  • ctx_test.go
  • docs/api/ctx.md

Comment thread ctx.go
Comment on lines +405 to +409
if c.matchedRoute != nil {
return c.matchedRoute
}
if c.methodInt == -1 || c.app == nil {
return nil

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect method mutation and existing cache invalidation.
rg -n -A45 -B4 'func \(c \*DefaultCtx\) Method\(' ctx.go
rg -n -C4 'matchedRoute\s*=|firstMatchIndex\s*=' ctx.go

# Find existing MatchedRoute coverage to extend.
rg -n -C5 'MatchedRoute\(' ctx_test.go

Repository: gofiber/fiber

Length of output: 151


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate relevant files without relying on exact repository root layout.
printf 'Git root: '; git rev-parse --show-toplevel 2>/dev/null || pwd
printf 'ctx.go candidates: '; fd -a '^ctx\.go$' . 2>/dev/null || find . -name ctx.go -type f

printf '\nSearch DefaultCtx.Method and MatchedRoute symbols:\n'
rg -n -C5 'func \(c \*DefaultCtx\) Method\(' . || true
rg -n -C4 'func \(c \*DefaultCtx\) MatchedRoute' . || true
rg -n -C3 'matchedRoute\s*=|firstMatchIndex\s*=' . || true
rg -n -C5 'MatchedRoute\(' . || true

Repository: gofiber/fiber

Length of output: 8640


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Relevant ctx.go sections:\n'
sed -n '394,445p' ctx.go
sed -n '260,330p' ctx.go

printf '\nReq.go Method implementation:\n'
sed -n '910,955p' req.go

printf '\nContext reset methods around Reset:\n'
sed -n '760,815p' ctx.go

printf '\nInspect request method override and MethodInt behavior:\n'
rg -n -C4 'func \(c \*DefaultCtx\) MethodInt\(\)|func UpdateRequestMethod|Request.*Method|MethodInt|HeaderRequestMethod|c\.methodInt|c\.Req.*Header|SetMethod\(' req.go ctx.go router.go

Repository: gofiber/fiber

Length of output: 17239


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Route matching helpers and firstMatchIndex write:\n'
sed -n '720,770p' router.go
rg -n -C6 'firstMatchIndex = ' router.go

printf '\nRead-only behavioral model for cached MatchedRoute after method override:\n'
python3 - <<'PY'
import copy

class Tree:
    def __init__(self, routes_by_path):
        self.routes_by_path = routes_by_path

class App:
    def __init__(self):
        self.treeIndex = {
            0: Tree({"same": "GET_route"}),
            1: Tree({"same": "POST_route"}),
        }

class Ctx:
    def __init__(self):
        self.app = App()
        self.treePathHash = 0
        self.methodInt = 0
        self.firstMatchIndex = 0
        self.matchedRoute = "GET_route"

def matcheRoute(ctx):
    if ctx.matchedRoute is not None:
        return ctx.matchedRoute
    if ctx.methodInt == -1 or ctx.app is None:
        return None
    return ctx.app.treeIndex[ctx.methodInt].routes_by_path["same"]

def method_override(ctx, method_int):
    ctx.methodInt = method_int
    ctx.firstMatchIndex = -1

ctx = Ctx()
before = matcheRoute(ctx)
first_after_method = ctx.firstMatchIndex
method_override(ctx, 1)
second = matcheRoute(ctx)
lookup_after_method = ctx.firstMatchIndex

print({"before": before, "firstMatchIndex_after_method": first_after_method, "after_method": second, "firstMatchIndex_after_second_call": lookup_after_method})
PY

Repository: gofiber/fiber

Length of output: 2381


Invalidate matchedRoute when DefaultReq.Method changes methodInt.

MatchedRoute() returns c.matchedRoute before inspecting c.methodInt. DefaultReq.Method() updates c.methodInt and clears firstMatchIndex, but it keeps the old c.matchedRoute cached. Middleware that calls c.Request().Method("POST") can return the previous GET matched route on the first MatchedRoute() call after the override. Clear c.matchedRoute in DefaultReq.Method() and add a regression test covering MatchedRoute(), Request().Method(...), and another MatchedRoute() call.

🤖 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 `@ctx.go` around lines 405 - 409, Clear c.matchedRoute in DefaultReq.Method()
whenever changing c.methodInt, alongside resetting firstMatchIndex. Add a
regression test that obtains MatchedRoute(), overrides the method through
Request().Method(...), then verifies the subsequent MatchedRoute() does not
return the previously cached route.

Copilot AI 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.

Pull request overview

Adds a new Ctx API (MatchedRoute()) to let middleware inspect the next matching non-middleware endpoint route (path/name) before calling Next(), addressing the common “route name is empty in pre-handler middleware” use case (RBAC, logging, etc.).

Changes:

  • Introduces MatchedRoute() on Ctx/DefaultCtx with look-ahead logic over the route tree, including a fast path when SkipUnmatchedRoutes pre-resolves an endpoint index.
  • Adds caching + invalidation hooks for path normalization / rewrites to avoid recomputing the look-ahead.
  • Adds tests for middleware usage, not-found behavior, and SkipUnmatchedRoutes, plus API docs updates.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

File Description
docs/api/ctx.md Documents MatchedRoute() and clarifies Route() semantics in middleware.
ctx.go Implements DefaultCtx.MatchedRoute() and caching/invalidation behavior.
ctx_test.go Adds coverage for middleware look-ahead, not-found, and SkipUnmatchedRoutes behavior.
ctx_interface_gen.go Extends the generated Ctx interface to include MatchedRoute().
Files not reviewed (1)
  • ctx_interface_gen.go: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ctx.go
Comment on lines +401 to +410
// Already on a non-middleware endpoint.
if c.route != nil && !c.route.use && !c.route.mount {
return c.route
}
if c.matchedRoute != nil {
return c.matchedRoute
}
if c.methodInt == -1 || c.app == nil {
return nil
}
Comment thread ctx.go
Comment on lines +424 to +438
tree := c.app.treeIndex[c.methodInt].lookup(c.treePathHash)
detectionPath := utils.UnsafeString(c.detectionPath)
path := utils.UnsafeString(c.path)
pathSlashes := c.pathSlashCount(c.app)
// Use a scratch params buffer so look-ahead does not clobber c.values.
var scratch [maxParams]string

for i := c.indexRoute + 1; i < len(tree); i++ {
route := tree[i]
if route.mount || route.use {
continue
}
if route.prefixRejects(pathHeadWord(detectionPath)) {
continue
}
@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.47059% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.44%. Comparing base (3f59010) to head (ef681a2).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
ctx.go 76.47% 4 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4601      +/-   ##
==========================================
- Coverage   93.47%   93.44%   -0.04%     
==========================================
  Files         140      140              
  Lines       14983    15017      +34     
==========================================
+ Hits        14006    14032      +26     
- Misses        608      612       +4     
- Partials      369      373       +4     
Flag Coverage Δ
unittests 93.44% <76.47%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@RubenPari

Copy link
Copy Markdown
Author

@copilot Fix the code for all comments in this review thread.

When a review comment includes a suggested change, apply the suggestion exactly.

Do not make changes beyond what is described in the linked review thread.

@gaby

gaby commented Aug 9, 2026

Copy link
Copy Markdown
Member

@RubenPari Thank you for your contributions. However, in the future, please refrain from submitting multiple pull requests (you have 10 open right now) simultaneously.

Reviewing each request requires a significant amount of time from our team.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

🚀 [Feature]: Get Route Name/Path inside Middlewares

4 participants