Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔥 feat: Add Support for Removing Routes #3230

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

ckoch786
Copy link

@ckoch786 ckoch786 commented Dec 5, 2024

Description

  • Add method to Remove Routes
  • Updated register method to prevent duplicate routes.

Fixes #3098

Changes introduced

Type of change

  • New feature (non-breaking change which adds functionality)
  • Enhancement (improvement to existing features and functionality)
  • Documentation update (changes to documentation)

@ckoch786 ckoch786 requested a review from a team as a code owner December 5, 2024 06:32
@ckoch786 ckoch786 requested review from gaby, sixcolors, ReneWerner87 and efectn and removed request for a team December 5, 2024 06:32
Copy link

welcome bot commented Dec 5, 2024

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

Copy link
Contributor

coderabbitai bot commented Dec 5, 2024

Walkthrough

The pull request introduces significant enhancements to route management in the Fiber web framework. The changes focus on providing methods to remove routes dynamically, including RemoveRoute and RemoveRouteByName. These new methods allow developers to delete existing routes by path or name, with support for specifying HTTP methods. The implementation includes robust handling of route removal, ensuring proper management of the routing tree and handler counts.

Changes

File Change Summary
router.go Added methods for route removal: routeExists, RemoveRoute, RemoveRouteByName, and deleteRoute. Enhanced route management logic to handle duplicate routes and improve route manipulation.
router_test.go Introduced comprehensive test cases for route removal, including tests for removing routes by name, nested routes, parameterized routes, and concurrent route modifications. Added helper functions to support testing.
docs/api/app.md Updated documentation to explain new route removal methods RemoveRoute and RemoveRouteByName, including usage examples and importance of RebuildTree().
docs/whats_new.md Documented new route management methods as part of Fiber v3 updates.

Assessment against linked issues

