Skip to content

Conversation

@duhnnie
Copy link

@duhnnie duhnnie commented Dec 8, 2025

Description

This PR is a V3 port of this fix for this issue originally reported for V2.

@duhnnie duhnnie requested a review from a team as a code owner December 8, 2025 16:24
@welcome
Copy link

welcome bot commented Dec 8, 2025

Thanks for opening this pull request! 🎉 Please check out our contributing guidelines. If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 8, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

ErrorHandler selection now iterates deterministically over mounted sub-app keys and compares trailing-slash–normalized request paths and prefixes to choose the deepest matching sub-app's ErrorHandler; a new test verifies correct handler selection across nested mounts and fallbacks.

Changes

Cohort / File(s) Summary
Error Handler Path Matching
app.go
ErrorHandler now loops over appListKeys, normalizes trailing slashes for ctx.Path() and prefixes, selects the deepest matching mounted sub-app, and delegates to that sub-app's ErrorHandler or falls back to the root handler.
Error Handler Resolution Test
app_test.go
Added TestErrorHandler_PicksRightOne to assert correct ErrorHandler selection for multiple nested/mounted routes (including fallback) and to exercise trailing-slash matching behavior.
Module Manifest
go.mod
Module file present in the diff (no API/export changes).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review normalization logic for edge cases (root path, single-slash, multiple trailing slashes).
  • Verify deterministic ordering and that deepest-prefix selection is correct when prefixes overlap.
  • Check the new test covers expected ambiguous-prefix scenarios and fails for incorrect matching.

Possibly related PRs

Suggested reviewers

  • gaby
  • sixcolors
  • ReneWerner87
  • efectn

Poem

"I hopped through routes both short and long,
With ears attuned to every trailing-song.
I sniffed the prefix, normalized the trail,
Found the deepest match without fail. 🐇✨"

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is minimal and lacks detail. It only references prior fixes without explaining the problem, solution, or changes introduced. Required sections like 'Changes introduced' and proper checklist items are absent. Expand the description to explain what the bug was, how the fix works, confirm test coverage, and check relevant items in the template's checklist section.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title indicates a bug fix for custom error handler invocation in V3, which aligns with the changes made to error handling logic in app.go and the test added in app_test.go.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @duhnnie, 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 ports a critical fix from V2 to V3, addressing an issue where custom error handlers for mounted applications were not always invoked correctly. The changes refine the logic within the ErrorHandler to ensure that the most specific and appropriate custom error handler is consistently applied based on the request path and the application's mounting hierarchy. This enhances the reliability of error handling in complex Fiber applications utilizing sub-applications.

Highlights

  • Corrected Custom Error Handler Invocation: The primary change ensures that the correct custom error handler is invoked for mounted applications, resolving an issue where the wrong handler might have been used.
  • Path Normalization for Mounted Apps: The ErrorHandler now normalizes request paths and application prefixes by adding trailing slashes, leading to more accurate matching when determining which mounted application's error handler to use.
  • Improved Mounted App Iteration: The logic for finding the most specific mounted application has been updated to iterate through a predefined list of keys (appListKeys) rather than directly over the map, which can help with consistent behavior.
  • Comprehensive Test Case Added: A new test TestErrorHandler_PicksRightOne has been introduced to thoroughly verify that the correct error handler is selected across various nested mounted application scenarios.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ReneWerner87 ReneWerner87 added this to v3 Dec 8, 2025
@ReneWerner87 ReneWerner87 added this to the v3 milestone Dec 8, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request ports a fix from V2 to V3, addressing an issue with custom error handler invocation for mounted sub-applications. The changes ensure the correct error handler is selected by normalizing path and prefix strings with a trailing slash and by iterating over mount prefixes in a deterministic, sorted order. A comprehensive test case has been added to verify the fix across various nested mounting scenarios. The implementation appears correct and the new test provides good coverage for the addressed issue.

app_test.go Outdated
Comment on lines 2807 to 2808
// /api/v1/users mount has a custom ErrorHandler
{"/api/v1/use", "/api/v1/use error handler"},
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The comment on line 2807 is misleading. It refers to /api/v1/users, but the test case on the following line is for /api/v1/use. Additionally, in this test setup, the /api/v1/users mount does not have a custom error handler, whereas /api/v1/use does. To avoid confusion for future readers, the comment should be updated to accurately describe the test case.

Suggested change
// /api/v1/users mount has a custom ErrorHandler
{"/api/v1/use", "/api/v1/use error handler"},
// /api/v1/use mount has a custom ErrorHandler
{"_api/v1/use", "/api/v1/use error handler"},

Copy link
Author

Choose a reason for hiding this comment

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

I applied the suggested comment only, next suggestion is not necessary.

app_test.go Outdated
}

