mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
new feature: versioned controllers
Former-commit-id: c797e23c78b1e74bbe9ba56673f3a98f17f5e2f7
This commit is contained in:
parent
3f98b39632
commit
311b560717
|
@ -371,13 +371,17 @@ Other Improvements:
|
||||||
|
|
||||||
![DBUG routes](https://iris-go.com/images/v12.2.0-dbug2.png?v=0)
|
![DBUG routes](https://iris-go.com/images/v12.2.0-dbug2.png?v=0)
|
||||||
|
|
||||||
- New [rollbar example](https://github.com/kataras/iris/tree/master/_examples/logging/rollbar/main.go).
|
- Versioned Controllers feature through the new `mvc.Version` option. See [_examples/mvc/versioned-controller](https://github.com/kataras/iris/blob/master/_examples/mvc/versioned-controller/main.go).
|
||||||
|
|
||||||
|
- Fix [#1539](https://github.com/kataras/iris/issues/1539).
|
||||||
|
|
||||||
|
- New [rollbar example](https://github.com/kataras/iris/blob/master/_examples/logging/rollbar/main.go).
|
||||||
|
|
||||||
- New builtin [requestid](https://github.com/kataras/iris/tree/master/middleware/requestid) middleware.
|
- New builtin [requestid](https://github.com/kataras/iris/tree/master/middleware/requestid) middleware.
|
||||||
|
|
||||||
- New builtin [JWT](https://github.com/kataras/iris/tree/master/middleware/jwt) middleware based on [square/go-jose](https://github.com/square/go-jose) featured with optional encryption to set claims with sensitive data when necessary.
|
- New builtin [JWT](https://github.com/kataras/iris/tree/master/middleware/jwt) middleware based on [square/go-jose](https://github.com/square/go-jose) featured with optional encryption to set claims with sensitive data when necessary.
|
||||||
|
|
||||||
- New `iris.RouteOverlap` route registration rule. `Party.SetRegisterRule(iris.RouteOverlap)` to allow overlapping across multiple routes for the same request subdomain, method, path. See [1536#issuecomment-643719922](https://github.com/kataras/iris/issues/1536#issuecomment-643719922).
|
- New `iris.RouteOverlap` route registration rule. `Party.SetRegisterRule(iris.RouteOverlap)` to allow overlapping across multiple routes for the same request subdomain, method, path. See [1536#issuecomment-643719922](https://github.com/kataras/iris/issues/1536#issuecomment-643719922). This allows two or more **MVC Controllers** to listen on the same path based on one or more registered dependencies (see [_examples/mvc/authenticated-controller](https://github.com/kataras/iris/tree/master/_examples/mvc/authenticated-controller)).
|
||||||
|
|
||||||
- `Context.ReadForm` now can return an `iris.ErrEmptyForm` instead of `nil` when the new `Configuration.FireEmptyFormError` is true (when `iris.WithEmptyFormError` is set) on missing form body to read from.
|
- `Context.ReadForm` now can return an `iris.ErrEmptyForm` instead of `nil` when the new `Configuration.FireEmptyFormError` is true (when `iris.WithEmptyFormError` is set) on missing form body to read from.
|
||||||
|
|
||||||
|
|
|
@ -190,6 +190,7 @@
|
||||||
* [Regexp](mvc/regexp/main.go)
|
* [Regexp](mvc/regexp/main.go)
|
||||||
* [Session Controller](mvc/session-controller/main.go)
|
* [Session Controller](mvc/session-controller/main.go)
|
||||||
* [Authenticated Controller](mvc/authenticated-controller/main.go)
|
* [Authenticated Controller](mvc/authenticated-controller/main.go)
|
||||||
|
* [Versioned Controller](mvc/versioned-controller/main.go)
|
||||||
* [Websocket Controller](mvc/websocket)
|
* [Websocket Controller](mvc/websocket)
|
||||||
* [Register Middleware](mvc/middleware)
|
* [Register Middleware](mvc/middleware)
|
||||||
* [gRPC](mvc/grpc-compatible)
|
* [gRPC](mvc/grpc-compatible)
|
||||||
|
|
52
_examples/mvc/versioned-controller/main.go
Normal file
52
_examples/mvc/versioned-controller/main.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kataras/iris/v12"
|
||||||
|
"github.com/kataras/iris/v12/mvc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := newApp()
|
||||||
|
|
||||||
|
// See main_test.go for request examples.
|
||||||
|
app.Listen(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newApp() *iris.Application {
|
||||||
|
app := iris.New()
|
||||||
|
|
||||||
|
dataRouter := app.Party("/data")
|
||||||
|
{
|
||||||
|
m := mvc.New(dataRouter)
|
||||||
|
m.Handle(new(v1Controller), mvc.Version("1")) // 1 or 1.0, 1.0.0 ...
|
||||||
|
m.Handle(new(v2Controller), mvc.Version("2.3")) // 2.3 or 2.3.0
|
||||||
|
m.Handle(new(v3Controller), mvc.Version(">=3, <4")) // 3, 3.x, 3.x.x ...
|
||||||
|
m.Handle(new(noVersionController))
|
||||||
|
}
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
type v1Controller struct{}
|
||||||
|
|
||||||
|
func (c *v1Controller) Get() string {
|
||||||
|
return "data (v1.x)"
|
||||||
|
}
|
||||||
|
|
||||||
|
type v2Controller struct{}
|
||||||
|
|
||||||
|
func (c *v2Controller) Get() string {
|
||||||
|
return "data (v2.x)"
|
||||||
|
}
|
||||||
|
|
||||||
|
type v3Controller struct{}
|
||||||
|
|
||||||
|
func (c *v3Controller) Get() string {
|
||||||
|
return "data (v3.x)"
|
||||||
|
}
|
||||||
|
|
||||||
|
type noVersionController struct{}
|
||||||
|
|
||||||
|
func (c *noVersionController) Get() string {
|
||||||
|
return "data"
|
||||||
|
}
|
26
_examples/mvc/versioned-controller/main_test.go
Normal file
26
_examples/mvc/versioned-controller/main_test.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12"
|
||||||
|
"github.com/kataras/iris/v12/httptest"
|
||||||
|
"github.com/kataras/iris/v12/versioning"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVersionedController(t *testing.T) {
|
||||||
|
app := newApp()
|
||||||
|
|
||||||
|
e := httptest.New(t, app)
|
||||||
|
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "1").Expect().
|
||||||
|
Status(iris.StatusOK).Body().Equal("data (v1.x)")
|
||||||
|
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "2.3.0").Expect().
|
||||||
|
Status(iris.StatusOK).Body().Equal("data (v2.x)")
|
||||||
|
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "3.1").Expect().
|
||||||
|
Status(iris.StatusOK).Body().Equal("data (v3.x)")
|
||||||
|
// Test invalid version or no version at all.
|
||||||
|
e.GET("/data").WithHeader(versioning.AcceptVersionHeaderKey, "4").Expect().
|
||||||
|
Status(iris.StatusOK).Body().Equal("data")
|
||||||
|
e.GET("/data").Expect().
|
||||||
|
Status(iris.StatusOK).Body().Equal("data")
|
||||||
|
}
|
|
@ -14,14 +14,14 @@ func TestSessionsEncodeDecode(t *testing.T) {
|
||||||
es := e.GET("/set").Expect()
|
es := e.GET("/set").Expect()
|
||||||
es.Status(iris.StatusOK)
|
es.Status(iris.StatusOK)
|
||||||
es.Cookies().NotEmpty()
|
es.Cookies().NotEmpty()
|
||||||
es.Body().Equal("All ok session set to: iris")
|
es.Body().Equal("All ok session set to: iris [isNew=true]")
|
||||||
|
|
||||||
e.GET("/get").Expect().Status(iris.StatusOK).Body().Equal("The name on the /set was: iris")
|
e.GET("/get").Expect().Status(iris.StatusOK).Body().Equal("The name on the /set was: iris")
|
||||||
// delete and re-get
|
// delete and re-get
|
||||||
e.GET("/delete").Expect().Status(iris.StatusOK)
|
e.GET("/delete").Expect().Status(iris.StatusOK)
|
||||||
e.GET("/get").Expect().Status(iris.StatusOK).Body().Equal("The name on the /set was: ")
|
e.GET("/get").Expect().Status(iris.StatusOK).Body().Equal("The name on the /set was: ")
|
||||||
// set, clear and re-get
|
// set, clear and re-get
|
||||||
e.GET("/set").Expect().Body().Equal("All ok session set to: iris")
|
e.GET("/set").Expect().Body().Equal("All ok session set to: iris [isNew=false]")
|
||||||
e.GET("/clear").Expect().Status(iris.StatusOK)
|
e.GET("/clear").Expect().Status(iris.StatusOK)
|
||||||
e.GET("/get").Expect().Status(iris.StatusOK).Body().Equal("The name on the /set was: ")
|
e.GET("/get").Expect().Status(iris.StatusOK).Body().Equal("The name on the /set was: ")
|
||||||
}
|
}
|
||||||
|
|
|
@ -283,3 +283,24 @@ func NewConditionalHandler(filter Filter, handlers ...Handler) Handler {
|
||||||
ctx.Next()
|
ctx.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JoinHandlers returns a copy of "h1" and "h2" Handlers slice joined as one slice of Handlers.
|
||||||
|
func JoinHandlers(h1 Handlers, h2 Handlers) Handlers {
|
||||||
|
if len(h1) == 0 {
|
||||||
|
return h2
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(h2) == 0 {
|
||||||
|
return h1
|
||||||
|
}
|
||||||
|
|
||||||
|
nowLen := len(h1)
|
||||||
|
totalLen := nowLen + len(h2)
|
||||||
|
// create a new slice of Handlers in order to merge the "h1" and "h2"
|
||||||
|
newHandlers := make(Handlers, totalLen)
|
||||||
|
// copy the already Handlers to the just created
|
||||||
|
copy(newHandlers, h1)
|
||||||
|
// start from there we finish, and store the new Handlers too
|
||||||
|
copy(newHandlers[nowLen:], h2)
|
||||||
|
return newHandlers
|
||||||
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ func (repo *repository) register(route *Route, rule RouteRegisterRule) (*Route,
|
||||||
|
|
||||||
var defaultOverlapFilter = func(ctx context.Context) bool {
|
var defaultOverlapFilter = func(ctx context.Context) bool {
|
||||||
if ctx.IsStopped() {
|
if ctx.IsStopped() {
|
||||||
// It's stopped and the response can be overriden by a new handler.
|
// It's stopped and the response can be overridden by a new handler.
|
||||||
rs, ok := ctx.ResponseWriter().(context.ResponseWriterReseter)
|
rs, ok := ctx.ResponseWriter().(context.ResponseWriterReseter)
|
||||||
return ok && rs.Reset()
|
return ok && rs.Reset()
|
||||||
}
|
}
|
||||||
|
@ -509,8 +509,8 @@ func (api *APIBuilder) createRoutes(errorCode int, methods []string, relativePat
|
||||||
)
|
)
|
||||||
|
|
||||||
if errorCode == 0 {
|
if errorCode == 0 {
|
||||||
beginHandlers = joinHandlers(api.middleware, beginHandlers)
|
beginHandlers = context.JoinHandlers(api.middleware, beginHandlers)
|
||||||
doneHandlers = joinHandlers(api.doneHandlers, doneHandlers)
|
doneHandlers = context.JoinHandlers(api.doneHandlers, doneHandlers)
|
||||||
}
|
}
|
||||||
|
|
||||||
mainHandlers := context.Handlers(handlers)
|
mainHandlers := context.Handlers(handlers)
|
||||||
|
@ -528,9 +528,9 @@ func (api *APIBuilder) createRoutes(errorCode int, methods []string, relativePat
|
||||||
|
|
||||||
// global begin handlers -> middleware that are registered before route registration
|
// global begin handlers -> middleware that are registered before route registration
|
||||||
// -> handlers that are passed to this Handle function.
|
// -> handlers that are passed to this Handle function.
|
||||||
routeHandlers := joinHandlers(beginHandlers, mainHandlers)
|
routeHandlers := context.JoinHandlers(beginHandlers, mainHandlers)
|
||||||
// -> done handlers
|
// -> done handlers
|
||||||
routeHandlers = joinHandlers(routeHandlers, doneHandlers)
|
routeHandlers = context.JoinHandlers(routeHandlers, doneHandlers)
|
||||||
|
|
||||||
// here we separate the subdomain and relative path
|
// here we separate the subdomain and relative path
|
||||||
subdomain, path := splitSubdomainAndPath(fullpath)
|
subdomain, path := splitSubdomainAndPath(fullpath)
|
||||||
|
@ -618,7 +618,7 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P
|
||||||
|
|
||||||
fullpath := parentPath + relativePath
|
fullpath := parentPath + relativePath
|
||||||
// append the parent's + child's handlers
|
// append the parent's + child's handlers
|
||||||
middleware := joinHandlers(api.middleware, handlers)
|
middleware := context.JoinHandlers(api.middleware, handlers)
|
||||||
|
|
||||||
// the allow methods per party and its children.
|
// the allow methods per party and its children.
|
||||||
allowMethods := make([]string, len(api.allowMethods))
|
allowMethods := make([]string, len(api.allowMethods))
|
||||||
|
@ -1060,19 +1060,6 @@ func (api *APIBuilder) Layout(tmplLayoutFile string) Party {
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
// joinHandlers uses to create a copy of all Handlers and return them in order to use inside the node
|
|
||||||
func joinHandlers(h1 context.Handlers, h2 context.Handlers) context.Handlers {
|
|
||||||
nowLen := len(h1)
|
|
||||||
totalLen := nowLen + len(h2)
|
|
||||||
// create a new slice of Handlers in order to merge the "h1" and "h2"
|
|
||||||
newHandlers := make(context.Handlers, totalLen)
|
|
||||||
// copy the already Handlers to the just created
|
|
||||||
copy(newHandlers, h1)
|
|
||||||
// start from there we finish, and store the new Handlers too
|
|
||||||
copy(newHandlers[nowLen:], h2)
|
|
||||||
return newHandlers
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://golang.org/doc/go1.9#callersframes
|
// https://golang.org/doc/go1.9#callersframes
|
||||||
func getCaller() (string, int) {
|
func getCaller() (string, int) {
|
||||||
var pcs [32]uintptr
|
var pcs [32]uintptr
|
||||||
|
|
|
@ -271,6 +271,7 @@ func dispatchFuncResult(ctx context.Context, values []reflect.Value, handler Res
|
||||||
contentType = value
|
contentType = value
|
||||||
} else {
|
} else {
|
||||||
// otherwise is content
|
// otherwise is content
|
||||||
|
contentType = context.ContentTextHeaderValue
|
||||||
content = []byte(value)
|
content = []byte(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,12 @@ type ControllerActivator struct {
|
||||||
// End-devs can change some properties of the *Route on the `BeforeActivator` by using the
|
// End-devs can change some properties of the *Route on the `BeforeActivator` by using the
|
||||||
// `GetRoute/GetRoutes(functionName)`.
|
// `GetRoute/GetRoutes(functionName)`.
|
||||||
routes map[string][]*router.Route
|
routes map[string][]*router.Route
|
||||||
|
// BeginHandlers is a slice of middleware for this controller.
|
||||||
|
// These handlers will be prependend to each one of
|
||||||
|
// the route that this controller will register(Handle/HandleMany/struct methods)
|
||||||
|
// to the targeted Party.
|
||||||
|
// Look the `Use` method too.
|
||||||
|
BeginHandlers context.Handlers
|
||||||
|
|
||||||
// true if this controller listens and serves to websocket events.
|
// true if this controller listens and serves to websocket events.
|
||||||
servesWebsocket bool
|
servesWebsocket bool
|
||||||
|
@ -199,6 +205,15 @@ func (c *ControllerActivator) GetRoutes(methodName string) []*router.Route {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use registers a middleware for this Controller.
|
||||||
|
// It appends one or more handlers to the `BeginHandlers`.
|
||||||
|
// It's like the `Party.Use` but specifically
|
||||||
|
// for the routes that this controller will register to the targeted `Party`.
|
||||||
|
func (c *ControllerActivator) Use(handlers ...context.Handler) *ControllerActivator {
|
||||||
|
c.BeginHandlers = append(c.BeginHandlers, handlers...)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
// Singleton returns new if all incoming clients' requests
|
// Singleton returns new if all incoming clients' requests
|
||||||
// have the same controller instance.
|
// have the same controller instance.
|
||||||
// This is done automatically by iris to reduce the creation
|
// This is done automatically by iris to reduce the creation
|
||||||
|
@ -343,6 +358,7 @@ func (c *ControllerActivator) handleMany(method, path, funcName string, override
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := c.handlerOf(path, funcName)
|
handler := c.handlerOf(path, funcName)
|
||||||
|
middleware = context.JoinHandlers(c.BeginHandlers, middleware)
|
||||||
|
|
||||||
// register the handler now.
|
// register the handler now.
|
||||||
routes := c.app.Router.HandleMany(method, path, append(middleware, handler)...)
|
routes := c.app.Router.HandleMany(method, path, append(middleware, handler)...)
|
||||||
|
|
|
@ -703,6 +703,31 @@ func TestControllerOverlapping(t *testing.T) {
|
||||||
m.Handle(new(publicController))
|
m.Handle(new(publicController))
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
e.GET("/").WithQuery("name", "kataras").Expect().Status(httptest.StatusOK).JSON().Equal(iris.Map{"id": 1})
|
e.GET("/").WithQuery("name", "kataras").Expect().Status(httptest.StatusOK).
|
||||||
e.GET("/").Expect().Status(httptest.StatusOK).JSON().Equal(iris.Map{"data": "things"})
|
JSON().Equal(iris.Map{"id": 1})
|
||||||
|
e.GET("/").Expect().Status(httptest.StatusOK).
|
||||||
|
JSON().Equal(iris.Map{"data": "things"})
|
||||||
|
}
|
||||||
|
|
||||||
|
type testControllerMethodHandlerBindStruct struct{}
|
||||||
|
|
||||||
|
type queryData struct {
|
||||||
|
Name string `json:"name" url:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*testControllerMethodHandlerBindStruct) Any(data queryData) queryData {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestControllerMethodHandlerBindStruct(t *testing.T) {
|
||||||
|
app := iris.New()
|
||||||
|
|
||||||
|
New(app).Handle(new(testControllerMethodHandlerBindStruct))
|
||||||
|
|
||||||
|
data := iris.Map{"name": "kataras"}
|
||||||
|
|
||||||
|
e := httptest.New(t, app)
|
||||||
|
e.GET("/").WithQueryObject(data).Expect().Status(httptest.StatusOK).JSON().Equal(data)
|
||||||
|
e.PATCH("/").WithJSON(data).Expect().Status(httptest.StatusOK).JSON().Equal(data)
|
||||||
|
// more tests inside the hero package itself.
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,8 @@ type GRPC struct {
|
||||||
Strict bool
|
Strict bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Option = GRPC{}
|
||||||
|
|
||||||
// Apply parses the controller's methods and registers gRPC handlers to the application.
|
// Apply parses the controller's methods and registers gRPC handlers to the application.
|
||||||
func (g GRPC) Apply(c *ControllerActivator) {
|
func (g GRPC) Apply(c *ControllerActivator) {
|
||||||
defer c.Activated()
|
defer c.Activated()
|
||||||
|
|
16
mvc/mvc.go
16
mvc/mvc.go
|
@ -146,15 +146,27 @@ func (app *Application) Register(dependencies ...interface{}) *Application {
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
// Option is an interface which does contain a single `Apply` method that accepts
|
// Option is an interface which does contain a single `Apply` method that accepts
|
||||||
// a `ControllerActivator`. It can be passed on `Application.Handle` method to
|
// a `ControllerActivator`. It can be passed on `Application.Handle` method to
|
||||||
// mdoify the behavior right after the `BeforeActivation` state.
|
// mdoify the behavior right after the `BeforeActivation` state.
|
||||||
//
|
//
|
||||||
// See `GRPC` package-level structure too.
|
// See `GRPC` package-level structure
|
||||||
type Option interface {
|
// and `Version` package-level function too.
|
||||||
|
Option interface {
|
||||||
Apply(*ControllerActivator)
|
Apply(*ControllerActivator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OptionFunc is the functional type of `Option`.
|
||||||
|
// Read `Option` docs.
|
||||||
|
OptionFunc func(*ControllerActivator)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Apply completes the `Option` interface.
|
||||||
|
func (opt OptionFunc) Apply(c *ControllerActivator) {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
|
||||||
// Handle serves a controller for the current mvc application's Router.
|
// Handle serves a controller for the current mvc application's Router.
|
||||||
// It accept any custom struct which its functions will be transformed
|
// It accept any custom struct which its functions will be transformed
|
||||||
// to routes.
|
// to routes.
|
||||||
|
|
38
mvc/versioning.go
Normal file
38
mvc/versioning.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package mvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kataras/iris/v12/context"
|
||||||
|
"github.com/kataras/iris/v12/core/router"
|
||||||
|
"github.com/kataras/iris/v12/versioning"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version returns a valid `Option` that can be passed to the `Application.Handle` method.
|
||||||
|
// It requires a specific "version" constraint for a Controller,
|
||||||
|
// e.g. ">1, <=2" or just "1".
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// m := mvc.New(dataRouter)
|
||||||
|
// m.Handle(new(v1Controller), mvc.Version("1"))
|
||||||
|
// m.Handle(new(v2Controller), mvc.Version("2.3"))
|
||||||
|
// m.Handle(new(v3Controller), mvc.Version(">=3, <4"))
|
||||||
|
// m.Handle(new(noVersionController))
|
||||||
|
//
|
||||||
|
// See the `versioning` package's documentation for more information on
|
||||||
|
// how the version is extracted from incoming requests.
|
||||||
|
//
|
||||||
|
// Note that this Option will set the route register rule to `RouteOverlap`.
|
||||||
|
func Version(version string) OptionFunc {
|
||||||
|
return func(c *ControllerActivator) {
|
||||||
|
c.Router().SetRegisterRule(router.RouteOverlap) // required for this feature.
|
||||||
|
|
||||||
|
c.Use(func(ctx context.Context) {
|
||||||
|
if !versioning.Match(ctx, version) {
|
||||||
|
ctx.StopExecution()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Next()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user