Objective Addressed Explanation
Allow removing registered routes [#3098]
Provide method to unregister routes
Maintain API stability

Possibly related PRs

Suggested reviewers

  • sixcolors
  • ReneWerner87
  • efectn

Poem

🐰 Hop, skip, and route away,
Fiber's router finds a new play!
Routes come and go with grace,
Removing paths without a trace.
Dynamic routing, oh what fun! 🚀


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92ce4d8 and fa732af.

📒 Files selected for processing (1)
  • docs/whats_new.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/whats_new.md

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ckoch786 ckoch786 changed the title Feature/3098 allow removing registered route 🔥feat: RemoveRoute Dec 5, 2024
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: 3

🧹 Outside diff range and nitpick comments (7)
router.go (2)

310-310: Add a space after '//' in the comment for proper formatting

The comment at line 310 should have a space after the // for better readability and to adhere to Go's style guidelines.

Apply this diff to fix the comment formatting:

-//Decrement global handler count
+// Decrement global handler count
🧰 Tools
🪛 golangci-lint (1.62.2)

310-310: File is not gofumpt-ed with -extra

(gofumpt)


310-310: commentFormatting: put a space between // and comment text

(gocritic)


392-395: Simplify the return statement by directly returning the boolean expression

You can simplify the code by returning the result of the condition directly, which improves readability.

Apply this diff to simplify the code:

 if r.path == pathRaw {
     return true
 }
 return false
+// Simplify the return statement
+return r.path == pathRaw
🧰 Tools
🪛 golangci-lint (1.62.2)

392-392: S1008: should use 'return r.path == pathRaw' instead of 'if r.path == pathRaw { return true }; return false'

(gosimple)

router_test.go (5)

403-403: Remove unnecessary blank line at the start of the block

There is an extra empty line at the start of the block, which is unnecessary and can be removed for better code readability.

Apply this diff to remove the unnecessary line:

-	
 	app.Get("/test", func(c Ctx) error {
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 403-403: empty-lines: extra empty line at the start of a block

(revive)


403-403: unnecessary leading newline

(whitespace)


414-414: Remove unnecessary blank line at the start of the block

There is an extra empty line at the start of the second route registration block, which can be removed for consistency and readability.

Apply this diff:

-	
 	app.Get("/test", func(c Ctx) error {
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 414-414: empty-lines: extra empty line at the start of a block

(revive)


414-414: unnecessary leading newline

(whitespace)


471-471: Remove unnecessary blank line at the start of the block

An extra empty line is present at the start of the Test_App_Remove_Route_With_Middleware function block. Removing it improves code consistency.

Apply this diff:

-	
 func Test_App_Remove_Route_With_Middleware(t *testing.T) {

479-479: Remove unnecessary blank line inside the function

There's an extra blank line inside the function which can be removed to improve code readability.

Apply this diff:

-
 	app.Get("/test", func(c Ctx) error {
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 479-479: empty-lines: extra empty line at the start of a block

(revive)


479-479: unnecessary leading newline

(whitespace)


490-490: Remove unnecessary blank line at the start of the block

Another extra empty line is present before the second route registration. Removing it maintains consistency.

Apply this diff:

-
 	app.Get("/test", func(c Ctx) error {
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 490-490: empty-lines: extra empty line at the start of a block

(revive)


490-490: unnecessary leading newline

(whitespace)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 89a0cd3 and f289503.

📒 Files selected for processing (2)
  • router.go (5 hunks)
  • router_test.go (1 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
router_test.go

[warning] 403-403: empty-lines: extra empty line at the start of a block

(revive)


403-403: unnecessary leading newline

(whitespace)


404-404: File is not gofumpt-ed with -extra

(gofumpt)


[warning] 414-414: empty-lines: extra empty line at the start of a block

(revive)


414-414: unnecessary leading newline

(whitespace)


415-415: File is not gofumpt-ed with -extra

(gofumpt)


[warning] 479-479: empty-lines: extra empty line at the start of a block

(revive)


479-479: unnecessary leading newline

(whitespace)


480-480: File is not gofumpt-ed with -extra

(gofumpt)


[warning] 490-490: empty-lines: extra empty line at the start of a block

(revive)


490-490: unnecessary leading newline

(whitespace)


491-491: File is not gofumpt-ed with -extra

(gofumpt)

router.go

310-310: File is not gofumpt-ed with -extra

(gofumpt)


310-310: commentFormatting: put a space between // and comment text

(gocritic)


390-390: File is not gofumpt-ed with -extra

(gofumpt)


392-392: S1008: should use 'return r.path == pathRaw' instead of 'if r.path == pathRaw { return true }; return false'

(gosimple)


[warning] 408-408: confusing-naming: Method 'removeRoute' differs only by capitalization to method 'RemoveRoute' in the same source file

(revive)

🔇 Additional comments (1)
router_test.go (1)

403-416: Verify duplicate route registration in 'Test_App_Remove_Route'

In Test_App_Remove_Route, the route /test is registered twice with different handlers. This may lead to unintended behavior, as the second registration may overwrite the first. Please confirm if this is intentional or adjust the test accordingly.

🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 403-403: empty-lines: extra empty line at the start of a block

(revive)


403-403: unnecessary leading newline

(whitespace)


404-404: File is not gofumpt-ed with -extra

(gofumpt)


[warning] 414-414: empty-lines: extra empty line at the start of a block

(revive)


414-414: unnecessary leading newline

(whitespace)


415-415: File is not gofumpt-ed with -extra

(gofumpt)

router.go Outdated Show resolved Hide resolved
router.go Outdated Show resolved Hide resolved
router.go Outdated Show resolved Hide resolved
@efectn efectn added this to the v3 milestone Dec 5, 2024
@efectn efectn linked an issue Dec 5, 2024 that may be closed by this pull request
3 tasks
router.go Outdated Show resolved Hide resolved
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

🧹 Outside diff range and nitpick comments (3)
router.go (3)

310-310: Add space after // in comment

Fix the comment formatting to follow Go conventions.

-			//Decrement global handler count
+			// Decrement global handler count
🧰 Tools
🪛 golangci-lint (1.62.2)

310-310: commentFormatting: put a space between // and comment text

(gocritic)


310-310: File is not gofumpt-ed with -extra

(gofumpt)


392-399: Simplify the return statement

The if-return block can be simplified to a direct return statement.

 func (app *App) routeExists(method string, pathRaw string) bool {
 	return slices.ContainsFunc(app.stack[app.methodInt(method)], func(r *Route) bool {
-		if r.path == pathRaw {
-			return true
-		}
-		return false
+		return r.path == pathRaw
 	})
 }
🧰 Tools
🪛 golangci-lint (1.62.2)

392-392: File is not gofumpt-ed with -extra

(gofumpt)


394-394: S1008: should use 'return r.path == pathRaw' instead of 'if r.path == pathRaw { return true }; return false'

(gosimple)


402-403: Remove or address TODO comments in documentation

The TODO comments in the documentation raise important questions about the intended usage of RemoveRoute. These should be either:

  1. Removed if they're implementation notes
  2. Converted to proper documentation if they're usage constraints
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f289503 and 68c18c1.

📒 Files selected for processing (1)
  • router.go (5 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
router.go

310-310: commentFormatting: put a space between // and comment text

(gocritic)


310-310: File is not gofumpt-ed with -extra

(gofumpt)


392-392: File is not gofumpt-ed with -extra

(gofumpt)


394-394: S1008: should use 'return r.path == pathRaw' instead of 'if r.path == pathRaw { return true }; return false'

(gosimple)


[warning] 410-410: confusing-naming: Method 'removeRoute' differs only by capitalization to method 'RemoveRoute' in the same source file

(revive)

🔇 Additional comments (4)
router.go (4)

11-11: LGTM: Good choice of using the standard slices package

The addition of the slices package is appropriate for the new route management functionality.


307-316: LGTM: Well-implemented duplicate route handling

The implementation is thread-safe and properly manages route and handler counts using atomic operations.

🧰 Tools
🪛 golangci-lint (1.62.2)

310-310: commentFormatting: put a space between // and comment text

(gocritic)


310-310: File is not gofumpt-ed with -extra

(gofumpt)


401-408: Rename internal method to avoid confusion

Having RemoveRoute and removeRoute methods that differ only by capitalization can lead to confusion.


410-431: Verify route removal edge cases

Let's verify the handling of several edge cases in the route removal implementation:

  1. Removal of routes with mounted apps
  2. Impact on middleware chains
  3. Memory management of removed routes
✅ Verification successful

Based on the shell script results, I can now generate the final response:

Route removal implementation is safe and well-handled

The codebase analysis shows that route removal is properly implemented with the following safety measures:

  • Mutex locking is used to prevent race conditions during route removal
  • Atomic operations handle global counters (handlers and routes)
  • Mounted apps are properly tracked via mountFields and hasMountedApps()
  • Memory management is handled through Go's garbage collection as removed routes are simply unlinked from the stack slice
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for mounted routes and middleware chains
ast-grep --pattern 'mount := true' -l go
rg -A 5 'func.*Mount'

# Check for potential memory leaks
rg -g '*.go' 'removeRoute|RemoveRoute' -A 5 -B 5

Length of output: 7958

🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 410-410: confusing-naming: Method 'removeRoute' differs only by capitalization to method 'RemoveRoute' in the same source file

(revive)

router.go Outdated Show resolved Hide resolved
router.go Outdated Show resolved Hide resolved
router.go Outdated Show resolved Hide resolved
router.go Show resolved Hide resolved
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: 3

🧹 Outside diff range and nitpick comments (1)
router_test.go (1)

457-514: Enhance middleware testing coverage

The test could be improved by:

  1. Verifying middleware execution order
  2. Testing multiple middleware functions
  3. Testing middleware state preservation
 func Test_App_Remove_Route_With_Middleware(t *testing.T) {
     t.Parallel()
     app := New()
 
+    executionOrder := []string{}
     middleware1 := func(c Ctx) error {
+        executionOrder = append(executionOrder, "middleware1")
         return c.Next()
     }
+    middleware2 := func(c Ctx) error {
+        executionOrder = append(executionOrder, "middleware2")
+        return c.Next()
+    }
 
-    app.Get("/test", func(c Ctx) error {
-        // ... existing code ...
-    }, middleware1)
+    app.Get("/test", func(c Ctx) error {
+        executionOrder = append(executionOrder, "handler")
+        return c.SendStatus(http.StatusOK)
+    }, middleware1, middleware2)
 
     // ... rest of the test ...
+    require.Equal(t, []string{"middleware1", "middleware2", "handler"}, executionOrder)
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 465-465: empty-lines: extra empty line at the start of a block

(revive)


465-465: unnecessary leading newline

(whitespace)


466-466: File is not gofumpt-ed with -extra

(gofumpt)


[warning] 476-476: empty-lines: extra empty line at the start of a block

(revive)


476-476: unnecessary leading newline

(whitespace)


477-477: File is not gofumpt-ed with -extra

(gofumpt)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 68c18c1 and 5c384f7.

📒 Files selected for processing (1)
  • router_test.go (2 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
router_test.go

[warning] 406-406: empty-lines: extra empty line at the start of a block

(revive)


406-406: unnecessary leading newline

(whitespace)


407-407: File is not gofumpt-ed with -extra

(gofumpt)


[warning] 417-417: empty-lines: extra empty line at the start of a block

(revive)


417-417: unnecessary leading newline

(whitespace)


418-418: File is not gofumpt-ed with -extra

(gofumpt)


[warning] 465-465: empty-lines: extra empty line at the start of a block

(revive)


465-465: unnecessary leading newline

(whitespace)


466-466: File is not gofumpt-ed with -extra

(gofumpt)


[warning] 476-476: empty-lines: extra empty line at the start of a block

(revive)


476-476: unnecessary leading newline

(whitespace)


477-477: File is not gofumpt-ed with -extra

(gofumpt)


517-517: parameter testing.TB should be the first or after context.Context

(thelper)


517-517: parameter testing.TB should have name tb

(thelper)


517-517: test helper function should start from tb.Helper()

(thelper)

router_test.go Outdated Show resolved Hide resolved
router_test.go Outdated Show resolved Hide resolved
router_test.go Outdated Show resolved Hide resolved
@gaby gaby changed the title 🔥feat: RemoveRoute 🔥feat: Add Support for Removing Routes Dec 6, 2024
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: 2

🧹 Outside diff range and nitpick comments (4)
router.go (2)

306-318: Clarify TODO comment and document handler count adjustment

The TODO comment about RemoveRoute needs clarification. Additionally, the handler count adjustment logic using bitwise complement (^) is not immediately obvious.

  1. Remove or clarify the TODO comment
  2. Add a comment explaining the handler count adjustment:
-            //Decrement global handler count
+            // Decrement global handler count using bitwise complement
+            // ^uint32(len(handlers)-1) is equivalent to -uint32(len(handlers)-1)
             atomic.AddUint32(&app.handlersCount, ^uint32(len(handlers)-1))
🧰 Tools
🪛 golangci-lint (1.62.2)

311-311: commentFormatting: put a space between // and comment text

(gocritic)


311-311: File is not gofumpt-ed with -extra

(gofumpt)


408-432: Improve error handling for invalid methods

The method silently skips invalid HTTP methods. Consider providing feedback to callers about invalid methods.

 func (app *App) deleteRoute(path string, methods []string) {
+    var invalidMethods []string
     for _, method := range methods {
         method = utils.ToUpper(method)
         m := app.methodInt(method)
         if m == -1 {
+            invalidMethods = append(invalidMethods, method)
             continue
         }
         index := slices.IndexFunc(app.stack[m], func(r *Route) bool {
             return r.Path == path
         })
         if index == -1 {
             continue
         }
         app.stack[m] = slices.Delete(app.stack[m], index, index+1)
     }
+    if len(invalidMethods) > 0 {
+        app.config.ErrorHandler(nil, fmt.Errorf("invalid HTTP methods: %v", invalidMethods))
+    }
     app.routesRefreshed = true
 }
router_test.go (2)

465-489: Enhance error messages in route handler verification

While the verification is thorough, the error message could be more descriptive.

 for _, route := range routes {
-    require.Equal(tb, 1, strings.Count(route.handlers, " "))
+    require.Equal(tb, 1, strings.Count(route.handlers, " "),
+        "route %s %s should have exactly one handler, got: %s",
+        route.method, route.path, route.handlers)
 }
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 470-470: empty-lines: extra empty line at the start of a block

(revive)


470-470: unnecessary leading newline

(whitespace)


471-471: File is not gofumpt-ed with -extra

(gofumpt)


386-410: Add test cases for edge scenarios

While the basic functionality is well-tested, consider adding test cases for:

  1. Removing non-existent routes
  2. Removing routes with invalid methods
  3. Concurrent route operations

Example test case:

func Test_App_Remove_NonExistent_Route(t *testing.T) {
    t.Parallel()
    app := New()
    
    app.RemoveRoute("/non-existent", "GET")
    require.Equal(t, uint32(0), app.handlersCount)
    require.Equal(t, uint32(0), app.routesCount)
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5c384f7 and 04764d8.

📒 Files selected for processing (2)
  • router.go (5 hunks)
  • router_test.go (3 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
router_test.go

[warning] 442-442: empty-lines: extra empty line at the end of a block

(revive)


452-452: File is not gofumpt-ed with -extra

(gofumpt)


453-453: unnecessary trailing newline

(whitespace)


[warning] 470-470: empty-lines: extra empty line at the start of a block

(revive)


470-470: unnecessary leading newline

(whitespace)


471-471: File is not gofumpt-ed with -extra

(gofumpt)

router.go

311-311: commentFormatting: put a space between // and comment text

(gocritic)


311-311: File is not gofumpt-ed with -extra

(gofumpt)


393-393: File is not gofumpt-ed with -extra

(gofumpt)

🔇 Additional comments (3)
router.go (1)

474-477: Well-documented performance considerations

The comment clearly explains the performance implications and design decisions regarding thread safety.

router_test.go (2)

455-463: Well-structured test helper function

The verifyRequest helper is well-implemented with proper use of testing.TB and t.Helper().


412-440: Well-implemented middleware test case

The test thoroughly verifies route removal behavior with middleware, including proper handler count verification.

router.go Outdated Show resolved Hide resolved
router.go Outdated Show resolved Hide resolved
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

🧹 Outside diff range and nitpick comments (6)
router.go (4)

392-396: Improve RemoveRoute documentation

The method documentation should:

  1. Explain the purpose of the middlewareCount parameter
  2. Document the behavior when methods array is empty
  3. Clarify when RebuildTree is required to be called
-// RemoveRoute is used to remove a route from the stack
-// You should call RebuildTree after using this to ensure consistency of the tree
+// RemoveRoute removes a route from the stack for the specified HTTP methods.
+// Parameters:
+//   - path: The route path to remove
+//   - middlewareCount: Number of middleware handlers (use 0 for routes without middleware)
+//   - methods: HTTP methods (GET, POST, etc.). If empty, the route is not removed
+//
+// Note: Call RebuildTree() after removing routes to ensure routing tree consistency

402-404: Clarify middleware count increment logic

The increment of middlewareCount when it's 0 is not clearly explained and could be confusing.

-	if middlewareCount == 0 {
-		middlewareCount++
-	}
+	// Ensure at least one handler is counted (the route handler itself)
+	if middlewareCount == 0 {
+		middlewareCount = 1 // Count the route handler
+	}

406-407: Simplify atomic decrement operation

The current atomic decrement using bitwise complement is hard to understand.

-	atomic.AddUint32(&app.handlersCount, ^uint32(middlewareCount-1))
+	atomic.AddUint32(&app.handlersCount, ^uint32(middlewareCount-1)) // Decrement by middlewareCount
🧰 Tools
🪛 golangci-lint (1.62.2)

406-406: commentFormatting: put a space between // and comment text

(gocritic)


406-406: File is not gofumpt-ed with -extra

(gofumpt)


416-418: Log invalid HTTP methods

The method silently skips invalid HTTP methods, which could hide configuration errors.

 		if m == -1 {
+			app.config.Logger.Printf("Invalid HTTP method %s when removing route %s", method, path)
 			continue // Skip invalid HTTP methods
 		}
router_test.go (2)

403-457: Consider using table-driven tests

The duplicate route registration tests have similar structure and could be refactored into a table-driven test to improve maintainability.

func Test_App_Route_Registration_Prevent_Duplicate(t *testing.T) {
    tests := []struct {
        name            string
        middleware     []func(Ctx) error
        expectedHandlers uint32
    }{
        {
            name:             "without middleware",
            middleware:       nil,
            expectedHandlers: 2,
        },
        {
            name:             "with middleware",
            middleware:       []func(Ctx) error{func(c Ctx) error { return c.Next() }},
            expectedHandlers: 3,
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            app := New()
            registerTreeManipulationRoutes(app, tt.middleware...)
            registerTreeManipulationRoutes(app)
            
            verifyRequest(t, app, "/test", http.StatusOK)
            require.Equal(t, tt.expectedHandlers, app.handlersCount)
        })
    }
}

Line range hint 459-471: Remove unnecessary empty lines

The function has unnecessary empty lines at the start and end.

 func registerTreeManipulationRoutes(app *App, middleware ...func(Ctx) error) {
-
 	app.Get("/test", func(c Ctx) error {
 		app.Get("/dynamically-defined", func(c Ctx) error {
 			return c.SendStatus(http.StatusOK)
 		})
 		app.RebuildTree()
 		return c.SendStatus(http.StatusOK)
 	}, middleware...)
-
 }
🧰 Tools
🪛 golangci-lint (1.62.2)

470-470: File is not gofumpt-ed with -extra

(gofumpt)


471-471: unnecessary trailing newline

(whitespace)


[warning] 488-488: empty-lines: extra empty line at the start of a block

(revive)


488-488: unnecessary leading newline

(whitespace)


489-489: File is not gofumpt-ed with -extra

(gofumpt)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 04764d8 and 590152e.

📒 Files selected for processing (2)
  • router.go (5 hunks)
  • router_test.go (3 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
router.go

386-386: File is not gofumpt-ed with -extra

(gofumpt)


406-406: commentFormatting: put a space between // and comment text

(gocritic)


406-406: File is not gofumpt-ed with -extra

(gofumpt)

router_test.go

[warning] 459-459: empty-lines: extra empty line at the start of a block

(revive)


[warning] 459-459: empty-lines: extra empty line at the end of a block

(revive)


459-459: unnecessary leading newline

(whitespace)


460-460: File is not gofumpt-ed with -extra

(gofumpt)


470-470: File is not gofumpt-ed with -extra

(gofumpt)


471-471: unnecessary trailing newline

(whitespace)


[warning] 488-488: empty-lines: extra empty line at the start of a block

(revive)


488-488: unnecessary leading newline

(whitespace)


489-489: File is not gofumpt-ed with -extra

(gofumpt)

🔇 Additional comments (4)
router.go (3)

386-390: Case sensitivity must be respected in route existence check

The route path comparison should follow the app's case sensitivity configuration.

🧰 Tools
🪛 golangci-lint (1.62.2)

386-386: File is not gofumpt-ed with -extra

(gofumpt)


306-310: LGTM: Duplicate route handling implementation

The implementation correctly prevents duplicate routes by removing existing routes before adding new ones.


Line range hint 475-479: LGTM: Clear documentation about performance implications

The documentation effectively communicates the performance impact and thread-safety considerations of rebuilding the tree.

router_test.go (1)

473-481: LGTM: Well-implemented test helpers

The helper functions are well-structured with proper test helper marking, clear error messages, and good parameter naming.

Also applies to: 484-507

router_test.go Outdated Show resolved Hide resolved
router.go Outdated Show resolved Hide resolved
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

🧹 Outside diff range and nitpick comments (6)
router_test.go (3)

380-380: Remove unnecessary empty lines in registerTreeManipulationRoutes

There are extra empty lines at the start and end of the registerTreeManipulationRoutes function, as flagged by the static analysis tool.

Apply this diff to remove the unnecessary empty lines:

-func registerTreeManipulationRoutes(app *App, middleware ...func(Ctx) error) {

+func registerTreeManipulationRoutes(app *App, middleware ...func(Ctx) error) {
    app.Get("/test", func(c Ctx) error {
        app.Get("/dynamically-defined", sendStatusOK)
        app.RebuildTree()
        return c.SendStatus(http.StatusOK)
    }, middleware...)
- 
}

Also applies to: 389-390

🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 380-380: empty-lines: extra empty line at the end of a block

(revive)


[warning] 380-380: empty-lines: extra empty line at the start of a block

(revive)


380-380: unnecessary leading newline

(whitespace)


407-408: Remove extra empty line in verifyRouteHandlerCounts

An unnecessary empty line is present at the start of the for loop in the verifyRouteHandlerCounts function.

Apply this diff to remove the empty line:

func verifyRouteHandlerCounts(tb testing.TB, app *App, expectedRoutesCount int) {
	tb.Helper()

-	//  this is taken from listen.go's printRoutesMessage app method
+	//  this is taken from listen.go's printRoutesMessage app method
	var routes []RouteMessage
	for _, routeStack := range app.stack {
		for _, route := range routeStack {
			// ...
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 407-407: empty-lines: extra empty line at the start of a block

(revive)


407-407: unnecessary leading newline

(whitespace)


408-408: File is not gofumpt-ed with -extra

(gofumpt)


483-483: Remove unnecessary empty line at the end of function

There's an extra empty line at the end of Test_App_Remove_Route_Parameterized, which is unnecessary.

Apply this diff to remove the empty line:

func Test_App_Remove_Route_Parameterized(t *testing.T) {
	t.Parallel()
	app := New()

	app.Get("/test/:id", sendStatusOK)
	app.RemoveRoute("/test/:id", http.MethodGet)

	verifyThereAreNoRoutes(t, app)

-}
+}
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 483-483: empty-lines: extra empty line at the end of a block

(revive)

router.go (3)

441-441: Add space after // in comment

The comment //Decrement global handler count lacks a space after //. Proper comment formatting improves code readability.

Apply this diff to fix the comment:

-//Decrement global handler count
+// Decrement global handler count
 atomic.AddUint32(&app.handlersCount, ^uint32(handlerCount-1)) //nolint:gosec // Not a concern
🧰 Tools
🪛 golangci-lint (1.62.2)

441-441: File is not gofumpt-ed with -extra

(gofumpt)


441-441: commentFormatting: put a space between // and comment text

(gocritic)


442-442: Correct the nolint directive formatting

The nolint directive should immediately follow // without a leading space to be properly recognized by linters.

Apply this diff to correct the formatting:

 atomic.AddUint32(&app.handlersCount, ^uint32(handlerCount-1)) // nolint:gosec // Not a concern
-atomic.AddUint32(&app.handlersCount, ^uint32(handlerCount-1)) // nolint:gosec // Not a concern
+atomic.AddUint32(&app.handlersCount, ^uint32(handlerCount-1)) //nolint:gosec // Not a concern
🧰 Tools
🪛 golangci-lint (1.62.2)

442-442: directive // nolint:gosec // Not a concern should be written without leading space as //nolint:gosec // Not a concern

(nolintlint)


386-400: Address gofumpt formatting issues in routeExists

The routeExists function is not formatted according to gofumpt -extra guidelines, as indicated by the static analysis tool. Consistent formatting improves code readability and maintainability.

Consider running gofumpt -extra on the file to apply standard formatting.

🧰 Tools
🪛 golangci-lint (1.62.2)

386-386: File is not gofumpt-ed with -extra

(gofumpt)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 590152e and d411cb1.

📒 Files selected for processing (2)
  • router.go (4 hunks)
  • router_test.go (2 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
router.go

386-386: File is not gofumpt-ed with -extra

(gofumpt)


441-441: File is not gofumpt-ed with -extra

(gofumpt)


441-441: commentFormatting: put a space between // and comment text

(gocritic)


442-442: directive // nolint:gosec // Not a concern should be written without leading space as //nolint:gosec // Not a concern

(nolintlint)

router_test.go

[warning] 380-380: empty-lines: extra empty line at the end of a block

(revive)


[warning] 380-380: empty-lines: extra empty line at the start of a block

(revive)


380-380: unnecessary leading newline

(whitespace)


381-381: File is not gofumpt-ed with -extra

(gofumpt)


389-389: File is not gofumpt-ed with -extra

(gofumpt)


390-390: unnecessary trailing newline

(whitespace)


[warning] 407-407: empty-lines: extra empty line at the start of a block

(revive)


407-407: unnecessary leading newline

(whitespace)


408-408: File is not gofumpt-ed with -extra

(gofumpt)


429-429: test helper function should start from tb.Helper()

(thelper)


[warning] 483-483: empty-lines: extra empty line at the end of a block

(revive)


491-491: File is not gofumpt-ed with -extra

(gofumpt)


492-492: unnecessary trailing newline

(whitespace)

🔇 Additional comments (2)
router_test.go (1)

446-594: Excellent test coverage for route removal scenarios

The added test functions comprehensively cover various scenarios for route removal, including:

  • Removing routes by name (Test_App_Remove_Route_By_Name)
  • Handling non-existing routes (Test_App_Remove_Route_By_Name_Non_Existing_Route, Test_App_Remove_Route_Non_Existing_Route)
  • Removing nested routes (Test_App_Remove_Route_Nested)
  • Removing parameterized routes (Test_App_Remove_Route_Parameterized)
  • Concurrent route removal and addition (Test_App_Remove_Route_Concurrent)

These tests enhance the robustness of the route management functionality.

🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 483-483: empty-lines: extra empty line at the end of a block

(revive)


491-491: File is not gofumpt-ed with -extra

(gofumpt)


492-492: unnecessary trailing newline

(whitespace)

router.go (1)

11-11: Ensure compatibility with Go version for slices package

The slices package from the standard library is imported, which requires Go 1.21 or higher. Ensure that the project's minimum Go version is updated accordingly to prevent build issues.

Run the following script to check the Go version specified in go.mod:

✅ Verification successful

Go version is compatible for using the slices package

The project uses Go 1.23 which is higher than the required version 1.21 for the slices package.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the Go version is 1.21 or higher

# Extract the Go version from go.mod
go_version=$(grep '^go ' go.mod | awk '{print $2}')

# Compare versions
required_version="1.21"

if [[ "$(printf '%s\n' "$required_version" "$go_version" | sort -V | head -n1)" == "$required_version" ]]; then
  echo "Go version ($go_version) is compatible."
else
  echo "Go version ($go_version) is not compatible. Please update to Go 1.21 or higher."
fi

Length of output: 233

router_test.go Show resolved Hide resolved
@gaby
Copy link
Member

gaby commented Dec 9, 2024

@ckoch786 The method has to be added to the docs under docs/. Also mention this new method in the whats_new.md file. You can add it after the RebuildTree section introduced in #3074

@gaby
Copy link
Member

gaby commented Dec 9, 2024

@ckoch786 In the root of the repo we have a Makefile to ease local development. See here: https://github.com/gofiber/fiber#-development

You can run the tests locally and also the lint tests.

@gaby gaby changed the title 🔥feat: Add Support for Removing Routes 🔥 feat: Add Support for Removing Routes Dec 9, 2024
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

🧹 Outside diff range and nitpick comments (4)
router.go (3)

306-312: Consider adding debug logging for route replacement

The duplicate route handling logic is correct, but it would be helpful to log when a route is being replaced to aid in debugging and monitoring.

 // Duplicate Route Handling
 if app.routeExists(method, pathRaw) {
+    if app.config.EnableDebugLog {
+        app.debug("[Router] Replacing existing route: %s %s", method, pathRaw)
+    }
     matchPathFunc := func(r *Route) bool { return r.Path == pathRaw }
     app.deleteRoute([]string{method}, matchPathFunc)
 }

402-408: Clarify documentation about duplicate prevention

The comment "This only needs to be called to remove a route, route registration prevents duplicate routes" might be confusing. Consider clarifying that while registration prevents duplicates by replacing existing routes, this method is needed for explicit route removal.

-// This only needs to be called to remove a route, route registration prevents duplicate routes.
+// This method is used to explicitly remove a route. Note that registering a route with an existing path
+// will automatically replace the old route, but this method allows removing routes without replacement.

418-450: Consider documenting atomic operations

The atomic operations for decrementing counters use bitwise complement, which might not be immediately obvious to all developers.

 // Decrement global handler count
-atomic.AddUint32(&app.handlersCount, ^uint32(len(route.Handlers)-1)) // nolint:gosec // Not a concern
+// Use bitwise complement (^) with AddUint32 to perform atomic decrement
+atomic.AddUint32(&app.handlersCount, ^uint32(len(route.Handlers)-1))
 // Decrement global route position
-atomic.AddUint32(&app.routesCount, ^uint32(0))
+// Decrement route count by 1 using bitwise complement of 0
+atomic.AddUint32(&app.routesCount, ^uint32(0))
🧰 Tools
🪛 golangci-lint (1.62.2)

441-441: directive // nolint:gosec // Not a concern should be written without leading space as //nolint:gosec // Not a concern

(nolintlint)

router_test.go (1)

520-597: Consider adding more concurrent test scenarios

While the concurrent test is good, consider adding tests for:

  1. Concurrent removal of different routes
  2. Concurrent operations on routes with middleware
  3. Concurrent operations with route groups

Example additional test:

func Test_App_Remove_Route_Concurrent_Different_Routes(t *testing.T) {
    t.Parallel()
    app := New()
    
    // Add test routes
    app.Get("/test1", sendStatusOK)
    app.Get("/test2", sendStatusOK)
    
    // Concurrently remove different routes
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        app.RemoveRoute("/test1", http.MethodGet)
    }()
    go func() {
        defer wg.Done()
        app.RemoveRoute("/test2", http.MethodGet)
    }()
    wg.Wait()
    
    app.RebuildTree()
    verifyThereAreNoRoutes(t, app)
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d411cb1 and 3311017.

📒 Files selected for processing (2)
  • router.go (4 hunks)
  • router_test.go (2 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
router.go

386-386: File is not gofumpt-ed with -extra

(gofumpt)


441-441: directive // nolint:gosec // Not a concern should be written without leading space as //nolint:gosec // Not a concern

(nolintlint)

router_test.go

[warning] 380-380: empty-lines: extra empty line at the start of a block

(revive)


[warning] 380-380: empty-lines: extra empty line at the end of a block

(revive)


380-380: unnecessary leading newline

(whitespace)


381-381: File is not gofumpt-ed with -extra

(gofumpt)


389-389: File is not gofumpt-ed with -extra

(gofumpt)


390-390: unnecessary trailing newline

(whitespace)


[warning] 407-407: empty-lines: extra empty line at the start of a block

(revive)


407-407: unnecessary leading newline

(whitespace)


408-408: File is not gofumpt-ed with -extra

(gofumpt)


[warning] 486-486: empty-lines: extra empty line at the end of a block

(revive)


495-495: File is not gofumpt-ed with -extra

(gofumpt)


496-496: unnecessary trailing newline

(whitespace)

🔇 Additional comments (4)
router.go (2)

386-400: LGTM! Clean implementation of route existence check

The implementation correctly handles case sensitivity and follows the codebase's patterns.

🧰 Tools
🪛 golangci-lint (1.62.2)

386-386: File is not gofumpt-ed with -extra

(gofumpt)


410-416: LGTM! Consistent implementation for name-based route removal

The implementation follows the same pattern as RemoveRoute and reuses the deleteRoute logic effectively.

router_test.go (2)

376-435: LGTM! Well-structured test helpers

The helper functions are well-designed, properly use tb.Helper(), and provide good abstractions for common test operations.

🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 380-380: empty-lines: extra empty line at the start of a block

(revive)


[warning] 380-380: empty-lines: extra empty line at the end of a block

(revive)


380-380: unnecessary leading newline

(whitespace)


381-381: File is not gofumpt-ed with -extra

(gofumpt)


389-389: File is not gofumpt-ed with -extra

(gofumpt)


390-390: unnecessary trailing newline

(whitespace)


[warning] 407-407: empty-lines: extra empty line at the start of a block

(revive)


407-407: unnecessary leading newline

(whitespace)


408-408: File is not gofumpt-ed with -extra

(gofumpt)


437-518: LGTM! Comprehensive test coverage for route removal

The tests cover all important scenarios including basic removal, removal by name, nested routes, parameterized routes, and edge cases.

🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 486-486: empty-lines: extra empty line at the end of a block

(revive)


495-495: File is not gofumpt-ed with -extra

(gofumpt)


496-496: unnecessary trailing newline

(whitespace)

ckoch786 added a commit to ckoch786/fiber that referenced this pull request Dec 14, 2024
* Add new methods named RemoveRoute and RemoveRouteByName.

* Update register method to prevent duplicate routes.

* Clean up tests

* Update docs

* Add Dockerfile
@ckoch786 ckoch786 force-pushed the feature/3098-allow-removing-registered-route branch from 7b0a410 to 6909630 Compare December 14, 2024 20:46
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: 4

🧹 Outside diff range and nitpick comments (4)
Dockerfile (1)

1-19: Add final stage for smaller runtime image.

While the builder stage is well-configured, consider adding a final stage for a smaller runtime image.

Add this after the current content:

+# Create small runtime image
+FROM scratch
+
+COPY --from=builder /app/server /server
+
+EXPOSE 3000
+ENTRYPOINT ["/server"]
router_test.go (2)

380-390: Fix formatting issues.

The function is well-structured but has unnecessary empty lines.

Apply this diff to fix formatting:

 func registerTreeManipulationRoutes(app *App, middleware ...func(Ctx) error) {
-
     app.Get("/test", func(c Ctx) error {
         app.Get("/dynamically-defined", sendStatusOK)
-
         app.RebuildTree()
-
         return c.SendStatus(http.StatusOK)
     }, middleware...)
-
 }
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 380-380: empty-lines: extra empty line at the start of a block

(revive)


[warning] 380-380: empty-lines: extra empty line at the end of a block

(revive)


380-380: unnecessary leading newline

(whitespace)


381-381: File is not gofumpt-ed with -extra

(gofumpt)


389-389: File is not gofumpt-ed with -extra

(gofumpt)


390-390: unnecessary trailing newline

(whitespace)


402-427: Consider simplifying route message collection.

While the function works correctly, it could be simplified for better maintainability.

Consider this refactoring:

 func verifyRouteHandlerCounts(tb testing.TB, app *App, expectedRoutesCount int) {
     tb.Helper()
 
-    var routes []RouteMessage
+    routes := make(map[string]RouteMessage)
     for _, routeStack := range app.stack {
         for _, route := range routeStack {
             routeMsg := RouteMessage{
                 name:   route.Name,
                 method: route.Method,
                 path:   route.Path,
             }
 
             for _, handler := range route.Handlers {
                 routeMsg.handlers += runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name() + " "
             }
 
-            routes = append(routes, routeMsg)
+            key := route.Method + route.Path
+            routes[key] = routeMsg
         }
     }
 
-    for _, route := range routes {
+    for _, route := range routes {
         require.Equal(tb, expectedRoutesCount, strings.Count(route.handlers, " "))
     }
 }
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 407-407: empty-lines: extra empty line at the start of a block

(revive)


407-407: unnecessary leading newline

(whitespace)


408-408: File is not gofumpt-ed with -extra

(gofumpt)

docs/whats_new.md (1)

944-951: Add usage examples for route removal features.

The documentation would benefit from examples showing:

  1. Basic route removal
  2. Route removal by name
  3. Rebuilding the tree after removal

Consider adding examples like:

// Remove route by path
app.RemoveRoute("/api/users", MethodGet)
app.RebuildTree()

// Remove route by name
app.Get("/api/users", handler).Name("users")
app.RemoveRouteByName("users", MethodGet)
app.RebuildTree()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3311017 and 6909630.

📒 Files selected for processing (5)
  • Dockerfile (1 hunks)
  • docs/api/app.md (1 hunks)
  • docs/whats_new.md (1 hunks)
  • router.go (4 hunks)
  • router_test.go (2 hunks)
🧰 Additional context used
🪛 Markdownlint (0.37.0)
docs/api/app.md

765-765: Expected: 1; Actual: 2
Multiple consecutive blank lines

(MD012, no-multiple-blanks)


775-775: null
Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🪛 golangci-lint (1.62.2)
router.go

386-386: File is not gofumpt-ed with -extra

(gofumpt)


441-441: directive // nolint:gosec // Not a concern should be written without leading space as //nolint:gosec // Not a concern

(nolintlint)

router_test.go

[warning] 380-380: empty-lines: extra empty line at the start of a block

(revive)


[warning] 380-380: empty-lines: extra empty line at the end of a block

(revive)


380-380: unnecessary leading newline

(whitespace)


381-381: File is not gofumpt-ed with -extra

(gofumpt)


389-389: File is not gofumpt-ed with -extra

(gofumpt)


390-390: unnecessary trailing newline

(whitespace)


[warning] 407-407: empty-lines: extra empty line at the start of a block

(revive)


407-407: unnecessary leading newline

(whitespace)


408-408: File is not gofumpt-ed with -extra

(gofumpt)


460-460: commentedOutCode: may want to remove commented-out code

(gocritic)


460-460: File is not gofumpt-ed with -extra

(gofumpt)


460-460: commentFormatting: put a space between // and comment text

(gocritic)


471-471: File is not gofumpt-ed with -extra

(gofumpt)


471-471: commentedOutCode: may want to remove commented-out code

(gocritic)


471-471: commentFormatting: put a space between // and comment text

(gocritic)


479-479: SA4006: this value of err is never used

(staticcheck)


479-479: ineffectual assignment to err

(ineffassign)


[warning] 525-525: empty-lines: extra empty line at the end of a block

(revive)


534-534: File is not gofumpt-ed with -extra

(gofumpt)


535-535: unnecessary trailing newline

(whitespace)

🔇 Additional comments (8)
router.go (4)

386-400: LGTM! Well-implemented route existence check.

The implementation correctly handles case sensitivity and efficiently searches for existing routes.

🧰 Tools
🪛 golangci-lint (1.62.2)

386-386: File is not gofumpt-ed with -extra

(gofumpt)


402-416: LGTM! Clean and well-documented route removal methods.

Both methods are well-designed with clear documentation about the RebuildTree requirement.


492-492: LGTM! Excellent documentation update.

The documentation clearly explains the performance implications with benchmark results.


418-450: Verify concurrent route removal safety.

While the implementation handles thread safety for route removal, we should verify the behavior when removing routes during active requests.

✅ Verification successful

Let me gather more information about the route handling and tree structure to better understand the concurrent safety.


Let me check one more aspect about the request handling to see if there's any read locking during actual request processing.


Based on the gathered information, I can now provide the final response:

Route removal implementation is concurrently safe

The implementation correctly handles concurrent safety through multiple mechanisms:

  • Mutex protection during route modifications (add/delete/rebuild)
  • Atomic counter updates for handlers and routes
  • Tree structure is rebuilt atomically with mutex protection
  • Request handling uses a separate tree stack that's only updated after modifications are complete

The routesRefreshed flag ensures the router tree is rebuilt before processing new requests, preventing any race conditions during active requests.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for request handling code that might be affected by concurrent route removal
rg -A 5 "func.*next.*" router.go

Length of output: 472


Script:

#!/bin/bash
# Check how treeStack is populated and if there's any synchronization
rg -A 10 "treeStack.*=" router.go

# Look for tree building/refreshing logic
ast-grep --pattern 'func (app *App) buildTree() {
  $$$
}'

# Check if there's any read locking during request handling
rg -B 2 -A 5 "mutex\.(RLock|Lock)" router.go

Length of output: 1112


Script:

#!/bin/bash
# Check the main request handling logic
rg -B 5 -A 10 "func.*Handler|func.*handle" router.go

# Look for any tree access synchronization in the context
ast-grep --pattern 'func (c *$_) $_($$$) {
  $$$
  app.treeStack
  $$$
}'

Length of output: 992

🧰 Tools
🪛 golangci-lint (1.62.2)

441-441: directive // nolint:gosec // Not a concern should be written without leading space as //nolint:gosec // Not a concern

(nolintlint)

router_test.go (4)

376-378: LGTM! Well-structured helper function.

The function is concise, follows single responsibility principle, and is reused effectively across test cases.


392-400: LGTM! Well-structured test helper.

The function follows testing best practices:

  • Includes tb.Helper() call
  • Clear error messages
  • Proper status code verification

429-435: Add tb.Helper() call to test helper function.

The function is missing a tb.Helper() call which is required for proper test failure reporting.


487-607: LGTM! Comprehensive test coverage.

The test suite effectively covers various route removal scenarios:

  • Route removal by name
  • Non-existing routes
  • Nested routes
  • Parameterized routes
  • Concurrent operations
  • Duplicate route prevention

The concurrent test is particularly valuable for verifying thread safety.

🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 525-525: empty-lines: extra empty line at the end of a block

(revive)


534-534: File is not gofumpt-ed with -extra

(gofumpt)


535-535: unnecessary trailing newline

(whitespace)

docs/api/app.md Outdated
Comment on lines 791 to 801
app.Get("/api/feature-a", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-a")
})

app.RebuildTree()
return c.SendStatus(http.StatusOK)
})
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 14, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix inconsistencies in example code.

The example code has several issues:

  1. Uses Ctx instead of fiber.Ctx
  2. Uses undefined MethodGet constant
  3. Uses undefined http.StatusOK constant

Apply this diff to fix the issues:

-    app.Get("/api/feature-a", func(c Ctx) error {
-           app.RemoveRoute("/api/feature", MethodGet)
+    app.Get("/api/feature-a", func(c fiber.Ctx) error {
+           app.RemoveRoute("/api/feature", fiber.MethodGet)
            app.RebuildTree()
            // Redefine route
-           app.Get("/api/feature", func(c Ctx) error {
+           app.Get("/api/feature", func(c fiber.Ctx) error {
                    return c.SendString("Testing feature-a")
            })

            app.RebuildTree()
-           return c.SendStatus(http.StatusOK)
+           return c.SendStatus(fiber.StatusOK)
     })

Also applies to: 802-812

Copy link
Member

Choose a reason for hiding this comment

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

^Apply the above

Copy link
Contributor

Choose a reason for hiding this comment

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

@gaby, please apply the suggested diff above to fix the inconsistencies in the example code.

Copy link
Author

@ckoch786 ckoch786 Dec 15, 2024

Choose a reason for hiding this comment

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

@gaby @coderabbitai the router_test.go file is already part of the fiber package so the Ctx is already the fiber Ctx. Also MethodGet is also in the fiber package and that is what is being used. In addition MethodGet and StatusOK are both defined in the fiber package source file constants.go. So I did update my tests to use the fiber constant StatusOK instead of the http.StatusOK constant.

When I made the update it importing github.com/gofiber/fiber after doing so and running make tidy and then make test I get the following errors:

github.com/gofiber/fiber/v3 [github.com/gofiber/fiber/v3.test]

./router_test.go:378:9: c.SendStatus(http.StatusOK) (no value) used as value
./router_test.go:382:19: cannot use func(c *fiber.Ctx) error {…} (value of type func(c *"github.com/gofiber/fiber".Ctx) error) as Handler value in argument to app.Get
./router_test.go:383:35: cannot use sendStatusOK (value of type func(c *"github.com/gofiber/fiber".Ctx) error) as Handler value in argument to app.Get
./router_test.go:387:10: c.SendStatus(http.StatusOK) (no value) used as value
./router_test.go:450:28: cannot use func(c *fiber.Ctx) error {…} (value of type func(c *"github.com/gofiber/fiber".Ctx) error) as Handler value in argument to app.Get
./router_test.go:454:27: cannot use func(c *fiber.Ctx) error {…} (value of type func(c *"github.com/gofiber/fiber".Ctx) error) as Handler value in argument to app.Get
./router_test.go:455:11: c.SendString("Testing feature-a") (no value) used as value
./router_test.go:459:10: c.SendStatus(fiber.StatusOK) (no value) used as value
./router_test.go:461:28: cannot use func(c *fiber.Ctx) error {…} (value of type func(c *"github.com/gofiber/fiber".Ctx) error) as Handler value in argument to app.Get
./router_test.go:465:27: cannot use func(c *fiber.Ctx) error {…} (value of type func(c *"github.com/gofiber/fiber".Ctx) error) as Handler value in argument to app.Get
./router_test.go:465:27: too many errors
FAIL github.com/gofiber/fiber/v3 [build failed]

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

docs/api/app.md Outdated
Comment on lines 770 to 772
```go title="Signature"
func (app *App) RemoveRoute()
```
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 14, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update method signature to include parameters.

The method signature should be:

-func (app *App) RemoveRoute()
+func (app *App) RemoveRoute(path string, methods ...string)

Copy link
Member

Choose a reason for hiding this comment

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

Apply this change

Copy link
Member

Choose a reason for hiding this comment

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

Also remove the extra space at the beginning of the 2nd sentence.

Copy link
Contributor

Choose a reason for hiding this comment

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

@gaby, understood. The extra space at the beginning of the second sentence should be removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

@gaby, understood. Here are the updated method signatures:

func (app *App) RemoveRoute(path string, methods ...string)
func (app *App) RemoveRouteByName(name string, methods ...string)

I've also removed the extra space at the beginning of the second sentence:

"You must call the RebuildTree() method after the remove to ensure the route is removed."

docs/api/app.md Outdated Show resolved Hide resolved
router_test.go Outdated
Comment on lines 448 to 477
func Test_App_Romove_Route_A_B_Feature_Testing(t *testing.T) {
t.Parallel()
app := New()

app.Get("/api/feature-a", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-a")
})

//app.RebuildTree()
return c.SendStatus(http.StatusOK)
})
app.Get("/api/feature-b", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-b")
})

//app.RebuildTree()
return c.SendStatus(http.StatusOK)
})

resp, err := app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/feature-a", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusNotFound, resp.StatusCode, "Status code")
require.Equal(t, http.StatusOK, resp, "Status Code")

resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
resp, err = app.Test(httptest.NewRequest(MethodGet, "/api/feature", nil))
require.Equal(t, "Testing feature-a", resp, "Response Message")

resp, err = app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
resp, err = app.Test(httptest.NewRequest(MethodGet, "api/feature-b", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
require.Equal(t, "Testing feature-b", resp, "Response Message")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix multiple issues in test function.

  1. Function name has a typo ("Romove" should be "Remove")
  2. Contains commented out code
  3. Ineffectual assignment to err
  4. Test logic could be simplified using helper functions

Apply these changes:

-func Test_App_Romove_Route_A_B_Feature_Testing(t *testing.T) {
+func Test_App_Remove_Route_A_B_Feature_Testing(t *testing.T) {
     t.Parallel()
     app := New()
 
     app.Get("/api/feature-a", func(c Ctx) error {
         app.RemoveRoute("/api/feature", MethodGet)
         app.RebuildTree()
-        // Redefine route
         app.Get("/api/feature", func(c Ctx) error {
             return c.SendString("Testing feature-a")
         })
-        //app.RebuildTree()
         return c.SendStatus(http.StatusOK)
     })
     app.Get("/api/feature-b", func(c Ctx) error {
         app.RemoveRoute("/api/feature", MethodGet)
         app.RebuildTree()
-        // Redefine route
         app.Get("/api/feature", func(c Ctx) error {
             return c.SendString("Testing feature-b")
         })
-        //app.RebuildTree()
         return c.SendStatus(http.StatusOK)
     })
 
-    resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/feature-a", nil))
-    require.NoError(t, err, "app.Test(req)")
-    require.Equal(t, http.StatusOK, resp, "Status Code")
+    verifyRequest(t, app, "/api/feature-a", http.StatusOK)
 
-    resp, err = app.Test(httptest.NewRequest(MethodGet, "/api/feature", nil))
-    require.Equal(t, "Testing feature-a", resp, "Response Message")
+    resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/feature", nil))
+    require.NoError(t, err)
+    body, err := io.ReadAll(resp.Body)
+    require.NoError(t, err)
+    require.Equal(t, "Testing feature-a", string(body), "Response Message")
 
-    resp, err = app.Test(httptest.NewRequest(MethodGet, "api/feature-b", nil))
-    require.NoError(t, err, "app.Test(req)")
-    require.Equal(t, "Testing feature-b", resp, "Response Message")
+    verifyRequest(t, app, "/api/feature-b", http.StatusOK)
+    
+    resp, err = app.Test(httptest.NewRequest(MethodGet, "/api/feature", nil))
+    require.NoError(t, err)
+    body, err = io.ReadAll(resp.Body)
+    require.NoError(t, err)
+    require.Equal(t, "Testing feature-b", string(body), "Response Message")
 }
📝 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
func Test_App_Romove_Route_A_B_Feature_Testing(t *testing.T) {
t.Parallel()
app := New()
app.Get("/api/feature-a", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-a")
})
//app.RebuildTree()
return c.SendStatus(http.StatusOK)
})
app.Get("/api/feature-b", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-b")
})
//app.RebuildTree()
return c.SendStatus(http.StatusOK)
})
resp, err := app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/feature-a", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusNotFound, resp.StatusCode, "Status code")
require.Equal(t, http.StatusOK, resp, "Status Code")
resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
resp, err = app.Test(httptest.NewRequest(MethodGet, "/api/feature", nil))
require.Equal(t, "Testing feature-a", resp, "Response Message")
resp, err = app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
resp, err = app.Test(httptest.NewRequest(MethodGet, "api/feature-b", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
require.Equal(t, "Testing feature-b", resp, "Response Message")
}
func Test_App_Remove_Route_A_B_Feature_Testing(t *testing.T) {
t.Parallel()
app := New()
app.Get("/api/feature-a", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-a")
})
return c.SendStatus(http.StatusOK)
})
app.Get("/api/feature-b", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-b")
})
return c.SendStatus(http.StatusOK)
})
verifyRequest(t, app, "/api/feature-a", http.StatusOK)
resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/feature", nil))
require.NoError(t, err)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, "Testing feature-a", string(body), "Response Message")
verifyRequest(t, app, "/api/feature-b", http.StatusOK)
resp, err = app.Test(httptest.NewRequest(MethodGet, "/api/feature", nil))
require.NoError(t, err)
body, err = io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, "Testing feature-b", string(body), "Response Message")
}
🧰 Tools
🪛 golangci-lint (1.62.2)

460-460: commentedOutCode: may want to remove commented-out code

(gocritic)


460-460: File is not gofumpt-ed with -extra

(gofumpt)


460-460: commentFormatting: put a space between // and comment text

(gocritic)


471-471: File is not gofumpt-ed with -extra

(gofumpt)


471-471: commentedOutCode: may want to remove commented-out code

(gocritic)


471-471: commentFormatting: put a space between // and comment text

(gocritic)


479-479: SA4006: this value of err is never used

(staticcheck)


479-479: ineffectual assignment to err

(ineffassign)

@ckoch786
Copy link
Author

@gaby I cleaned up my branch and just pushed the docs changes.

ckoch786 added a commit to ckoch786/fiber that referenced this pull request Dec 14, 2024
* Add new methods named RemoveRoute and RemoveRouteByName.

* Update register method to prevent duplicate routes.

* Clean up tests

* Update docs

* Add Dockerfile
@ckoch786 ckoch786 force-pushed the feature/3098-allow-removing-registered-route branch from 82142d1 to 8df76b9 Compare December 14, 2024 20:55
Dockerfile Outdated
@@ -0,0 +1,25 @@
# Usage:
Copy link
Member

Choose a reason for hiding this comment

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

What is this doing here?

Copy link
Author

Choose a reason for hiding this comment

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

I am stuck in Windows currently so I created the Dockerfile so that I could get all the dependencies for running make to install properly. I have more updates that make it more useful that I will be pushing soon that utilizes the build caching and I updated the comments to include how to utilize volumes and bind mounts with run to keep preserve all the downloaded packages between runs and to allow the linter to update the local code that is outside the docker container.

However if you do not want me to add it I can just remove it.

What is nice about using docker for running the make actions is that if it is used it gives you an isolated container from the rest of your machine so that you can get consistent results. Also using docker makes it easier for others to work with the Makefile as all they need to do is install Docker and the rest will just work.

Copy link
Member

Choose a reason for hiding this comment

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

You cna remove it, this could be added to the docs though. Or one of the other fiber repos

Copy link
Author

Choose a reason for hiding this comment

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

Also if you choose to keep it I can create helper scripts for running docker build and docker run to make it easier to use .

docs/api/app.md Outdated
Comment on lines 770 to 772
```go title="Signature"
func (app *App) RemoveRoute()
```
Copy link
Member

Choose a reason for hiding this comment

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

Apply this change

docs/api/app.md Outdated
Comment on lines 770 to 772
```go title="Signature"
func (app *App) RemoveRoute()
```
Copy link
Member

Choose a reason for hiding this comment

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

Also remove the extra space at the beginning of the 2nd sentence.

docs/api/app.md Outdated Show resolved Hide resolved
docs/api/app.md Outdated
Comment on lines 791 to 801
app.Get("/api/feature-a", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-a")
})

app.RebuildTree()
return c.SendStatus(http.StatusOK)
})
Copy link
Member

Choose a reason for hiding this comment

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

^Apply the above

router_test.go Outdated
@@ -369,31 +373,267 @@ func Test_Router_NotFound_HTML_Inject(t *testing.T) {
require.Equal(t, "Cannot DELETE /does/not/exist&lt;script&gt;alert(&#39;foo&#39;);&lt;/script&gt;", string(c.Response.Body()))
}

func sendStatusOK(c Ctx) error {
Copy link
Member

Choose a reason for hiding this comment

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

Remove this helper, do this call in the test

Copy link
Member

@gaby gaby left a comment

Choose a reason for hiding this comment

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

Some comments

@gaby
Copy link
Member

gaby commented Dec 15, 2024

@ckoch786 Test and lint workflows are failing. You can test them locally using the Makefile.

ckoch786 added a commit to ckoch786/fiber that referenced this pull request Dec 15, 2024
* Add new methods named RemoveRoute and RemoveRouteByName.

* Update register method to prevent duplicate routes.

* Clean up tests

* Update docs

* Add Dockerfile
@ckoch786 ckoch786 force-pushed the feature/3098-allow-removing-registered-route branch from 6b4e2d5 to cf59d6d Compare December 15, 2024 19:49
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: 2

🧹 Nitpick comments (6)
Dockerfile (2)

1-20: Consider moving Dockerfile to documentation.

Based on the previous discussion, this Dockerfile was created for local development on Windows. Since it's a development tool, it would be more appropriate to move it to the documentation or another Fiber repository.


45-46: Fix formatting in RUN command.

Add whitespace after the RUN command for better readability.

-RUN --mount=type=cache,target=/go/pkg/mod/cache
-    go mod download -x
+RUN --mount=type=cache,target=/go/pkg/mod/cache \
+    go mod download -x
🧰 Tools
🪛 Hadolint (2.12.0)

[error] 45-45: missing whitespace

(DL1000)

docs/api/app.md (1)

768-768: Fix spacing in documentation.

Remove the extra space at the beginning of the sentence.

-This method removes a route by path.  You must call the `RebuildTree()` method after the remove in to ensure the route is removed.
+This method removes a route by path. You must call the `RebuildTree()` method after the remove in to ensure the route is removed.
router_test.go (3)

376-390: Fix formatting issues.

The code has unnecessary empty lines and formatting issues.

Apply this diff to fix the formatting:

 func sendStatusOK(c Ctx) error {
     return c.SendStatus(http.StatusOK)
 }
-
 func registerTreeManipulationRoutes(app *App, middleware ...func(Ctx) error) {
-
     app.Get("/test", func(c Ctx) error {
         app.Get("/dynamically-defined", sendStatusOK)
-
         app.RebuildTree()
-
         return c.SendStatus(http.StatusOK)
     }, middleware...)
-
 }
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 380-380: empty-lines: extra empty line at the end of a block

(revive)


[warning] 380-380: empty-lines: extra empty line at the start of a block

(revive)


380-380: unnecessary leading newline

(whitespace)


381-381: File is not gofumpt-ed with -extra

(gofumpt)


389-389: File is not gofumpt-ed with -extra

(gofumpt)


390-390: unnecessary trailing newline

(whitespace)


525-535: Fix formatting issues.

The test has unnecessary empty lines.

Apply this diff to fix the formatting:

 func Test_App_Remove_Route_Parameterized(t *testing.T) {
     t.Parallel()
     app := New()

     app.Get("/test/:id", sendStatusOK)
     verifyRequest(t, app, "/test/:id", http.StatusOK)
     app.RemoveRoute("/test/:id", MethodGet)

     verifyThereAreNoRoutes(t, app)
-
 }
🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 525-525: empty-lines: extra empty line at the end of a block

(revive)


534-534: File is not gofumpt-ed with -extra

(gofumpt)


535-535: unnecessary trailing newline

(whitespace)


559-581: Consider enhancing concurrent test verification.

While the test verifies basic concurrent operations, consider adding:

  1. Verification of intermediate states
  2. Checking for race conditions using the race detector
  3. Verifying handler counts remain consistent

Apply this diff to enhance the test:

 func Test_App_Remove_Route_Concurrent(t *testing.T) {
     t.Parallel()
     app := New()

     // Add test route
     app.Get("/test", sendStatusOK)
+    initialHandlersCount := app.handlersCount

     // Concurrently remove and add routes
     var wg sync.WaitGroup
     for i := 0; i < 10; i++ {
         wg.Add(1)
         go func() {
             defer wg.Done()
             app.RemoveRoute("/test", MethodGet)
             app.Get("/test", sendStatusOK)
         }()
     }
     wg.Wait()

     // Verify final state
     app.RebuildTree()
     verifyRequest(t, app, "/test", http.StatusOK)
+    require.Equal(t, initialHandlersCount, app.handlersCount, "Handlers count should remain consistent")
+    verifyRouteHandlerCounts(t, app, 1)
 }

To run with race detector:

go test -race ./... -run=Test_App_Remove_Route_Concurrent
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6909630 and cf59d6d.

📒 Files selected for processing (5)
  • Dockerfile (1 hunks)
  • docs/api/app.md (1 hunks)
  • docs/whats_new.md (1 hunks)
  • router.go (4 hunks)
  • router_test.go (2 hunks)
🧰 Additional context used
🪛 Markdownlint (0.37.0)
docs/api/app.md

765-765: Expected: 1; Actual: 2
Multiple consecutive blank lines

(MD012, no-multiple-blanks)


775-775: null
Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🪛 golangci-lint (1.62.2)
router.go

386-386: File is not gofumpt-ed with -extra

(gofumpt)


441-441: directive // nolint:gosec // Not a concern should be written without leading space as //nolint:gosec // Not a concern

(nolintlint)

router_test.go

[warning] 380-380: empty-lines: extra empty line at the end of a block

(revive)


[warning] 380-380: empty-lines: extra empty line at the start of a block

(revive)


380-380: unnecessary leading newline

(whitespace)


381-381: File is not gofumpt-ed with -extra

(gofumpt)


389-389: File is not gofumpt-ed with -extra

(gofumpt)


390-390: unnecessary trailing newline

(whitespace)


[warning] 407-407: empty-lines: extra empty line at the start of a block

(revive)


407-407: unnecessary leading newline

(whitespace)


408-408: File is not gofumpt-ed with -extra

(gofumpt)


479-479: SA4006: this value of err is never used

(staticcheck)


479-479: ineffectual assignment to err

(ineffassign)


[warning] 525-525: empty-lines: extra empty line at the end of a block

(revive)


534-534: File is not gofumpt-ed with -extra

(gofumpt)


535-535: unnecessary trailing newline

(whitespace)

🪛 Hadolint (2.12.0)
Dockerfile

[error] 45-45: missing whitespace

(DL1000)

🔇 Additional comments (11)
router.go (4)

386-400: LGTM: Case-sensitive route existence check.

The implementation correctly handles case sensitivity based on app configuration.

🧰 Tools
🪛 golangci-lint (1.62.2)

386-386: File is not gofumpt-ed with -extra

(gofumpt)


402-408: LGTM: Clean implementation of RemoveRoute.

The method is well-documented and follows the single responsibility principle.


410-416: LGTM: Clean implementation of RemoveRouteByName.

The method is well-documented and follows the same pattern as RemoveRoute.


418-450: Consider adding error handling for concurrent modifications.

The deleteRoute method handles concurrent access with mutex, but consider adding error handling for cases where the route is modified between finding and deleting it.

Consider adding error handling:

 func (app *App) deleteRoute(methods []string, matchFunc func(r *Route) bool) {
     app.mutex.Lock()
     defer app.mutex.Unlock()
+    var deletedCount int
 
     for _, method := range methods {
         method = utils.ToUpper(method)
         m := app.methodInt(method)
         if m == -1 {
             continue
         }
         index := slices.IndexFunc(app.stack[m], matchFunc)
         if index == -1 {
             continue
         }
         route := app.stack[m][index]
         atomic.AddUint32(&app.handlersCount, ^uint32(len(route.Handlers)-1))
         atomic.AddUint32(&app.routesCount, ^uint32(0))
         app.stack[m] = slices.Delete(app.stack[m], index, index+1)
+        deletedCount++
     }
+    if deletedCount == 0 {
+        return fmt.Errorf("no routes were deleted")
+    }
     app.routesRefreshed = true
+    return nil
 }
🧰 Tools
🪛 golangci-lint (1.62.2)

441-441: directive // nolint:gosec // Not a concern should be written without leading space as //nolint:gosec // Not a concern

(nolintlint)

docs/api/app.md (2)

770-777: ⚠️ Potential issue

Update method signatures in documentation.

The method signatures should include their parameters.

-func (app *App) RemoveRoute()
+func (app *App) RemoveRoute(path string, methods ...string)

-func (app *App) RemoveRouteByName()
+func (app *App) RemoveRouteByName(name string, methods ...string)

Likely invalid or redundant comment.

🧰 Tools
🪛 Markdownlint (0.37.0)

775-775: null
Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


791-812: ⚠️ Potential issue

Fix example code to use proper package references.

The example code should use proper package references and constants.

-    app.Get("/api/feature-a", func(c Ctx) error {
-           app.RemoveRoute("/api/feature", MethodGet)
+    app.Get("/api/feature-a", func(c fiber.Ctx) error {
+           app.RemoveRoute("/api/feature", fiber.MethodGet)
            app.RebuildTree()
            // Redefine route
-           app.Get("/api/feature", func(c Ctx) error {
+           app.Get("/api/feature", func(c fiber.Ctx) error {
                    return c.SendString("Testing feature-a")
            })

            app.RebuildTree()
-           return c.SendStatus(http.StatusOK)
+           return c.SendStatus(fiber.StatusOK)
    })

Likely invalid or redundant comment.

router_test.go (5)

487-498: LGTM!

The test is well-structured and effectively verifies route removal by name functionality.


500-508: LGTM!

The test effectively verifies that removing a non-existent route doesn't affect the application state.


510-523: LGTM!

The test effectively verifies the removal of nested routes within groups.


537-557: LGTM!

Both tests effectively verify:

  1. Basic route removal functionality
  2. Handling of non-existent route removal

402-427: 🛠️ Refactor suggestion

Add tb.Helper() call to verifyRouteHandlerCounts.

The function is a test helper but is missing the tb.Helper() call, which would improve test failure reporting by pointing to the correct line in the calling test.

Apply this diff to add the helper call:

 func verifyRouteHandlerCounts(tb testing.TB, app *App, expectedRoutesCount int) {
+    tb.Helper()
     var routes []RouteMessage
     for _, routeStack := range app.stack {
         for _, route := range routeStack {

Likely invalid or redundant comment.

🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 407-407: empty-lines: extra empty line at the start of a block

(revive)


407-407: unnecessary leading newline

(whitespace)


408-408: File is not gofumpt-ed with -extra

(gofumpt)

router_test.go Outdated
Comment on lines 448 to 477
func Test_App_Remove_Route_A_B_Feature_Testing(t *testing.T) {
t.Parallel()
app := New()

app.Get("/api/feature-a", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-a")
})

app.RebuildTree()
return c.SendStatus(http.StatusOK)
})
app.Get("/api/feature-b", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-b")
})

app.RebuildTree()
return c.SendStatus(http.StatusOK)
})

resp, err := app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/feature-a", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusNotFound, resp.StatusCode, "Status code")
require.Equal(t, http.StatusOK, resp, "Status Code")

resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
resp, err = app.Test(httptest.NewRequest(MethodGet, "/api/feature", nil))
require.Equal(t, "Testing feature-a", resp, "Response Message")

resp, err = app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
resp, err = app.Test(httptest.NewRequest(MethodGet, "api/feature-b", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
require.Equal(t, "Testing feature-b", resp, "Response Message")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix response handling and cleanup code.

The test has several issues:

  1. Incorrect response handling - comparing response object with string
  2. Commented code that should be removed
  3. Missing leading slash in path

Apply this diff to fix the issues:

 func Test_App_Remove_Route_A_B_Feature_Testing(t *testing.T) {
     t.Parallel()
     app := New()

     app.Get("/api/feature-a", func(c Ctx) error {
         app.RemoveRoute("/api/feature", MethodGet)
         app.RebuildTree()
-        // Redefine route
         app.Get("/api/feature", func(c Ctx) error {
             return c.SendString("Testing feature-a")
         })
-        app.RebuildTree()
         return c.SendStatus(http.StatusOK)
     })
     app.Get("/api/feature-b", func(c Ctx) error {
         app.RemoveRoute("/api/feature", MethodGet)
         app.RebuildTree()
-        // Redefine route
         app.Get("/api/feature", func(c Ctx) error {
             return c.SendString("Testing feature-b")
         })
-        app.RebuildTree()
         return c.SendStatus(http.StatusOK)
     })

     resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/feature-a", nil))
     require.NoError(t, err, "app.Test(req)")
-    require.Equal(t, http.StatusOK, resp, "Status Code")
+    require.Equal(t, http.StatusOK, resp.StatusCode, "Status Code")

     resp, err = app.Test(httptest.NewRequest(MethodGet, "/api/feature", nil))
-    require.Equal(t, "Testing feature-a", resp, "Response Message")
+    body, err := io.ReadAll(resp.Body)
+    require.NoError(t, err)
+    require.Equal(t, "Testing feature-a", string(body), "Response Message")

-    resp, err = app.Test(httptest.NewRequest(MethodGet, "api/feature-b", nil))
+    resp, err = app.Test(httptest.NewRequest(MethodGet, "/api/feature-b", nil))
     require.NoError(t, err, "app.Test(req)")
-    require.Equal(t, "Testing feature-b", resp, "Response Message")
+    body, err = io.ReadAll(resp.Body)
+    require.NoError(t, err)
+    require.Equal(t, "Testing feature-b", string(body), "Response Message")
 }
📝 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
func Test_App_Remove_Route_A_B_Feature_Testing(t *testing.T) {
t.Parallel()
app := New()
app.Get("/api/feature-a", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-a")
})
app.RebuildTree()
return c.SendStatus(http.StatusOK)
})
app.Get("/api/feature-b", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-b")
})
app.RebuildTree()
return c.SendStatus(http.StatusOK)
})
resp, err := app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/feature-a", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusNotFound, resp.StatusCode, "Status code")
require.Equal(t, http.StatusOK, resp, "Status Code")
resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
resp, err = app.Test(httptest.NewRequest(MethodGet, "/api/feature", nil))
require.Equal(t, "Testing feature-a", resp, "Response Message")
resp, err = app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
resp, err = app.Test(httptest.NewRequest(MethodGet, "api/feature-b", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
require.Equal(t, "Testing feature-b", resp, "Response Message")
}
func Test_App_Remove_Route_A_B_Feature_Testing(t *testing.T) {
t.Parallel()
app := New()
app.Get("/api/feature-a", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-a")
})
return c.SendStatus(http.StatusOK)
})
app.Get("/api/feature-b", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-b")
})
return c.SendStatus(http.StatusOK)
})
resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/feature-a", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status Code")
resp, err = app.Test(httptest.NewRequest(MethodGet, "/api/feature", nil))
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, "Testing feature-a", string(body), "Response Message")
resp, err = app.Test(httptest.NewRequest(MethodGet, "/api/feature-b", nil))
require.NoError(t, err, "app.Test(req)")
body, err = io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, "Testing feature-b", string(body), "Response Message")
}
🧰 Tools
🪛 golangci-lint (1.62.2)

479-479: SA4006: this value of err is never used

(staticcheck)


479-479: ineffectual assignment to err

(ineffassign)

docs/whats_new.md Show resolved Hide resolved
@ckoch786
Copy link
Author

ckoch786 commented Dec 15, 2024

@gaby @coderabbitai I am using the the Makefile lint and test. The lint is now passing locally however the tests are really hit or miss for me locally not sure what is going on. Even when running on a fresh checkout of gofiber/fiber on main three of the tests fail for me.
Also some of the tests that fail when running with make test pass when I run them with go test -run Test_ so not really sure how to figure out why they are failing. Any thoughts?

Also I did get to the point where all the test passed after running a few with go test -run and then rerunning make test but now I am back to 651 of them failing, not sure what I am doing wrong.

ckoch786 added a commit to ckoch786/fiber that referenced this pull request Dec 15, 2024
* Add new methods named RemoveRoute and RemoveRouteByName.

* Update register method to prevent duplicate routes.

* Clean up tests

* Update docs
@ckoch786 ckoch786 force-pushed the feature/3098-allow-removing-registered-route branch from cf59d6d to 01a58ab Compare December 15, 2024 19:59
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

♻️ Duplicate comments (2)
router_test.go (1)

443-477: ⚠️ Potential issue

Fix response handling and remove commented code

