fix: fix different params configuration

This commit is contained in:
Davide Bianchi
2022-12-23 21:03:53 +01:00
parent 48e22b5674
commit 4587271bc2
13 changed files with 331 additions and 67 deletions

View File

@@ -3,4 +3,5 @@ package apirouter
type Router[HandlerFunc any, Route any] interface {
AddRoute(method string, path string, handler HandlerFunc) Route
SwaggerHandler(contentType string, blob []byte) HandlerFunc
TransformPathToOasPath(path string) string
}

View File

@@ -0,0 +1,15 @@
package apirouter
import (
"strings"
)
func TransformPathParamsWithColon(path string) string {
pathParams := strings.Split(path, "/")
for i, param := range pathParams {
if strings.HasPrefix(param, ":") {
pathParams[i] = strings.Replace(param, ":", "{", 1) + "}"
}
}
return strings.Join(pathParams, "/")
}

View File

@@ -0,0 +1,59 @@
package apirouter
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestTransformPathParamsWithColon(t *testing.T) {
testCases := []struct {
name string
path string
expectedPath string
}{
{
name: "only /",
path: "/",
expectedPath: "/",
},
{
name: "without params",
path: "/foo",
expectedPath: "/foo",
},
{
name: "without params ending with /",
path: "/foo/",
expectedPath: "/foo/",
},
{
name: "with params",
path: "/foo/:par1",
expectedPath: "/foo/{par1}",
},
{
name: "with params ending with /",
path: "/foo/:par1/",
expectedPath: "/foo/{par1}/",
},
{
name: "with multiple params",
path: "/:par1/:par2/:par3",
expectedPath: "/{par1}/{par2}/{par3}",
},
{
name: "with multiple params ending with /",
path: "/:par1/:par2/:par3/",
expectedPath: "/{par1}/{par2}/{par3}/",
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
actual := TransformPathParamsWithColon(test.path)
require.Equal(t, test.expectedPath, actual)
})
}
}

View File

@@ -336,7 +336,7 @@ func TestGenerateAndExposeSwagger(t *testing.T) {
w.Write([]byte("ok"))
}, Definitions{})
mSubRouter := mRouter.PathPrefix("/prefix").Subrouter()
mSubRouter := mRouter.NewRoute().Subrouter()
subrouter, err := router.SubRouter(gorilla.NewRouter(mSubRouter), SubRouterOptions{
PathPrefix: "/prefix",
})
@@ -370,6 +370,30 @@ func TestGenerateAndExposeSwagger(t *testing.T) {
actual, err := os.ReadFile("testdata/subrouter.json")
require.NoError(t, err)
require.JSONEq(t, string(actual), body)
t.Run("test request /prefix", func(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/prefix", nil)
mRouter.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
})
t.Run("test request /prefix/taz", func(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/prefix/taz", nil)
mRouter.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
})
t.Run("test request /foo", func(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
mRouter.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
})
})
t.Run("ok - new router with path prefix", func(t *testing.T) {
@@ -405,7 +429,7 @@ func TestGenerateAndExposeSwagger(t *testing.T) {
body := readBody(t, w.Result().Body)
actual, err := os.ReadFile("testdata/router_with_prefix.json")
require.NoError(t, err)
require.JSONEq(t, string(actual), body)
require.JSONEq(t, string(actual), body, body)
})
}

View File

