Repository: swaggest/rest (go)

Issue:
Middlewares not working?
Hi,

I started to use this module in a project but I can't get most of the middlewares to work with the chi wrapper.

When I use the default chi router it works without problem (e.g with CORS and StipSlashes middlewares):

```go

r := chi.NewRouter()
r.Use(
middleware.StripSlashes,
cors.Handler(cors.Options{
			AllowedOrigins:   "*",
			AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
			AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
			AllowCredentials: false,
			MaxAge:           300,
		})
)
```

But with the wrapper it does not, as I am getting CORSed on preflight requests and end slashes are not stripped:

```go
r :=  chirouter.NewWrapper(chi.NewRouter())
r.Use(...) //Same as above
```

Do you have any idea why? 

Pull request description:
Fix pre-routing middlewares by using chi's management
Fixes #62 

Patch:
diff --git a/chirouter/wrapper.go b/chirouter/wrapper.go
index 8ba56ff..66194de 100644
--- a/chirouter/wrapper.go
+++ b/chirouter/wrapper.go
@@ -39,13 +39,14 @@ func (r *Wrapper) copy(router chi.Router, pattern string) *Wrapper {
 
 // Use appends one of more middlewares onto the Router stack.
 func (r *Wrapper) Use(middlewares ...func(http.Handler) http.Handler) {
+	r.Router.Use(middlewares...)
 	r.middlewares = append(r.middlewares, middlewares...)
 }
 
 // With adds inline middlewares for an endpoint handler.
 func (r Wrapper) With(middlewares ...func(http.Handler) http.Handler) chi.Router {
-	c := r.copy(r.Router, "")
-	c.Use(middlewares...)
+	c := r.copy(r.Router.With(middlewares...), "")
+	c.middlewares = append(c.middlewares, middlewares...)
 
 	return c
 }
@@ -76,18 +77,20 @@ func (r *Wrapper) Route(pattern string, fn func(r chi.Router)) chi.Router {
 
 // Mount attaches another http.Handler along "./basePattern/*".
 func (r *Wrapper) Mount(pattern string, h http.Handler) {
-	p := r.prepareHandler("", pattern, h)
-	r.Router.Mount(pattern, p)
+	r.captureHandler("", pattern, h)
+	r.Router.Mount(pattern, h)
 }
 
 // Handle adds routes for `basePattern` that matches all HTTP methods.
 func (r *Wrapper) Handle(pattern string, h http.Handler) {
-	r.Router.Handle(pattern, r.prepareHandler("", pattern, h))
+	r.captureHandler("", pattern, h)
+	r.Router.Handle(pattern, h)
 }
 
 // Method adds routes for `basePattern` that matches the `method` HTTP method.
 func (r *Wrapper) Method(method, pattern string, h http.Handler) {
-	r.Router.Method(method, pattern, r.prepareHandler(method, pattern, h))
+	r.captureHandler(method, pattern, h)
+	r.Router.Method(method, pattern, h)
 }
 
 // MethodFunc adds the route `pattern` that matches `method` http method to execute the `handlerFn` http.HandlerFunc.
@@ -144,10 +147,8 @@ func (r *Wrapper) resolvePattern(pattern string) string {
 	return r.basePattern + strings.ReplaceAll(pattern, "/*/", "/")
 }
 
-func (r *Wrapper) prepareHandler(method, pattern string, h http.Handler) http.Handler {
+func (r *Wrapper) captureHandler(method, pattern string, h http.Handler) {
 	mw := r.middlewares
 	mw = append(mw, nethttp.HandlerWithRouteMiddleware(method, r.resolvePattern(pattern)))
-	h = nethttp.WrapHandler(h, mw...)
-
-	return h
+	nethttp.WrapHandler(h, mw...)
 }
diff --git a/nethttp/wrap.go b/nethttp/wrap.go
index 082c0d0..9772260 100644
--- a/nethttp/wrap.go
+++ b/nethttp/wrap.go
@@ -88,3 +88,11 @@ type wrappedHandler struct {
 	wrapped http.Handler
 	mwName  string
 }
+
+func (w *wrappedHandler) String() string {
+	if h, ok := w.wrapped.(*wrappedHandler); ok {
+		return w.mwName + "(" + h.String() + ")"
+	}
+
+	return "handler"
+}


Test patch:
diff --git a/chirouter/wrapper_test.go b/chirouter/wrapper_test.go
index c2867fb..e4f02a7 100644
--- a/chirouter/wrapper_test.go
+++ b/chirouter/wrapper_test.go
@@ -7,8 +7,10 @@ import (
 	"testing"
 
 	"github.com/go-chi/chi/v5"
+	"github.com/go-chi/chi/v5/middleware"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"github.com/swaggest/rest"
 	"github.com/swaggest/rest/chirouter"
 	"github.com/swaggest/rest/nethttp"
 )
@@ -23,28 +25,47 @@ type HandlerWithBar struct {
 	http.Handler
 }
 
+func (h HandlerWithFoo) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+	if _, err := rw.Write([]byte("foo")); err != nil {
+		panic(err)
+	}
+
+	h.Handler.ServeHTTP(rw, r)
+}
+
 func (h HandlerWithBar) Bar() {}
 
 func (h HandlerWithBar) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+	h.Handler.ServeHTTP(rw, r)
+
 	if _, err := rw.Write([]byte("bar")); err != nil {
 		panic(err)
 	}
-
-	h.Handler.ServeHTTP(rw, r)
 }
 
 func TestNewWrapper(t *testing.T) {
-	var r chi.Router
-	r = chi.NewRouter()
-
-	r = chirouter.NewWrapper(r).With(func(handler http.Handler) http.Handler {
+	r := chirouter.NewWrapper(chi.NewRouter()).With(func(handler http.Handler) http.Handler {
 		return http.HandlerFunc(handler.ServeHTTP)
 	})
 
+	handlersCnt := 0
+	totalCnt := 0
+
 	mw := func(handler http.Handler) http.Handler {
-		var bar interface{ Bar() }
+		var (
+			withRoute rest.HandlerWithRoute
+			bar       interface{ Bar() }
+			foo       interface{ Foo() }
+		)
 
-		assert.False(t, nethttp.HandlerAs(handler, &bar))
+		totalCnt++
+
+		if nethttp.HandlerAs(handler, &withRoute) {
+			handlersCnt++
+
+			assert.False(t, nethttp.HandlerAs(handler, &bar), "%s", handler)
+			assert.True(t, nethttp.HandlerAs(handler, &foo), "%s", handler)
+		}
 
 		return HandlerWithBar{Handler: handler}
 	}
@@ -62,11 +83,13 @@ func TestNewWrapper(t *testing.T) {
 		)
 	})
 
-	r.Mount("/mount", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}))
+	r.Mount("/mount",
+		HandlerWithFoo{Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {})},
+	)
 