The test function Test_App_Remove_Route_A_B_Feature_Testing has several issues:

  • The require.Equal assertions incorrectly compare the *http.Response object to a string. You should read the response body and compare it to the expected string.
  • Commented-out code should be removed for code cleanliness.
  • Missing leading slashes in the request paths.

Apply this diff to fix the issues:

 func Test_App_Remove_Route_A_B_Feature_Testing(t *testing.T) {
     t.Parallel()
     app := New()

     app.Get("/api/feature-a", func(c Ctx) error {
         app.RemoveRoute("/api/feature", MethodGet)
         app.RebuildTree()
-        // Redefine route
         app.Get("/api/feature", func(c Ctx) error {
             return c.SendString("Testing feature-a")
         })
-        app.RebuildTree()
         return c.SendStatus(StatusOK)
     })
     app.Get("/api/feature-b", func(c Ctx) error {
         app.RemoveRoute("/api/feature", MethodGet)
         app.RebuildTree()
-        // Redefine route
         app.Get("/api/feature", func(c Ctx) error {
             return c.SendString("Testing feature-b")
         })
-        app.RebuildTree()
         return c.SendStatus(StatusOK)
     })

     verifyRequest(t, app, "/api/feature-a", StatusOK)

     resp := verifyRequest(t, app, "/api/feature", StatusOK)
+    body, err := io.ReadAll(resp.Body)
+    require.NoError(t, err)
+    require.Equal(t, "Testing feature-a", string(body), "Response Message")

-    resp = verifyRequest(t, app, "api/feature-b", StatusOK)
+    resp = verifyRequest(t, app, "/api/feature-b", StatusOK)
     require.NoError(t, err)
-    require.Equal(t, "Testing feature-b", resp, "Response Message")
+    body, err = io.ReadAll(resp.Body)
+    require.NoError(t, err)
+    require.Equal(t, "Testing feature-b", string(body), "Response Message")
 }
