feat: add strong types on Route

This commit is contained in:
Davide Bianchi
2022-12-22 15:35:56 +01:00
parent aeb5680612
commit 76d2e514e1
11 changed files with 104 additions and 45 deletions

View File

@@ -1,8 +1,6 @@
package apirouter
type Router[HandlerFunc any] interface {
type Router[HandlerFunc any, Route any] interface {
AddRoute(method string, path string, handler HandlerFunc) Route
SwaggerHandler(contentType string, blob []byte) HandlerFunc
}
type Route any

View File

@@ -21,8 +21,8 @@ const (
swaggerOpenapiVersion = "test swagger version"
)
type GorillaSwaggerRouter = swagger.Router[gorilla.HandlerFunc]
type echoSwaggerRouter = swagger.Router[http.HandlerFunc]
type GorillaSwaggerRouter = swagger.Router[gorilla.HandlerFunc, gorilla.Route]
type echoSwaggerRouter = swagger.Router[http.HandlerFunc, *echo.Route]
func TestIntegration(t *testing.T) {
t.Run("router works correctly", func(t *testing.T) {
@@ -248,7 +248,7 @@ func echoOkHandler(c echo.Context) error {
return c.String(http.StatusOK, "OK")
}
func newEchoRouter(e *echo.Echo) apirouter.Router[http.HandlerFunc] {
func newEchoRouter(e *echo.Echo) apirouter.Router[http.HandlerFunc, *echo.Route] {
return echoRouter{router: e}
}
@@ -256,7 +256,7 @@ type echoRouter struct {
router *echo.Echo
}
func (r echoRouter) AddRoute(method, path string, handler http.HandlerFunc) apirouter.Route {
func (r echoRouter) AddRoute(method, path string, handler http.HandlerFunc) *echo.Route {
return r.router.Add(method, path, echo.WrapHandler(http.HandlerFunc(handler)))
}

14
main.go
View File

@@ -30,8 +30,8 @@ const (
// Router handle the api router and the swagger schema.
// api router supported out of the box are:
// - gorilla mux
type Router[T any] struct {
router apirouter.Router[T]
type Router[HandlerFunc, Route any] struct {
router apirouter.Router[HandlerFunc, Route]
swaggerSchema *openapi3.T
context context.Context
jsonDocumentationPath string
@@ -52,7 +52,7 @@ type Options struct {
}
// NewRouter generate new router with swagger. Default to OpenAPI 3.0.0
func NewRouter[T any](router apirouter.Router[T], options Options) (*Router[T], error) {
func NewRouter[HandlerFunc, Route any](router apirouter.Router[HandlerFunc, Route], options Options) (*Router[HandlerFunc, Route], error) {
swagger, err := generateNewValidSwagger(options.Openapi)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrValidatingSwagger, err)
@@ -79,7 +79,7 @@ func NewRouter[T any](router apirouter.Router[T], options Options) (*Router[T],
jsonDocumentationPath = options.JSONDocumentationPath
}
return &Router[T]{
return &Router[HandlerFunc, Route]{
router: router,
swaggerSchema: swagger,
context: ctx,
@@ -93,8 +93,8 @@ type SubRouterOptions struct {
PathPrefix string
}
func (r Router[T]) SubRouter(router apirouter.Router[T], opts SubRouterOptions) (*Router[T], error) {
return &Router[T]{
func (r Router[HandlerFunc, Route]) SubRouter(router apirouter.Router[HandlerFunc, Route], opts SubRouterOptions) (*Router[HandlerFunc, Route], error) {
return &Router[HandlerFunc, Route]{
router: router,
swaggerSchema: r.swaggerSchema,
context: r.context,
@@ -130,7 +130,7 @@ func generateNewValidSwagger(swagger *openapi3.T) (*openapi3.T, error) {
// GenerateAndExposeSwagger creates a /documentation/json route on router and
// expose the generated swagger
func (r Router[T]) GenerateAndExposeSwagger() error {
func (r Router[_, _]) GenerateAndExposeSwagger() error {
if err := r.swaggerSchema.Validate(r.context); err != nil {
return fmt.Errorf("%w: %s", ErrValidatingSwagger, err)
}

View File

@@ -42,7 +42,7 @@ func TestNewRouter(t *testing.T) {
})
require.NoError(t, err)
require.Equal(t, &Router[gorilla.HandlerFunc]{
require.Equal(t, &Router[gorilla.HandlerFunc, gorilla.Route]{
context: context.Background(),
router: mAPIRouter,
swaggerSchema: openapi,
@@ -60,7 +60,7 @@ func TestNewRouter(t *testing.T) {
})
require.NoError(t, err)
require.Equal(t, &Router[gorilla.HandlerFunc]{
require.Equal(t, &Router[gorilla.HandlerFunc, gorilla.Route]{
context: ctx,
router: mAPIRouter,
swaggerSchema: openapi,
@@ -80,7 +80,7 @@ func TestNewRouter(t *testing.T) {
})
require.NoError(t, err)
require.Equal(t, &Router[gorilla.HandlerFunc]{
require.Equal(t, &Router[gorilla.HandlerFunc, gorilla.Route]{
context: ctx,
router: mAPIRouter,
swaggerSchema: openapi,

View File

@@ -24,12 +24,12 @@ var (
// AddRawRoute add route to router with specific method, path and handler. Add the
// router also to the swagger schema, after validating it
func (r Router[HandlerFunc]) AddRawRoute(method string, routePath string, handler HandlerFunc, operation Operation) (interface{}, error) {
func (r Router[HandlerFunc, Route]) AddRawRoute(method string, routePath string, handler HandlerFunc, operation Operation) (Route, error) {
op := operation.Operation
if op != nil {
err := operation.Validate(r.context)
if err != nil {
return nil, err
return getZero[Route](), err
}
} else {
op = openapi3.NewOperation()
@@ -88,46 +88,46 @@ const (
cookieParamType = "cookie"
)
// AddRoute add a route with json schema inferted by passed schema.
func (r Router[HandlerFunc]) AddRoute(method string, path string, handler HandlerFunc, schema Definitions) (interface{}, error) {
// AddRoute add a route with json schema inferred by passed schema.
func (r Router[HandlerFunc, Route]) AddRoute(method string, path string, handler HandlerFunc, schema Definitions) (Route, error) {
operation := NewOperation()
operation.Responses = make(openapi3.Responses)
operation.Tags = schema.Tags
err := r.resolveRequestBodySchema(schema.RequestBody, operation)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrRequestBody, err)
return getZero[Route](), fmt.Errorf("%w: %s", ErrRequestBody, err)
}
err = r.resolveResponsesSchema(schema.Responses, operation)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrResponses, err)
return getZero[Route](), fmt.Errorf("%w: %s", ErrResponses, err)
}
err = r.resolveParameterSchema(pathParamsType, getPathParamsAutofilled(schema, path), operation)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrPathParams, err)
return getZero[Route](), fmt.Errorf("%w: %s", ErrPathParams, err)
}
err = r.resolveParameterSchema(queryParamType, schema.Querystring, operation)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrPathParams, err)
return getZero[Route](), fmt.Errorf("%w: %s", ErrPathParams, err)
}
err = r.resolveParameterSchema(headerParamType, schema.Headers, operation)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrPathParams, err)
return getZero[Route](), fmt.Errorf("%w: %s", ErrPathParams, err)
}
err = r.resolveParameterSchema(cookieParamType, schema.Cookies, operation)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrPathParams, err)
return getZero[Route](), fmt.Errorf("%w: %s", ErrPathParams, err)
}
return r.AddRawRoute(method, path, handler, operation)
}
func (r Router[_]) getSchemaFromInterface(v interface{}, allowAdditionalProperties bool) (*openapi3.Schema, error) {
func (r Router[_, _]) getSchemaFromInterface(v interface{}, allowAdditionalProperties bool) (*openapi3.Schema, error) {
if v == nil {
return &openapi3.Schema{}, nil
}
@@ -158,7 +158,7 @@ func (r Router[_]) getSchemaFromInterface(v interface{}, allowAdditionalProperti
return schema, nil
}
func (r Router[_]) resolveRequestBodySchema(bodySchema *ContentValue, operation Operation) error {
func (r Router[_, _]) resolveRequestBodySchema(bodySchema *ContentValue, operation Operation) error {
if bodySchema == nil {
return nil
}
@@ -177,7 +177,7 @@ func (r Router[_]) resolveRequestBodySchema(bodySchema *ContentValue, operation
return nil
}
func (r Router[_]) resolveResponsesSchema(responses map[int]ContentValue, operation Operation) error {
func (r Router[_, _]) resolveResponsesSchema(responses map[int]ContentValue, operation Operation) error {
if responses == nil {
operation.Responses = openapi3.NewResponses()
}
@@ -196,7 +196,7 @@ func (r Router[_]) resolveResponsesSchema(responses map[int]ContentValue, operat
return nil
}
func (r Router[_]) resolveParameterSchema(paramType string, paramConfig ParameterValue, operation Operation) error {
func (r Router[_, _]) resolveParameterSchema(paramType string, paramConfig ParameterValue, operation Operation) error {
var keys = make([]string, 0, len(paramConfig))
for k := range paramConfig {
keys = append(keys, k)
@@ -247,7 +247,7 @@ func (r Router[_]) resolveParameterSchema(paramType string, paramConfig Paramete
return nil
}
func (r Router[_]) addContentToOASSchema(content Content) (openapi3.Content, error) {
func (r Router[_, _]) addContentToOASSchema(content Content) (openapi3.Content, error) {
oasContent := openapi3.NewContent()
for k, v := range content {
var err error
@@ -278,3 +278,8 @@ func getPathParamsAutofilled(schema Definitions, path string) ParameterValue {
}
return schema.PathParams
}
func getZero[T any]() T {
var result T
return result
}

View File

@@ -17,7 +17,7 @@ import (
const jsonType = "application/json"
const formDataType = "multipart/form-data"
type TestRouter = Router[gorilla.HandlerFunc]
type TestRouter = Router[gorilla.HandlerFunc, gorilla.Route]
func TestAddRoutes(t *testing.T) {

View File

@@ -6,18 +6,19 @@ import (
)
type HandlerFunc = fiber.Handler
type Route = fiber.Router
type fiberRouter struct {
router fiber.Router
}
func NewRouter(router fiber.Router) apirouter.Router[HandlerFunc] {
func NewRouter(router fiber.Router) apirouter.Router[HandlerFunc, Route] {
return fiberRouter{
router: router,
}
}
func (r fiberRouter) AddRoute(method string, path string, handler HandlerFunc) apirouter.Route {
func (r fiberRouter) AddRoute(method string, path string, handler HandlerFunc) Route {
return r.router.Add(method, path, handler)
}

View File

@@ -16,16 +16,14 @@ func TestFiberRouterSupport(t *testing.T) {
ar := NewRouter(fiberRouter)
t.Run("create a new api router", func(t *testing.T) {
require.Implements(t, (*apirouter.Router[HandlerFunc])(nil), ar)
require.Implements(t, (*apirouter.Router[HandlerFunc, Route])(nil), ar)
})
t.Run("add new route", func(t *testing.T) {
route := ar.AddRoute(http.MethodGet, "/foo", func(c *fiber.Ctx) error {
return c.SendStatus(http.StatusOK)
})
_, ok := route.(fiber.Router)
require.True(t, ok)
require.IsType(t, route, fiber.New())
t.Run("router exposes correctly api", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/foo", nil)

View File

@@ -0,0 +1,58 @@
package fiber_test
import (
"context"
"net/http"
"testing"
swagger "github.com/davidebianchi/gswagger"
oasFiber "github.com/davidebianchi/gswagger/support/fiber"
"github.com/getkin/kin-openapi/openapi3"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/require"
)
type SwaggerRouter = swagger.Router[oasFiber.HandlerFunc, oasFiber.Route]
const (
swaggerOpenapiTitle = "test swagger title"
swaggerOpenapiVersion = "test swagger version"
)
func TestWithFiber(t *testing.T) {
require.True(t, true)
}
func setupSwagger(t *testing.T) (*fiber.App, *SwaggerRouter) {
t.Helper()
context := context.Background()
fiberRouter := fiber.New()
router, err := swagger.NewRouter(oasFiber.NewRouter(fiberRouter), swagger.Options{
Context: context,
Openapi: &openapi3.T{
Info: &openapi3.Info{
Title: swaggerOpenapiTitle,
Version: swaggerOpenapiVersion,
},
},
})
require.NoError(t, err)
operation := swagger.Operation{}
_, err = router.AddRawRoute(http.MethodGet, "/hello", okHandler, operation)
require.NoError(t, err)
err = router.GenerateAndExposeSwagger()
require.NoError(t, err)
return fiberRouter, router
}
func okHandler(c *fiber.Ctx) error {
c.Status(http.StatusOK)
return c.SendString("OK")
}

View File

@@ -10,12 +10,13 @@ import (
// HandlerFunc is the http type handler used by gorilla/mux
type HandlerFunc func(w http.ResponseWriter, req *http.Request)
type Route = *mux.Route
type gorillaRouter struct {
router *mux.Router
}
func (r gorillaRouter) AddRoute(method string, path string, handler HandlerFunc) apirouter.Route {
func (r gorillaRouter) AddRoute(method string, path string, handler HandlerFunc) Route {
return r.router.HandleFunc(path, handler).Methods(method)
}
@@ -27,7 +28,7 @@ func (r gorillaRouter) SwaggerHandler(contentType string, blob []byte) HandlerFu
}
}
func NewRouter(router *mux.Router) apirouter.Router[HandlerFunc] {
func NewRouter(router *mux.Router) apirouter.Router[HandlerFunc, Route] {
return gorillaRouter{
router: router,
}

View File

@@ -16,7 +16,7 @@ func TestGorillaMuxRouter(t *testing.T) {
ar := NewRouter(muxRouter)
t.Run("create a new api router", func(t *testing.T) {
require.Implements(t, (*apirouter.Router[HandlerFunc])(nil), ar)
require.Implements(t, (*apirouter.Router[HandlerFunc, Route])(nil), ar)
})
t.Run("add new route", func(t *testing.T) {
@@ -24,9 +24,7 @@ func TestGorillaMuxRouter(t *testing.T) {
w.WriteHeader(200)
w.Write(nil)
})
_, ok := route.(*mux.Route)
require.True(t, ok)
require.IsType(t, route, &mux.Route{})
t.Run("router exposes correctly api", func(t *testing.T) {
w := httptest.NewRecorder()