From 8068786909224605bc2e2579fc2c20040cd5b9e3 Mon Sep 17 00:00:00 2001 From: wlkjyy Date: Sun, 21 Jun 2026 14:51:15 +0800 Subject: [PATCH] feat: [#970] implement withoutMiddleware for routes - Group.WithoutMiddleware: excludes middleware by reflect.Type at registration time (isSameMiddleware in utils.go) - Action.WithoutMiddleware: stores exclusions in Info.ExcludedMiddleware - group.go: Middleware/WithoutMiddleware symmetric, inline constructors - tests: TestWithoutMiddleware in GroupTestSuite, TestIsSameMiddleware - replace directive points to framework PR branch until merged --- action.go | 8 +++++ context.go | 6 ++-- go.mod | 5 ++- go.sum | 4 +-- group.go | 75 +++++++++++++++++++++++++++++++++++++------ group_test.go | 15 +++++++++ middleware_timeout.go | 2 +- utils.go | 20 ++++++++++++ utils_test.go | 12 +++++++ 9 files changed, 131 insertions(+), 16 deletions(-) diff --git a/action.go b/action.go index 70dde32..6989fbb 100644 --- a/action.go +++ b/action.go @@ -38,3 +38,11 @@ func (r *Action) Name(name string) contractsroute.Action { return r } + +func (r *Action) WithoutMiddleware(middleware ...contractshttp.Middleware) contractsroute.Action { + info := routes[r.path][r.method] + info.ExcludedMiddleware = append(info.ExcludedMiddleware, middleware...) + routes[r.path][r.method] = info + + return r +} diff --git a/context.go b/context.go index 01dc3f6..a265271 100644 --- a/context.go +++ b/context.go @@ -15,9 +15,9 @@ type sharedValuesKeyType struct{} type sharedUserCtxKeyType struct{} var ( - sessionKey = sessionKeyType{} - sharedValuesKey = sharedValuesKeyType{} - sharedUserCtxKey = sharedUserCtxKeyType{} + sessionKey = sessionKeyType{} + sharedValuesKey = sharedValuesKeyType{} + sharedUserCtxKey = sharedUserCtxKeyType{} ) func Background() http.Context { diff --git a/go.mod b/go.mod index 75e43d7..e3add06 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.25.6 require ( github.com/gofiber/fiber/v3 v3.3.0 github.com/gofiber/utils/v2 v2.1.0 - github.com/goravel/framework v1.17.2-0.20260620091942-4f2585d35807 + github.com/goravel/framework v1.17.2-0.20260621064957-8c973e865adb github.com/spf13/cast v1.10.0 github.com/stretchr/testify v1.11.1 github.com/valyala/fasthttp v1.71.0 @@ -126,3 +126,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/gorm v1.31.1 // indirect ) + +// TODO: remove after framework PR is merged +replace github.com/goravel/framework => github.com/u-wlkjyy/framework v1.17.2-0.20260621064957-8c973e865adb diff --git a/go.sum b/go.sum index 88c8105..01eca68 100644 --- a/go.sum +++ b/go.sum @@ -131,8 +131,6 @@ github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQ github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA= github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= -github.com/goravel/framework v1.17.2-0.20260620091942-4f2585d35807 h1:ghACsFUv+iwFYEh5AlhCMONb8tu3iL9vQTB3BZeBeEg= -github.com/goravel/framework v1.17.2-0.20260620091942-4f2585d35807/go.mod h1:J3xvbbFAbS/sYePrlIMfUD6anGcrnE49mnntYlKUvls= github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -236,6 +234,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ= github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= +github.com/u-wlkjyy/framework v1.17.2-0.20260621064957-8c973e865adb h1:052mJgGyRtXRfLrpcMpTZMRwQr+ogVdABfHdZrxd/rk= +github.com/u-wlkjyy/framework v1.17.2-0.20260621064957-8c973e865adb/go.mod h1:J3xvbbFAbS/sYePrlIMfUD6anGcrnE49mnntYlKUvls= github.com/urfave/cli/v3 v3.10.0 h1:0aU8yOObVDMkM13Cj4G+zb4P0PdeJMec65f81Ak1ioM= github.com/urfave/cli/v3 v3.10.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= diff --git a/group.go b/group.go index 99c8450..1cab8c7 100644 --- a/group.go +++ b/group.go @@ -18,11 +18,12 @@ import ( ) type Group struct { - config config.Config - instance *fiber.App - prefix string - middlewares []contractshttp.Middleware - lastMiddlewares []contractshttp.Middleware + config config.Config + instance *fiber.App + prefix string + middlewares []contractshttp.Middleware + lastMiddlewares []contractshttp.Middleware + excludedMiddlewares []contractshttp.Middleware } func NewGroup(config config.Config, instance *fiber.App, prefix string, middlewares []contractshttp.Middleware, lastMiddlewares []contractshttp.Middleware) contractsroute.Router { @@ -36,15 +37,47 @@ func NewGroup(config config.Config, instance *fiber.App, prefix string, middlewa } func (r *Group) Group(handler contractsroute.GroupFunc) { - handler(NewGroup(r.config, r.instance, r.getFullPath(""), r.middlewares, r.lastMiddlewares)) + handler(&Group{ + config: r.config, + instance: r.instance, + prefix: r.getFullPath(""), + middlewares: r.middlewares, + lastMiddlewares: r.lastMiddlewares, + excludedMiddlewares: r.excludedMiddlewares, + }) } func (r *Group) Prefix(path string) contractsroute.Router { - return NewGroup(r.config, r.instance, r.getFullPath(path), r.middlewares, r.lastMiddlewares) + return &Group{ + config: r.config, + instance: r.instance, + prefix: r.getFullPath(path), + middlewares: r.middlewares, + lastMiddlewares: r.lastMiddlewares, + excludedMiddlewares: r.excludedMiddlewares, + } } func (r *Group) Middleware(middlewares ...contractshttp.Middleware) contractsroute.Router { - return NewGroup(r.config, r.instance, r.getFullPath(""), append(r.middlewares, middlewares...), r.lastMiddlewares) + return &Group{ + config: r.config, + instance: r.instance, + prefix: r.getFullPath(""), + middlewares: append(r.middlewares, middlewares...), + lastMiddlewares: r.lastMiddlewares, + excludedMiddlewares: r.excludedMiddlewares, + } +} + +func (r *Group) WithoutMiddleware(middlewares ...contractshttp.Middleware) contractsroute.Router { + return &Group{ + config: r.config, + instance: r.instance, + prefix: r.getFullPath(""), + middlewares: r.middlewares, + lastMiddlewares: r.lastMiddlewares, + excludedMiddlewares: append(r.excludedMiddlewares, middlewares...), + } } func (r *Group) Any(path string, handler contractshttp.HandlerFunc) contractsroute.Action { @@ -157,7 +190,7 @@ func (h httpFSToFS) Open(name string) (fs.File, error) { func (r *Group) getMiddlewares(handler contractshttp.HandlerFunc) []fiber.Handler { var middlewares []fiber.Handler - middlewares = middlewaresToFiberHandlers(append(r.middlewares, r.lastMiddlewares...)) + middlewares = middlewaresToFiberHandlers(r.excludeMiddlewares(append(r.middlewares, r.lastMiddlewares...))) if handler != nil { middlewares = append(middlewares, handlerToFiberHandler(handler)) } @@ -165,6 +198,30 @@ func (r *Group) getMiddlewares(handler contractshttp.HandlerFunc) []fiber.Handle return middlewares } +// excludeMiddlewares filters out middlewares excluded via WithoutMiddleware, +// comparing by reflect.Type (see isSameMiddleware in utils.go). +func (r *Group) excludeMiddlewares(middlewares []contractshttp.Middleware) []contractshttp.Middleware { + if len(r.excludedMiddlewares) == 0 { + return middlewares + } + + var result []contractshttp.Middleware + for _, middleware := range middlewares { + excluded := false + for _, ex := range r.excludedMiddlewares { + if isSameMiddleware(ex, middleware) { + excluded = true + break + } + } + if !excluded { + result = append(result, middleware) + } + } + + return result +} + func (r *Group) getMiddlewaresWithPath(path string, handler contractshttp.HandlerFunc) []any { var handlers []any handlers = append(handlers, path) diff --git a/group_test.go b/group_test.go index d1d8d31..338b2b5 100644 --- a/group_test.go +++ b/group_test.go @@ -360,6 +360,21 @@ func (s *GroupTestSuite) TestIssue408() { s.Equal("/prefix/{id}/test/{name}", routes[1].Path) } +func (s *GroupTestSuite) TestWithoutMiddleware() { + mw := func(ctx contractshttp.Context) { + ctx.WithValue("mw", "applied") + ctx.Request().Next() + } + + s.route.Middleware(mw).WithoutMiddleware(mw).Get("/without", func(ctx contractshttp.Context) contractshttp.Response { + return ctx.Response().Json(http.StatusOK, contractshttp.Json{ + "mw": ctx.Value("mw"), + }) + }) + + s.assert("GET", "/without", http.StatusOK, `{"mw":null}`) +} + func (s *GroupTestSuite) assert(method, url string, expectCode int, expectBody string) { req, err := http.NewRequest(method, url, nil) s.Nil(err) diff --git a/middleware_timeout.go b/middleware_timeout.go index d9864b7..a7fbc34 100644 --- a/middleware_timeout.go +++ b/middleware_timeout.go @@ -5,9 +5,9 @@ import ( "errors" "time" - contractshttp "github.com/goravel/framework/contracts/http" "github.com/gofiber/fiber/v3" fibertimeout "github.com/gofiber/fiber/v3/middleware/timeout" + contractshttp "github.com/goravel/framework/contracts/http" ) // Timeout creates middleware to set a timeout for a request. diff --git a/utils.go b/utils.go index b30e441..fabc9e1 100644 --- a/utils.go +++ b/utils.go @@ -2,6 +2,7 @@ package fiber import ( "errors" + "reflect" "regexp" "strings" @@ -108,3 +109,22 @@ func mergeSlashForPath(path string) string { return strings.ReplaceAll(path, "//", "/") } + +// isSameMiddleware reports whether two middleware values have the same concrete +// type (dereferencing pointers). This lets WithoutMiddleware match struct-based +// middleware across different instances. Closure-based middleware (func(Context)) +// share a single reflect.Type and cannot be told apart — a documented limitation. +func isSameMiddleware(a, b any) bool { + tA := reflect.TypeOf(a) + tB := reflect.TypeOf(b) + if tA == nil || tB == nil { + return false + } + if tA.Kind() == reflect.Pointer { + tA = tA.Elem() + } + if tB.Kind() == reflect.Pointer { + tB = tB.Elem() + } + return tA == tB +} diff --git a/utils_test.go b/utils_test.go index cbdb9fb..9371bc3 100644 --- a/utils_test.go +++ b/utils_test.go @@ -13,3 +13,15 @@ func TestBracketToColon(t *testing.T) { func TestColonToBracket(t *testing.T) { assert.Equal(t, "/{id}/{name}", colonToBracket("/:id/:name")) } + +func TestIsSameMiddleware(t *testing.T) { + type mwA struct{} + type mwB struct{} + + assert.True(t, isSameMiddleware(&mwA{}, &mwA{})) + assert.False(t, isSameMiddleware(&mwA{}, &mwB{})) + fn1 := func() {} + fn2 := func() {} + assert.True(t, isSameMiddleware(fn1, fn2)) + assert.False(t, isSameMiddleware(nil, fn1)) +}