docs/api/app.md (1)

768-816: ⚠️ Potential issue

Fix inconsistencies in example code

The example code has several issues:

  • Uses Ctx instead of fiber.Ctx.
  • Uses undefined MethodGet constant; it should be fiber.MethodGet.
  • Uses undefined http.StatusOK constant; it should be fiber.StatusOK.
  • Missing blank lines around fenced code blocks as per Markdown conventions.

Apply this diff to fix the issues:

 This method removes a route by path. You must call the `RebuildTree()` method after the remove to ensure the route is removed.

 ```go title="Signature"
-func (app *App) RemoveRoute(path string, methods ...string)
+func (app *App) RemoveRoute(path string, methods ...string) 

This method removes a route by name.

-func (app *App) RemoveRouteByName(name string, methods ...string)
+func (app *App) RemoveRouteByName(name string, methods ...string) 
package main

import (
    "log"

    "github.com/gofiber/fiber/v3"
)

func main() {
    app := fiber.New()

    app.Get("/api/feature-a", func(c fiber.Ctx) error {
-           app.RemoveRoute("/api/feature", MethodGet)
+           app.RemoveRoute("/api/feature", fiber.MethodGet)
           app.RebuildTree()
           // Redefine route
-           app.Get("/api/feature", func(c Ctx) error {
+           app.Get("/api/feature", func(c fiber.Ctx) error {
                return c.SendString("Testing feature-a")
            })

           app.RebuildTree()
-           return c.SendStatus(http.StatusOK)
+           return c.SendStatus(fiber.StatusOK)
    })
    app.Get("/api/feature-b", func(c fiber.Ctx) error {
-           app.RemoveRoute("/api/feature", MethodGet)
+           app.RemoveRoute("/api/feature", fiber.MethodGet)
           app.RebuildTree()
           // Redefine route
-           app.Get("/api/feature", func(c Ctx) error {
+           app.Get("/api/feature", func(c fiber.Ctx) error {
                return c.SendString("Testing feature-b")
            })

           app.RebuildTree()
-           return c.SendStatus(http.StatusOK)
+           return c.SendStatus(fiber.StatusOK)
    })

    log.Fatal(app.Listen(":3000"))
}
🧰 Tools
🪛 Markdownlint (0.37.0)

775-775: null
Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🧹 Nitpick comments (2)
docs/api/app.md (2)

765-765: Remove multiple consecutive blank lines

There are multiple consecutive blank lines, which is against Markdown conventions. Please remove extra blank lines to maintain consistency.

Apply this change:

-765~
-
🧰 Tools
🪛 Markdownlint (0.37.0)

765-765: Expected: 1; Actual: 2
Multiple consecutive blank lines

(MD012, no-multiple-blanks)


775-775: Ensure blank lines around fenced code blocks

According to Markdown conventions, fenced code blocks should be surrounded by blank lines. Please add a blank line before the fenced code block.

Apply this change:

-This method removes a route by name
+This method removes a route by name

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Markdownlint (0.37.0)</summary>

775-775: null
Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between cf59d6dbfa9da2b59b46fe52f984bcf45dec93f1 and 01a58abcff9b01434b57af8f77cea337e86d3947.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `docs/api/app.md` (1 hunks)
* `docs/whats_new.md` (1 hunks)
* `router.go` (4 hunks)
* `router_test.go` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* docs/whats_new.md

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Learnings (1)</summary>

<details>
<summary>docs/api/app.md (1)</summary>

Learnt from: ckoch786
PR: #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.


</details>

</details><details>
<summary>🪛 Markdownlint (0.37.0)</summary>

<details>
<summary>docs/api/app.md</summary>

765-765: Expected: 1; Actual: 2
Multiple consecutive blank lines

(MD012, no-multiple-blanks)

---

775-775: null
Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (1)</summary>

<details>
<summary>router.go (1)</summary>

`386-400`: **Ensure consistent handling of case sensitivity in `routeExists`**

The method `routeExists` correctly accounts for case sensitivity in path comparison. However, to ensure consistency across the codebase, consider using `r.Path` instead of `r.path` when comparing routes, as `r.Path` contains the original registered route path.



Run the following script to check for inconsistencies in route path comparisons:

</details>

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link
Contributor

coderabbitai bot commented Dec 15, 2024

Tip

For best results, initiate chat on the files or code changes.

@ckoch786, sorry to hear you're experiencing issues running the tests locally. Here are some steps that might help:

  1. Verify Go Version: Ensure you're using a compatible Go version. Fiber requires Go 1.18 or higher. Check your version with:

    go version
  2. Clean Module Cache and Vendor Directory: Stale dependencies might cause tests to fail. Run:

    go clean -modcache
    rm -rf vendor/
    go mod tidy
    go mod vendor
  3. Run Tests Verbosely: Running tests with verbose output can help identify issues:

    make testverbose

    Or directly:

    go test -v ./...
  4. Check for Environment Variables: Ensure there are no environment variables affecting the tests. Some tests might rely on specific settings.

  5. Port Conflicts: Ensure no other processes are using the ports required by the tests. Conflicts can cause tests to fail unexpectedly.

  6. Update Dependencies: Make sure all dependencies are up to date:

    go get -u ./...
  7. Review Failing Tests: Identify which tests are failing:

    • Run individual tests to isolate issues:

      go test -v -run Test_SpecificFunction ./...
    • Check the error messages for clues.

If the problems persist, please share the specific tests failing and any error messages you receive. This information can help diagnose the issue more effectively.

* Add new methods named RemoveRoute and RemoveRouteByName.

* Update register method to prevent duplicate routes.

* Clean up tests

* Update docs
@ckoch786 ckoch786 force-pushed the feature/3098-allow-removing-registered-route branch from 01a58ab to 0cab312 Compare December 15, 2024 20:09
@ckoch786
Copy link
Author

@gaby I am looking at the failing tests from the actions that ran against my branch. I think I found the the changes that I made that is probably causing them to fail. Though it is weird that they do not always fail for me locally, not sure what that is about yet.

@gaby
Copy link
Member

gaby commented Dec 22, 2024

@gaby I am looking at the failing tests from the actions that ran against my branch. I think I found the the changes that I made that is probably causing them to fail. Though it is weird that they do not always fail for me locally, not sure what that is about yet.

Are you running just the new tests or the full suite?

Use this make test. We use -shuffle to run them in random orders, it helps catch weird edge cases.

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 (5)
router.go (3)

386-400: Code formatting could be improved.

Consider removing the extra blank lines between the case sensitivity check and the return statement to maintain consistent spacing.

 func (app *App) routeExists(method, pathRaw string) bool {
     pathToCheck := pathRaw
     if !app.config.CaseSensitive {
         pathToCheck = utils.ToLower(pathToCheck)
     }
-
     return slices.ContainsFunc(app.stack[app.methodInt(method)], func(r *Route) bool {
         routePath := r.path
         if !app.config.CaseSensitive {
             routePath = utils.ToLower(routePath)
         }
-
         return routePath == pathToCheck
     })
 }

402-416: Consider adding error return values.

The methods could benefit from returning errors to indicate whether the route was found and removed successfully. This would help users handle cases where routes don't exist.

-func (app *App) RemoveRoute(path string, methods ...string) {
+func (app *App) RemoveRoute(path string, methods ...string) error {
     pathMatchFunc := func(r *Route) bool { return r.Path == path }
-    app.deleteRoute(methods, pathMatchFunc)
+    return app.deleteRoute(methods, pathMatchFunc)
 }

-func (app *App) RemoveRouteByName(name string, methods ...string) {
+func (app *App) RemoveRouteByName(name string, methods ...string) error {
     matchFunc := func(r *Route) bool { return r.Name == name }
-    app.deleteRoute(methods, matchFunc)
+    return app.deleteRoute(methods, matchFunc)
 }

418-450: Consider adding validation for methods parameter.

The deleteRoute method could benefit from validating that at least one method is provided to prevent unnecessary locking.

 func (app *App) deleteRoute(methods []string, matchFunc func(r *Route) bool) {
+    if len(methods) == 0 {
+        return
+    }
     app.mutex.Lock()
     defer app.mutex.Unlock()
docs/api/app.md (1)

764-772: Fix typo in method description.

There's a grammatical error in the description.

-This method removes a route by path.  You must call the `RebuildTree()` method after the remove in to ensure the route is removed.
+This method removes a route by path. You must call the `RebuildTree()` method after the remove to ensure the route is removed.
🧰 Tools
🪛 Markdownlint (0.37.0)

765-765: Expected: 1; Actual: 2
Multiple consecutive blank lines

(MD012, no-multiple-blanks)

router_test.go (1)

558-584: Consider increasing concurrent test coverage.

While the concurrent test is well-implemented, consider:

  1. Increasing the number of iterations for better race condition detection
  2. Adding random delays to increase contention
  3. Testing with multiple different routes
-    for i := 0; i < 10; i++ {
+    for i := 0; i < 100; i++ {
         wg.Add(1)
         go func() {
             defer wg.Done()
+            time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
             app.RemoveRoute("/test", MethodGet)
             app.Get("/test", func(c Ctx) error {
                 return c.SendStatus(StatusOK)
             })
         }()
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01a58ab and 92ce4d8.

📒 Files selected for processing (4)
  • docs/api/app.md (1 hunks)
  • docs/whats_new.md (1 hunks)
  • router.go (4 hunks)
  • router_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/whats_new.md
🧰 Additional context used
📓 Learnings (1)
docs/api/app.md (1)
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`.
🪛 Markdownlint (0.37.0)
docs/api/app.md