@@ -38,7 +38,8 @@ func (r Router[HandlerFunc, Route]) AddRawRoute(method string, routePath string,
}
}
pathWithPrefix := path.Join(r.pathPrefix, routePath)
r.swaggerSchema.AddOperation(pathWithPrefix, method, op)
oasPath := r.router.TransformPathToOasPath(pathWithPrefix)
r.swaggerSchema.AddOperation(oasPath, method, op)
// Handle, when content-type is json, the request/response marshalling? Maybe with a specific option.
return r.router.AddRoute(method, pathWithPrefix, handler), nil
@@ -104,7 +105,8 @@ func (r Router[HandlerFunc, Route]) AddRoute(method string, path string, handler
return getZero[Route](), fmt.Errorf("%w: %s", ErrResponses, err)
}
err = r.resolveParameterSchema(pathParamsType, getPathParamsAutofilled(schema, path), operation)
oasPath := r.router.TransformPathToOasPath(path)
err = r.resolveParameterSchema(pathParamsType, getPathParamsAutoComplete(schema, oasPath), operation)
if err != nil {
return getZero[Route](), fmt.Errorf("%w: %s", ErrPathParams, err)
}
@@ -260,7 +262,7 @@ func (r Router[_, _]) addContentToOASSchema(content Content) (openapi3.Content,
return oasContent, nil
}
func getPathParamsAutofilled(schema Definitions, path string) ParameterValue {
func getPathParamsAutoComplete(schema Definitions, path string) ParameterValue {
if schema.PathParams == nil {
pathParams := strings.Split(path, "/")
for _, param := range pathParams {

View File

@@ -25,6 +25,10 @@ func (r echoRouter) SwaggerHandler(contentType string, blob []byte) echo.Handler
}
}
func (r echoRouter) TransformPathToOasPath(path string) string {
return apirouter.TransformPathParamsWithColon(path)
}
func NewRouter(router *echo.Echo) apirouter.Router[echo.HandlerFunc, Route] {
return echoRouter{
router: router,

View File

@@ -5,6 +5,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"os"
"testing"
oasEcho "github.com/davidebianchi/gswagger/support/echo"
@@ -22,19 +23,36 @@ const (
type echoSwaggerRouter = swagger.Router[echo.HandlerFunc, *echo.Route]
func TestIntegration(t *testing.T) {
func TestEchoIntegration(t *testing.T) {
t.Run("router works correctly - echo", func(t *testing.T) {
echoRouter, _ := setupEchoSwagger(t)
echoRouter, oasRouter := setupEchoSwagger(t)
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
err := oasRouter.GenerateAndExposeOpenapi()
require.NoError(t, err)
echoRouter.ServeHTTP(w, r)
t.Run("/hello", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
echoRouter.ServeHTTP(w, r)
body := readBody(t, w.Result().Body)
require.Equal(t, "OK", body)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
body := readBody(t, w.Result().Body)
require.Equal(t, "OK", body)
})
t.Run("/hello/:value", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodPost, "/hello/something", nil)
echoRouter.ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
body := readBody(t, w.Result().Body)
require.Equal(t, "OK", body)
})
t.Run("and generate swagger", func(t *testing.T) {
w := httptest.NewRecorder()
@@ -45,14 +63,14 @@ func TestIntegration(t *testing.T) {
require.Equal(t, http.StatusOK, w.Result().StatusCode)
body := readBody(t, w.Result().Body)
require.Equal(t, "{\"components\":{},\"info\":{\"title\":\"test swagger title\",\"version\":\"test swagger version\"},\"openapi\":\"3.0.0\",\"paths\":{\"/hello\":{\"get\":{\"responses\":{\"default\":{\"description\":\"\"}}}}}}", body)
require.JSONEq(t, readFile(t, "../testdata/integration.json"), body)
})
})
t.Run("works correctly with subrouter - handles path prefix - echo", func(t *testing.T) {
eRouter, swaggerRouter := setupEchoSwagger(t)
eRouter, oasRouter := setupEchoSwagger(t)
subRouter, err := swaggerRouter.SubRouter(oasEcho.NewRouter(eRouter), swagger.SubRouterOptions{
subRouter, err := oasRouter.SubRouter(oasEcho.NewRouter(eRouter), swagger.SubRouterOptions{
PathPrefix: "/prefix",
})
require.NoError(t, err)
@@ -60,15 +78,20 @@ func TestIntegration(t *testing.T) {
_, err = subRouter.AddRoute(http.MethodGet, "/foo", okHandler, swagger.Definitions{})
require.NoError(t, err)
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
err = oasRouter.GenerateAndExposeOpenapi()
require.NoError(t, err)
eRouter.ServeHTTP(w, r)
t.Run("correctly call /hello", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
eRouter.ServeHTTP(w, r)
body := readBody(t, w.Result().Body)
require.Equal(t, "OK", body)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
body := readBody(t, w.Result().Body)
require.Equal(t, "OK", body)
})
t.Run("correctly call sub router", func(t *testing.T) {
w := httptest.NewRecorder()
@@ -91,7 +114,7 @@ func TestIntegration(t *testing.T) {
require.Equal(t, http.StatusOK, w.Result().StatusCode)
body := readBody(t, w.Result().Body)
require.Equal(t, "{\"components\":{},\"info\":{\"title\":\"test swagger title\",\"version\":\"test swagger version\"},\"openapi\":\"3.0.0\",\"paths\":{\"/hello\":{\"get\":{\"responses\":{\"default\":{\"description\":\"\"}}}}}}", body)
require.JSONEq(t, readFile(t, "../testdata/intergation-subrouter.json"), body, body)
})
})
}
@@ -127,7 +150,7 @@ func setupEchoSwagger(t *testing.T) (*echo.Echo, *echoSwaggerRouter) {
_, err = router.AddRawRoute(http.MethodGet, "/hello", okHandler, operation)
require.NoError(t, err)
err = router.GenerateAndExposeOpenapi()
_, err = router.AddRoute(http.MethodPost, "/hello/:value", okHandler, swagger.Definitions{})
require.NoError(t, err)
return e, router
@@ -136,3 +159,12 @@ func setupEchoSwagger(t *testing.T) (*echo.Echo, *echoSwaggerRouter) {
func okHandler(c echo.Context) error {
return c.String(http.StatusOK, "OK")
}
func readFile(t *testing.T, path string) string {
t.Helper()
fileContent, err := os.ReadFile(path)
require.NoError(t, err)
return string(fileContent)
}

View File

@@ -28,3 +28,7 @@ func (r fiberRouter) SwaggerHandler(contentType string, blob []byte) HandlerFunc
return c.Send(blob)
}
}
func (r fiberRouter) TransformPathToOasPath(path string) string {
return apirouter.TransformPathParamsWithColon(path)
}

View File

@@ -5,6 +5,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"os"
"testing"
swagger "github.com/davidebianchi/gswagger"
@@ -22,18 +23,34 @@ const (
swaggerOpenapiVersion = "test swagger version"
)
func TestWithFiber(t *testing.T) {
func TestFiberIntegration(t *testing.T) {
t.Run("router works correctly", func(t *testing.T) {
router, _ := setupSwagger(t)
router, oasRouter := setupSwagger(t)
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
resp, err := router.Test(r)
err := oasRouter.GenerateAndExposeOpenapi()
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
body := readBody(t, resp.Body)
require.Equal(t, "OK", body)
t.Run("/hello", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
resp, err := router.Test(r)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
body := readBody(t, resp.Body)
require.Equal(t, "OK", body)
})
t.Run("/hello/:value", func(t *testing.T) {
r := httptest.NewRequest(http.MethodPost, "/hello/something", nil)
resp, err := router.Test(r)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
body := readBody(t, resp.Body)
require.Equal(t, "OK", body)
})
t.Run("and generate swagger", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, swagger.DefaultJSONDocumentationPath, nil)
@@ -43,36 +60,26 @@ func TestWithFiber(t *testing.T) {
require.Equal(t, http.StatusOK, resp.StatusCode)
body := readBody(t, resp.Body)
require.Equal(t, "{\"components\":{},\"info\":{\"title\":\"test swagger title\",\"version\":\"test swagger version\"},\"openapi\":\"3.0.0\",\"paths\":{\"/hello\":{\"get\":{\"responses\":{\"default\":{\"description\":\"\"}}}}}}", body)
require.JSONEq(t, readFile(t, "../testdata/integration.json"), body, body)
})
})
t.Run("works correctly with subrouter - handles path prefix - gorilla mux", func(t *testing.T) {
fiberRouter, oasRouter := setupSwagger(t)
fiberRouter.Route("/foo", func(router fiber.Router) {
subRouter, err := oasRouter.SubRouter(oasFiber.NewRouter(router), swagger.SubRouterOptions{
PathPrefix: "/prefix",
})
require.NoError(t, err)
_, err = subRouter.AddRoute(http.MethodGet, "/nested", okHandler, swagger.Definitions{})
require.NoError(t, err)
subRouter, err := oasRouter.SubRouter(oasFiber.NewRouter(fiberRouter), swagger.SubRouterOptions{
PathPrefix: "/prefix",
})
oasRouter.AddRoute(http.MethodGet, "/foo", okHandler, swagger.Definitions{})
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
resp, err := fiberRouter.Test(r)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
body := readBody(t, resp.Body)
require.Equal(t, "OK", body)
_, err = subRouter.AddRoute(http.MethodGet, "/foo", okHandler, swagger.Definitions{})
require.NoError(t, err)
t.Run("correctly call router", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/foo", nil)
err = oasRouter.GenerateAndExposeOpenapi()
require.NoError(t, err)
t.Run("correctly call /hello", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
resp, err := fiberRouter.Test(r)
require.NoError(t, err)
@@ -83,7 +90,7 @@ func TestWithFiber(t *testing.T) {
})
t.Run("correctly call sub router", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/foo/prefix/nested", nil)
r := httptest.NewRequest(http.MethodGet, "/prefix/foo", nil)
resp, err := fiberRouter.Test(r)
require.NoError(t, err)
@@ -101,7 +108,7 @@ func TestWithFiber(t *testing.T) {
require.Equal(t, http.StatusOK, resp.StatusCode)
body := readBody(t, resp.Body)
require.Equal(t, "{\"components\":{},\"info\":{\"title\":\"test swagger title\",\"version\":\"test swagger version\"},\"openapi\":\"3.0.0\",\"paths\":{\"/hello\":{\"get\":{\"responses\":{\"default\":{\"description\":\"\"}}}}}}", body)
require.JSONEq(t, readFile(t, "../testdata/intergation-subrouter.json"), body, body)
})
})
}
@@ -128,7 +135,7 @@ func setupSwagger(t *testing.T) (*fiber.App, *SwaggerRouter) {
_, err = router.AddRawRoute(http.MethodGet, "/hello", okHandler, operation)
require.NoError(t, err)
err = router.GenerateAndExposeOpenapi()
_, err = router.AddRoute(http.MethodPost, "/hello/:value", okHandler, swagger.Definitions{})
require.NoError(t, err)
return fiberRouter, router
@@ -147,3 +154,12 @@ func readBody(t *testing.T, requestBody io.ReadCloser) string {
return string(body)
}
func readFile(t *testing.T, path string) string {
t.Helper()
fileContent, err := os.ReadFile(path)
require.NoError(t, err)
return string(fileContent)
}

View File

@@ -28,6 +28,10 @@ func (r gorillaRouter) SwaggerHandler(contentType string, blob []byte) HandlerFu
}
}
func (r gorillaRouter) TransformPathToOasPath(path string) string {
return path
}
func NewRouter(router *mux.Router) apirouter.Router[HandlerFunc, Route] {
return gorillaRouter{
router: router,

View File

@@ -5,6 +5,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"os"
"testing"
swagger "github.com/davidebianchi/gswagger"
@@ -23,7 +24,10 @@ type SwaggerRouter = swagger.Router[gorilla.HandlerFunc, gorilla.Route]
func TestGorillaIntegration(t *testing.T) {
t.Run("router works correctly", func(t *testing.T) {
muxRouter, _ := setupSwagger(t)
muxRouter, oasRouter := setupSwagger(t)
err := oasRouter.GenerateAndExposeOpenapi()
require.NoError(t, err)
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
@@ -44,15 +48,15 @@ func TestGorillaIntegration(t *testing.T) {
require.Equal(t, http.StatusOK, w.Result().StatusCode)
body := readBody(t, w.Result().Body)
require.Equal(t, "{\"components\":{},\"info\":{\"title\":\"test swagger title\",\"version\":\"test swagger version\"},\"openapi\":\"3.0.0\",\"paths\":{\"/hello\":{\"get\":{\"responses\":{\"default\":{\"description\":\"\"}}}}}}", body)
require.JSONEq(t, readFile(t, "../testdata/integration.json"), body)
})
})
t.Run("works correctly with subrouter - handles path prefix - gorilla mux", func(t *testing.T) {
muxRouter, swaggerRouter := setupSwagger(t)
muxRouter, oasRouter := setupSwagger(t)
muxSubRouter := muxRouter.NewRoute().Subrouter()
subRouter, err := swaggerRouter.SubRouter(gorilla.NewRouter(muxSubRouter), swagger.SubRouterOptions{
subRouter, err := oasRouter.SubRouter(gorilla.NewRouter(muxSubRouter), swagger.SubRouterOptions{
PathPrefix: "/prefix",
})
require.NoError(t, err)
@@ -60,15 +64,20 @@ func TestGorillaIntegration(t *testing.T) {
_, err = subRouter.AddRoute(http.MethodGet, "/foo", okHandler, swagger.Definitions{})
require.NoError(t, err)
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
err = oasRouter.GenerateAndExposeOpenapi()
require.NoError(t, err)
muxRouter.ServeHTTP(w, r)
t.Run("correctly call router", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/hello", nil)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
muxRouter.ServeHTTP(w, r)
body := readBody(t, w.Result().Body)
require.Equal(t, "OK", body)
require.Equal(t, http.StatusOK, w.Result().StatusCode)
body := readBody(t, w.Result().Body)
require.Equal(t, "OK", body)
})
t.Run("correctly call sub router", func(t *testing.T) {
w := httptest.NewRecorder()
@@ -91,7 +100,7 @@ func TestGorillaIntegration(t *testing.T) {
require.Equal(t, http.StatusOK, w.Result().StatusCode)
body := readBody(t, w.Result().Body)
require.Equal(t, "{\"components\":{},\"info\":{\"title\":\"test swagger title\",\"version\":\"test swagger version\"},\"openapi\":\"3.0.0\",\"paths\":{\"/hello\":{\"get\":{\"responses\":{\"default\":{\"description\":\"\"}}}}}}", body)
require.JSONEq(t, readFile(t, "../testdata/intergation-subrouter.json"), body, body)
})
})
}
@@ -127,7 +136,7 @@ func setupSwagger(t *testing.T) (*mux.Router, *SwaggerRouter) {
_, err = router.AddRawRoute(http.MethodGet, "/hello", okHandler, operation)
require.NoError(t, err)
err = router.GenerateAndExposeOpenapi()
_, err = router.AddRoute(http.MethodPost, "/hello/{value}", okHandler, swagger.Definitions{})
require.NoError(t, err)
return muxRouter, router
@@ -137,3 +146,12 @@ func okHandler(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`OK`))
}
func readFile(t *testing.T, path string) string {
t.Helper()
fileContent, err := os.ReadFile(path)
require.NoError(t, err)
return string(fileContent)
}

38
support/testdata/integration.json vendored Normal file
View File

@@ -0,0 +1,38 @@
{
"components": {},
"info": {
"title": "test swagger title",
"version": "test swagger version"
},
"openapi": "3.0.0",
"paths": {
"/hello": {
"get": {
"responses": {
"default": {
"description": ""
}
}
}
},
"/hello/{value}": {
"post": {
"parameters": [
{
"in": "path",
"name": "value",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"default": {
"description": ""
}
}
}
}
}
}

View File

@@ -0,0 +1,47 @@
{
"components": {},
"info": {
"title": "test swagger title",
"version": "test swagger version"
},
"openapi": "3.0.0",
"paths": {
"/hello": {
"get": {
"responses": {
"default": {
"description": ""
}
}
}
},
"/hello/{value}": {
"post": {
"parameters": [
{
"in": "path",
"name": "value",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"default": {
"description": ""
}
}
}
},
"/prefix/foo": {
"get": {
"responses": {
"default": {
"description": ""
}
}
}
}
}
}