diff --git a/internal/appdef/jsonformat/formatter.go b/internal/appdef/jsonformat/formatter.go index 6bbb6017..e9468ddb 100644 --- a/internal/appdef/jsonformat/formatter.go +++ b/internal/appdef/jsonformat/formatter.go @@ -22,7 +22,7 @@ func RegisterType(t reflect.Type) { // scanType recursively scans a type for inline tags. func scanType(t reflect.Type) { - for t.Kind() == reflect.Ptr { + for t.Kind() == reflect.Pointer { t = t.Elem() } if t.Kind() != reflect.Struct { @@ -52,7 +52,7 @@ func scanType(t reflect.Type) { // Recurse into struct fields. fieldType := field.Type - for fieldType.Kind() == reflect.Ptr || fieldType.Kind() == reflect.Slice || fieldType.Kind() == reflect.Map { + for fieldType.Kind() == reflect.Pointer || fieldType.Kind() == reflect.Slice || fieldType.Kind() == reflect.Map { fieldType = fieldType.Elem() } if fieldType.Kind() == reflect.Struct { diff --git a/pkg/cache/mem.go b/pkg/cache/mem.go index 195450e9..02c24eb1 100644 --- a/pkg/cache/mem.go +++ b/pkg/cache/mem.go @@ -53,7 +53,7 @@ func (c *MemCache) Get(_ context.Context, key string, value interface{}) error { } // Check if value is a pointer - if reflect.TypeOf(value).Kind() != reflect.Ptr { + if reflect.TypeOf(value).Kind() != reflect.Pointer { return errors.New("value must be a pointer") } diff --git a/pkg/webkit/mux.go b/pkg/webkit/mux.go index 794f61cd..9ad94c5f 100644 --- a/pkg/webkit/mux.go +++ b/pkg/webkit/mux.go @@ -23,7 +23,6 @@ type ( ErrorHandler ErrorHandler NotFoundHandler Handler mux *chi.Mux - plugs []Plug } // Handler is a function that handles HTTP requests. Handler func(c *Context) error @@ -39,7 +38,6 @@ func New() *Kit { ErrorHandler: DefaultErrorHandler, NotFoundHandler: DefaultNotFoundHandler, mux: chi.NewRouter(), - plugs: []Plug{}, } } @@ -61,12 +59,28 @@ func (k *Kit) Add(method string, pattern string, handler Handler, plugs ...Plug) }) } -// Plug adds a middleware function to the chain. These are called after -// the funcs that are passed directly to the route-level handlers. +// Plug registers middleware that runs before route matching, mirroring chi's +// Use. It fires on every request — including OPTIONS preflights to paths that +// only register other HTTP methods — making it suitable for cross-cutting +// concerns such as CORS, logging, and request IDs. // // For example: app.Plug(middleware.Logger) func (k *Kit) Plug(plugs ...Plug) { - k.plugs = append(k.plugs, plugs...) + for _, plug := range plugs { + k.mux.Use(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := NewContext(w, r) + h := plug(func(c *Context) error { + r = c.Request + next.ServeHTTP(w, r) + return nil + }) + if err := h(ctx); err != nil { + k.handleError(ctx, err) + } + }) + }) + } } // Start starts the HTTP server. @@ -136,10 +150,6 @@ func (k *Kit) handle(w http.ResponseWriter, r *http.Request, handler Handler, pl h = plugs[i](h) } - for i := len(k.plugs) - 1; i >= 0; i-- { - h = k.plugs[i](h) - } - if err := h(ctx); err != nil { k.handleError(ctx, err) } @@ -223,37 +233,15 @@ func (k *Kit) Mount(pattern string, handler http.Handler) { // Group allows you to group multiple routes together under a common path. // The provided function can use the `kit` to add routes, middleware, etc. func (k *Kit) Group(pattern string, groupFunc func(kit *Kit)) { - // Create a sub-router using Chi. subRouter := chi.NewRouter() - // Apply middleware from parent to the subRouter. - for _, plug := range k.plugs { - subRouter.Use(func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := NewContext(w, r) - h := plug(func(c *Context) error { - r = c.Request - next.ServeHTTP(w, r) - return nil - }) - if err := h(ctx); err != nil { - k.handleError(ctx, err) - } - }) - }) - } - - // Create a new kit with the sub-router and inherit error handlers. subKit := &Kit{ ErrorHandler: k.ErrorHandler, NotFoundHandler: k.NotFoundHandler, mux: subRouter, - plugs: k.plugs, // Inherit parent plugs } - // Call the provided function with the sub-kit. groupFunc(subKit) - // Mount the sub router to the parent router. k.mux.Mount(pattern, subRouter) } diff --git a/pkg/webkit/mux_test.go b/pkg/webkit/mux_test.go index 01019966..d57c0d4f 100644 --- a/pkg/webkit/mux_test.go +++ b/pkg/webkit/mux_test.go @@ -45,20 +45,45 @@ func TestAdd(t *testing.T) { } func TestKit_Plug(t *testing.T) { - app := New() - app.Plug(func(next Handler) Handler { - return func(ctx *Context) error { - ctx.Set("test", "test") - return next(ctx) - } + t.Parallel() + + t.Run("Sets context value visible to handler", func(t *testing.T) { + t.Parallel() + app := New() + app.Plug(func(next Handler) Handler { + return func(ctx *Context) error { + ctx.Set("test", "test") + return next(ctx) + } + }) + app.Get("/", func(ctx *Context) error { + assert.Equal(t, "test", ctx.Get("test")) + return ctx.String(http.StatusOK, "test") + }) + rr := httptest.NewRecorder() + app.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/", nil)) + assert.Equal(t, http.StatusOK, rr.Code) }) - app.Get("/", func(ctx *Context) error { - assert.Equal(t, "test", ctx.Get("test")) - return ctx.String(http.StatusOK, "test") + + t.Run("Runs on OPTIONS preflight to GET-only route", func(t *testing.T) { + t.Parallel() + app := New() + app.Plug(func(next Handler) Handler { + return func(ctx *Context) error { + ctx.Response.Header().Set("X-Plug-Middleware", "ran") + if ctx.Request.Method == http.MethodOptions { + ctx.Response.WriteHeader(http.StatusOK) + return nil + } + return next(ctx) + } + }) + app.Get("/thing", handler) + rr := httptest.NewRecorder() + app.ServeHTTP(rr, httptest.NewRequest(http.MethodOptions, "/thing", nil)) + assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, "ran", rr.Header().Get("X-Plug-Middleware")) }) - rr := httptest.NewRecorder() - app.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/", nil)) - assert.Equal(t, http.StatusOK, rr.Code) } func TestKit_Connect(t *testing.T) {