765-765: Expected: 1; Actual: 2
Multiple consecutive blank lines

(MD012, no-multiple-blanks)


775-775: null
Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🔇 Additional comments (4)
router.go (1)

306-312: LGTM! Well-implemented duplicate route handling.

The implementation correctly prevents duplicate routes by checking existence and removing the old route before adding a new one. This maintains routing consistency and prevents potential conflicts.

docs/api/app.md (1)

779-816: LGTM! Well-documented example.

The example effectively demonstrates the usage of RemoveRoute, including proper error handling and the necessary RebuildTree calls.

router_test.go (2)

376-430: LGTM! Well-implemented test helpers.

The helper functions follow testing best practices, including proper tb.Helper() calls and clear documentation.


494-556: LGTM! Comprehensive edge case coverage.

The tests effectively cover important edge cases including non-existing routes, nested routes, and parameterized routes.

Comment on lines +443 to +477
func Test_App_Remove_Route_A_B_Feature_Testing(t *testing.T) {
t.Parallel()
app := New()

app.Get("/api/feature-a", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-a")
})

app.RebuildTree()
return c.SendStatus(StatusOK)
})
app.Get("/api/feature-b", func(c Ctx) error {
app.RemoveRoute("/api/feature", MethodGet)
app.RebuildTree()
// Redefine route
app.Get("/api/feature", func(c Ctx) error {
return c.SendString("Testing feature-b")
})

return c.SendStatus(http.StatusOK)
app.RebuildTree()
return c.SendStatus(StatusOK)
})

resp, err := app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusNotFound, resp.StatusCode, "Status code")
verifyRequest(t, app, "/api/feature-a", StatusOK)

resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
resp := verifyRequest(t, app, "/api/feature", StatusOK)
require.Equal(t, "Testing feature-a", resp, "Response Message")

resp, err = app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
resp = verifyRequest(t, app, "/api/feature-b", StatusOK)
require.Equal(t, "Testing feature-b", resp, "Response Message")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix test implementation issues.

The test has several issues:

  1. Contains unnecessary comments
  2. Incorrect response comparison
  3. Missing proper response body handling
-    // Redefine route
     app.Get("/api/feature", func(c Ctx) error {
         return c.SendString("Testing feature-a")
     })
-    app.RebuildTree()

     resp := verifyRequest(t, app, "/api/feature", StatusOK)
-    require.Equal(t, "Testing feature-a", resp, "Response Message")
+    body, err := io.ReadAll(resp.Body)
+    require.NoError(t, err)
+    require.Equal(t, "Testing feature-a", string(body), "Response Message")

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

📝 [Proposal]: Allow removing registered route
3 participants