for _, testCase := range testCases {
testCase := testCase // Explicitly capture the current value of `testCase`
Copy link
Author

@duhnnie duhnnie Dec 8, 2025

Choose a reason for hiding this comment

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

@gaby @ReneWerner87 This line wasn't included in test for V2, I realized about it at making the port for V3. Should I add it to V2? (even though the test run without failures).

Copy link
Author

Choose a reason for hiding this comment

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

Nevermind about this, the linter already made me remove it.

Copy link
Contributor

@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: 1

🧹 Nitpick comments (1)
app.go (1)

1298-1320: Make deepest-prefix selection independent of mount iteration order.

The new normalization logic for matching mounted sub‑apps looks good, but the prefix‑depth tracking currently updates mountedPrefixParts even for mounts that don’t have a custom ErrorHandler:

if prefix != "" && strings.HasPrefix(normalizedPath, normalizedPrefix) {
    parts := strings.Count(prefix, "/") + 1
    if mountedPrefixParts <= parts {
        if subApp.configured.ErrorHandler != nil {
            mountedErrHandler = subApp.config.ErrorHandler
        }
        mountedPrefixParts = parts
    }
}

This makes the final choice sensitive to the order of appListKeys: if a deeper mount without a custom handler is processed before its shallower parent with a handler, mountedPrefixParts can be bumped past the parent’s depth and the parent’s handler will never be considered, even though it should be the nearest applicable handler.

To decouple correctness from iteration order and only rank among actual candidates, you can gate the depth update on having a configured handler:

-	for _, prefix := range app.mountFields.appListKeys {
-		subApp := app.mountFields.appList[prefix]
-		normalizedPrefix := utils.AddTrailingSlashString(prefix)
-
-		if prefix != "" && strings.HasPrefix(normalizedPath, normalizedPrefix) {
-			// Count slashes instead of splitting - more efficient
-			parts := strings.Count(prefix, "/") + 1
-			if mountedPrefixParts <= parts {
-				if subApp.configured.ErrorHandler != nil {
-					mountedErrHandler = subApp.config.ErrorHandler
-				}
-
-				mountedPrefixParts = parts
-			}
-		}
-	}
+	for _, prefix := range app.mountFields.appListKeys {
+		if prefix == "" {
+			continue
+		}
+
+		subApp := app.mountFields.appList[prefix]
+		if subApp.configured.ErrorHandler == nil {
+			continue // no custom handler → not a candidate
+		}
+
+		normalizedPrefix := utils.AddTrailingSlashString(prefix)
+		if strings.HasPrefix(normalizedPath, normalizedPrefix) {
+			// Count slashes instead of splitting - more efficient
+			parts := strings.Count(prefix, "/") + 1
+			if mountedPrefixParts <= parts {
+				mountedErrHandler = subApp.config.ErrorHandler
+				mountedPrefixParts = parts
+			}
+		}
+	}

This keeps the “deepest matching prefix wins” behavior while ensuring it only depends on the set of mounts with explicit ErrorHandlers, not on how appListKeys is ordered internally.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e600f16 and 60bd0ac.

📒 Files selected for processing (2)
  • app.go (1 hunks)
  • app_test.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

When adding Go tests, always invoke t.Parallel() at the start of each test and subtest to maximize concurrency

Files:

  • app_test.go
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Prefer github.com/gofiber/utils/v2 helpers (for example, utils.Trim) when performing common operations such as string manipulation, whenever it is practical and appropriate for the surrounding code

Files:

  • app_test.go
  • app.go
🧠 Learnings (2)
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.

Applied to files:

  • app_test.go
📚 Learning: 2025-12-07T15:07:23.885Z
Learnt from: CR
Repo: gofiber/fiber PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T15:07:23.885Z
Learning: Applies to **/*_test.go : When adding Go tests, always invoke `t.Parallel()` at the start of each test and subtest to maximize concurrency

Applied to files:

  • app_test.go
🧬 Code graph analysis (1)
app_test.go (1)
app.go (3)
  • New (522-635)
  • Config (115-426)
  • ErrorHandler (59-59)
⏰ 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, ubuntu-latest)
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: unit (1.25.x, macos-latest)
  • GitHub Check: repeated
  • GitHub Check: Compare
  • GitHub Check: lint

@duhnnie duhnnie force-pushed the v3-fix-custom-errorhandler-invokation branch 2 times, most recently from 96b3aa1 to a647a8f Compare December 8, 2025 16:33
@codecov
Copy link

codecov bot commented Dec 8, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.63%. Comparing base (e600f16) to head (e151b4b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3930      +/-   ##
==========================================
- Coverage   91.65%   91.63%   -0.02%     
==========================================
  Files         119      119              
  Lines       10152    10155       +3     
==========================================
+ Hits         9305     9306       +1     
- Misses        537      538       +1     
- Partials      310      311       +1     
Flag Coverage Δ
unittests 91.63% <100.00%> (-0.02%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 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.

Copy link
Contributor

@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 (3)
app_test.go (3)

2819-2819: Remove redundant loop variable capture (Go 1.22+).

In Go 1.22 and later, loop variables are automatically per-iteration, so the explicit capture testCase := testCase is no longer necessary.

Apply this diff:

 	for _, testCase := range testCases {
-		testCase := testCase // Explicitly capture the current value of `testCase`
-
 		t.Run(testCase.path, func(t *testing.T) {

2823-2823: Prefer http.NoBody over nil for request body.

Use http.NoBody instead of nil to clearly indicate an empty request body, consistent with Go conventions.

Apply this diff:

-			resp, err := app.Test(httptest.NewRequest(MethodGet, testCase.path, nil))
+			resp, err := app.Test(httptest.NewRequest(MethodGet, testCase.path, http.NoBody))

2824-2831: Use require.NoError instead of t.Fatal for consistency.

The test file uses testify/require for assertions (e.g., line 2833). For consistency with project conventions, use require.NoError instead of manually checking errors with t.Fatal.

Apply this diff:

 			resp, err := app.Test(httptest.NewRequest(MethodGet, testCase.path, http.NoBody))
-			if err != nil {
-				t.Fatal(err)
-			}
+			require.NoError(t, err)
 
 			body, err := io.ReadAll(resp.Body)
-			if err != nil {
-				t.Fatal(err)
-			}
+			require.NoError(t, err)

Based on learnings, the Fiber project prefers require methods from testify for test assertions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96b3aa1 and a647a8f.

📒 Files selected for processing (1)
  • app_test.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

When adding Go tests, always invoke t.Parallel() at the start of each test and subtest to maximize concurrency

Files:

  • app_test.go
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Prefer github.com/gofiber/utils/v2 helpers (for example, utils.Trim) when performing common operations such as string manipulation, whenever it is practical and appropriate for the surrounding code

Files:

  • app_test.go
🧠 Learnings (9)
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.

Applied to files:

  • app_test.go
📚 Learning: 2024-11-15T07:56:21.623Z
Learnt from: ReneWerner87
Repo: gofiber/fiber PR: 3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.

Applied to files:

  • app_test.go
📚 Learning: 2025-12-07T15:07:23.885Z
Learnt from: CR
Repo: gofiber/fiber PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T15:07:23.885Z
Learning: Applies to **/*_test.go : When adding Go tests, always invoke `t.Parallel()` at the start of each test and subtest to maximize concurrency

Applied to files:

  • app_test.go
📚 Learning: 2025-10-16T07:15:26.529Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:15:26.529Z
Learning: In Fiber v3, net/http handlers (http.Handler, http.HandlerFunc, or raw func(http.ResponseWriter, *http.Request)) can be passed directly to routing methods like app.Get(), app.Post(), etc. The framework automatically detects and wraps them internally via toFiberHandler/collectHandlers. The github.com/gofiber/fiber/v3/middleware/adaptor package is legacy and should not be suggested for tests or code using native net/http handler support.

Applied to files:

  • app_test.go
📚 Learning: 2024-11-10T23:44:13.704Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.

Applied to files:

  • app_test.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/config.go:122-122
Timestamp: 2024-10-08T19:06:06.583Z
Learning: In `DefaultErrorHandler(c *fiber.Ctx, err error)`, since `c` is a pointer to an interface, we need to dereference `*c` when calling interface methods like `SendStatus`.

Applied to files:

  • app_test.go
📚 Learning: 2024-09-25T15:57:10.221Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-09-25T15:57:10.221Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.

Applied to files:

  • app_test.go
📚 Learning: 2024-11-08T04:10:42.990Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3193
File: middleware/cache/cache_test.go:897-897
Timestamp: 2024-11-08T04:10:42.990Z
Learning: In the Fiber framework, `Context()` is being renamed to `RequestCtx()`, and `UserContext()` to `Context()` to improve clarity and align with Go's context conventions.

Applied to files:

  • app_test.go
📚 Learning: 2024-12-13T08:14:22.851Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.851Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.

Applied to files:

  • app_test.go
🪛 GitHub Actions: golangci-lint
app_test.go

[error] 2819-2819: golangci-lint: The copy of the 'for' variable "testCase" can be deleted (Go 1.22+) (copyloopvar)

🪛 GitHub Check: lint
app_test.go

[failure] 2770-2770:
unused-parameter: parameter 'c' seems to be unused, consider removing or renaming it as _ (revive)


[failure] 2823-2823:
httpNoBody: http.NoBody should be preferred to the nil request body (gocritic)


[failure] 2819-2819:
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). (3)
  • GitHub Check: repeated
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: Compare

@duhnnie duhnnie force-pushed the v3-fix-custom-errorhandler-invokation branch from a647a8f to 399266f Compare December 8, 2025 16:38
@duhnnie duhnnie force-pushed the v3-fix-custom-errorhandler-invokation branch from 399266f to e151b4b Compare December 8, 2025 16:44
@duhnnie duhnnie changed the title V3 fix custom errorhandler invokation 🐛 bug: V3 fix custom errorhandler invokation Dec 8, 2025
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.

2 participants