Skip to content

Commit

Permalink
This update clarifies the use of the Route method.
Browse files Browse the repository at this point in the history
It specifically explains that this method doesn't create a new group itself, but applies middleware and routes to the current group. This clarification aids in preventing misuse of the feature. Additionally, new test cases have been added to indicate and verify this corrected usage.
  • Loading branch information
umputun committed Mar 13, 2024
1 parent 6978eff commit c2ad5f7
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 2 deletions.
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,29 @@ apiGroup.With(corsMiddleware, apiMiddleware).Handle("GET /hello", helloHandler)
You can also use the `Route` method to add routes and middleware in a single function call:

```go
group := routegroup.New(http.NewServeMux())
group.Route(func(b *routegroup.Bundle) {
router := routegroup.New(http.NewServeMux())
router.Group().Route(func(b *routegroup.Bundle) {
b.Use(loggingMiddleware, corsMiddleware)
b.Handle("GET /hello", helloHandler)
b.Handle("GET /bye", byeHandler)
})
http.ListenAndServe(":8080", group)
```

Important: The `Route` method does not create a new group by itself; it merely applies middleware and routes to the current group in a functional style. In the example provided, this is technically equivalent to sequentially calling the `Use` and `Handle` methods for the caller's group. While this may not seem intuitive, it is crucial to understand, as using the `Route` method might mistakenly appear to be a way to create a new (sub)group, which it is not. In 99% of cases, `Route` should be called after the creation of a sub-group, either by the `Mount` or `Group` methods.

For example, using `Route` in this manner is likely a mistake, as it will apply middleware to the root group, not to the newly created sub-group.

```go
group := routegroup.New(http.NewServeMux())
group.Route(func(b *routegroup.Bundle) {
b.Use(loggingMiddleware, corsMiddleware)
b.Route(func(sub *routegroup.Bundle) {
sub.Handle("GET /hello", helloHandler)
})
})
```

### Using derived groups

In some instances, it's practical to create an initial group that includes a set of middlewares, and then derive all other groups from it. This approach guarantees that every group incorporates a common set of middlewares as a foundation, allowing each to add its specific middlewares. To facilitate this scenario, `routegroup` offers both `Bundle.Group` and `Bundle.Mount` methods, and it also implements the `http.Handler` interface. The following example illustrates how to use derived groups:
Expand Down
62 changes: 62 additions & 0 deletions group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,68 @@ func TestGroupWithMiddleware(t *testing.T) {
})
}

func TestGroupWithMiddlewareAndTopLevelAfter(t *testing.T) {
group := routegroup.New(http.NewServeMux())

group.Group().Route(func(g *routegroup.Bundle) {
g.Use(testMiddleware)
g.HandleFunc("/test", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("test handler"))
})
})

group.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-Top-Middleware", "true")
next.ServeHTTP(w, r)
})
})

group.HandleFunc("/top", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("top handler"))
})

t.Run("GET /top", func(t *testing.T) {
recorder := httptest.NewRecorder()
request, err := http.NewRequest(http.MethodGet, "/top", http.NoBody)
if err != nil {
t.Fatal(err)
}
group.ServeHTTP(recorder, request)

if recorder.Code != http.StatusOK {
t.Errorf("Expected status code %d, got %d", http.StatusOK, recorder.Code)
}
if header := recorder.Header().Get("X-Top-Middleware"); header != "true" {
t.Errorf("Expected header X-Top-Middleware to be 'true', got '%s'", header)
}
if header := recorder.Header().Get("X-Test-Middleware"); header != "" {
t.Errorf("Expected header X-Test-Middleware not to be set, got '%s'", header)
}
})

t.Run("GET /test", func(t *testing.T) {
recorder := httptest.NewRecorder()
request, err := http.NewRequest(http.MethodGet, "/test", http.NoBody)
if err != nil {
t.Fatal(err)
}
group.ServeHTTP(recorder, request)

if recorder.Code != http.StatusOK {
t.Errorf("Expected status code %d, got %d", http.StatusOK, recorder.Code)
}
if header := recorder.Header().Get("X-Top-Middleware"); header == "true" {
t.Errorf("Expected header X-Top-Middleware not to be set, got '%s'", header)
}
if header := recorder.Header().Get("X-Test-Middleware"); header != "true" {
t.Errorf("Expected header X-Test-Middleware to be 'true', got '%s'", header)
}
})
}

func TestDisableNotFoundHandler(t *testing.T) {
group := routegroup.New(http.NewServeMux())
group.DisableNotFoundHandler()
Expand Down

0 comments on commit c2ad5f7

Please sign in to comment.