Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions internal/appdef/jsonformat/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion pkg/cache/mem.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
50 changes: 19 additions & 31 deletions pkg/webkit/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -39,7 +38,6 @@ func New() *Kit {
ErrorHandler: DefaultErrorHandler,
NotFoundHandler: DefaultNotFoundHandler,
mux: chi.NewRouter(),
plugs: []Plug{},
}
}

Expand All @@ -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.
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
49 changes: 37 additions & 12 deletions pkg/webkit/mux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading