diff options
| author | Eltrac <hi@guhub.cn> | 2025-12-03 14:36:10 +0800 |
|---|---|---|
| committer | Eltrac <hi@guhub.cn> | 2025-12-03 14:36:10 +0800 |
| commit | f7643a06846043cf932cf3d2eb7f49be5322ca56 (patch) | |
| tree | 5366abbeb5e8428e12dccd4b322d79e5452bad9d | |
| parent | 59ebb0972ee5107a208ef5e02759a7299afa9288 (diff) | |
feat: routes grouping
| -rw-r--r-- | group.go | 50 | ||||
| -rw-r--r-- | group_test.go | 123 | ||||
| -rw-r--r-- | types.go | 5 |
3 files changed, 178 insertions, 0 deletions
diff --git a/group.go b/group.go new file mode 100644 index 0000000..7cb56ad --- /dev/null +++ b/group.go @@ -0,0 +1,50 @@ +package growel + +import ( + "strings" +) + +/* constructor */ + +// Engine.Group creates a Group +// pass the Group as a parameter to the callback function +// used to create route groups +func (e *Engine) Group(prefix string, fn func(*Group)) { + g := &Group{ + prefix: prefix, + engine: e, + } + + fn(g) +} + +/* Inherit Engine methods */ + +func (g *Group) GET(path string, handler Handler) { + full := joinPath(g.prefix, path) + g.engine.GET(full, handler) +} + +func (g *Group) POST(path string, handler Handler) { + full := joinPath(g.prefix, path) + g.engine.POST(full, handler) +} + +func (g *Group) DELETE(path string, handler Handler) { + full := joinPath(g.prefix, path) + g.engine.DELETE(full, handler) +} + +func (g *Group) PUT(path string, handler Handler) { + full := joinPath(g.prefix, path) + g.engine.PUT(full, handler) +} + +/* utils */ + +func joinPath(a, b string) string { + if a == "/" { + return a + strings.TrimPrefix(b, "/") + } + return strings.TrimSuffix(a, "/") + "/" + strings.TrimPrefix(b, "/") +} diff --git a/group_test.go b/group_test.go new file mode 100644 index 0000000..e682f09 --- /dev/null +++ b/group_test.go @@ -0,0 +1,123 @@ +package growel + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +/* + ====================================================== + Test Group() + ====================================================== +*/ + +func TestGroupCallback(t *testing.T) { + e := New() + + called := false + + e.Group("/user", func(g *Group) { + if g.prefix != "/user" { + t.Fatalf("expected prefix '/user', got '%s'", g.prefix) + } + if g.engine != e { + t.Fatal("group.engine does not point to Engine") + } + called = true + }) + + if !called { + t.Fatal("Group callback not invoked") + } +} + +/* + ====================================================== + Test route registration + ====================================================== +*/ + +func TestGroupRouteRegistration(t *testing.T) { + e := New() + + e.Group("/api", func(api *Group) { + api.GET("/ping", func(c *Context) {}) + api.POST("/user", func(c *Context) {}) + api.PUT("/v1/item/", func(c *Context) {}) + api.DELETE("/v1/item/42", func(c *Context) {}) + }) + + routes := e.router.routes + + if len(routes) != 4 { + t.Fatalf("expected 4 routes, got %d", len(routes)) + } + + cases := []struct { + method string + path string + }{ + {"GET", "/api/ping"}, + {"POST", "/api/user"}, + {"PUT", "/api/v1/item"}, + {"DELETE", "/api/v1/item/42"}, + } + + for _, want := range cases { + found := false + + for _, r := range routes { + joined := "/" + joinParts(r.parts) + if r.method == want.method && joined == want.path { + found = true + break + } + } + + if !found { + t.Errorf("missing expected route: %s %s", want.method, want.path) + } + } +} + +// helper: convert []string{"api","ping"} → "api/ping" +func joinParts(parts []string) string { + out := "" + for i, p := range parts { + if i > 0 { + out += "/" + } + out += p + } + return out +} + +/* + ====================================================== + End-to-End ServeHTTP test + ====================================================== +*/ + +func TestGroupServeHTTP(t *testing.T) { + e := New() + + e.Group("/hello", func(h *Group) { + h.GET("/world", func(c *Context) { + c.Plain(200, "ok") + }) + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/hello/world", nil) + + e.ServeHTTP(w, req) + + if w.Code != 200 { + t.Fatalf("expected 200, got %d", w.Code) + } + + if body := w.Body.String(); body != "ok\n" && body != "ok" { + t.Fatalf("expected body 'ok', got '%s'", body) + } +} @@ -24,3 +24,8 @@ type Router struct { type Engine struct { router *Router } + +type Group struct { + prefix string + engine *Engine +} |