-	r.Route("/deeper/", func(r chi.Router) {
+	r.Route("/deeper", func(r chi.Router) {
 		r.Use(func(handler http.Handler) http.Handler {
-			return handler
+			return HandlerWithFoo{Handler: handler}
 		})
 
 		r.Get("/foo", func(writer http.ResponseWriter, request *http.Request) {})
@@ -91,6 +114,110 @@ func TestNewWrapper(t *testing.T) {
 		rw := httptest.NewRecorder()
 		r.ServeHTTP(rw, req)
 
-		assert.Equal(t, "bar", rw.Body.String(), u)
+		assert.Equal(t, "foobar", rw.Body.String(), u)
 	}
+
+	assert.Equal(t, 13, handlersCnt)
+	assert.Equal(t, 20, totalCnt)
+}
+
+func TestWrapper_Use_precedence(t *testing.T) {
+	var log []string
+
+	// Vanilla chi router.
+	cr := chi.NewRouter()
+	cr.Use(
+		func(handler http.Handler) http.Handler {
+			return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+				log = append(log, "cmw1 before")
+				handler.ServeHTTP(writer, request)
+				log = append(log, "cmw1 after")
+			})
+		},
+
+		func(handler http.Handler) http.Handler {
+			return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+				log = append(log, "cmw2 before")
+				handler.ServeHTTP(writer, request)
+				log = append(log, "cmw2 after")
+			})
+		},
+	)
+
+	// Wrapped chi router.
+	wr := chirouter.NewWrapper(chi.NewRouter())
+	wr.Use(
+		func(handler http.Handler) http.Handler {
+			return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+				log = append(log, "wmw1 before")
+				handler.ServeHTTP(writer, request)
+				log = append(log, "wmw1 after")
+			})
+		},
+
+		func(handler http.Handler) http.Handler {
+			return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+				log = append(log, "wmw2 before")
+				handler.ServeHTTP(writer, request)
+				log = append(log, "wmw2 after")
+			})
+		},
+	)
+
+	req, err := http.NewRequest(http.MethodGet, "/", nil)
+	require.NoError(t, err)
+
+	h := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+		log = append(log, "h")
+	})
+
+	// Both routers should invoke middlewares in the same order.
+	cr.Method(http.MethodGet, "/", h)
+	wr.Method(http.MethodGet, "/", h)
+
+	cr.ServeHTTP(nil, req)
+	wr.ServeHTTP(nil, req)
+	assert.Equal(t, []string{
+		"cmw1 before", "cmw2 before", "h", "cmw2 after", "cmw1 after",
+		"wmw1 before", "wmw2 before", "h", "wmw2 after", "wmw1 after",
+	}, log)
+}
+
+func TestWrapper_Use_StripSlashes(t *testing.T) {
+	var log []string
+
+	// Wrapped chi router.
+	wr := chirouter.NewWrapper(chi.NewRouter())
+	wr.Use(
+		middleware.StripSlashes,
+
+		func(handler http.Handler) http.Handler {
+			return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+				handler.ServeHTTP(writer, request)
+			})
+		},
+	)
+
+	req, err := http.NewRequest(http.MethodGet, "/foo/", nil)
+	require.NoError(t, err)
+
+	rw := httptest.NewRecorder()
+
+	h := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+		if _, err := writer.Write([]byte("OK")); err != nil {
+			log = append(log, err.Error())
+		}
+
+		log = append(log, "h")
+	})
+
+	wr.Method(http.MethodGet, "/foo", h)
+	wr.ServeHTTP(rw, req)
+
+	assert.Equal(t, http.StatusOK, rw.Code)
+	assert.Equal(t, "OK", rw.Body.String())
+
+	assert.Equal(t, []string{
+		"h",
+	}, log)
 }
diff --git a/web/service_test.go b/web/service_test.go
index 64d8456..a7c7abb 100644
--- a/web/service_test.go
+++ b/web/service_test.go
@@ -28,7 +28,8 @@ func TestDefaultService(t *testing.T) {
 	service.Trace("/albums", postAlbums(), nethttp.SuccessStatus(http.StatusCreated))
 	service.Options("/albums", postAlbums(), nethttp.SuccessStatus(http.StatusCreated))
 	service.Docs("/docs", func(title, schemaURL, basePath string) http.Handler {
-		return nil
+		// Mount github.com/swaggest/swgui/v4emb.New here.
+		return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {})
 	})
 
 	rw := httptest.NewRecorder()