1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-18 02:17:05 +00:00

various improvements and new 'UseOnce' method - read HISTORY.md

This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-08-06 03:35:58 +03:00
parent 5d480dc801
commit 46a3a99adf
20 changed files with 147 additions and 221 deletions

View File

@@ -1,185 +1,46 @@
package versioning
import (
"net/http"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/core/router"
)
type (
vroute struct {
method string
path string
versions Map
}
// Group is a group of version-based routes.
// One version per one or more routes.
type Group struct {
router.Party
// Group is a group of version-based routes.
// One version per one or more routes.
Group struct {
version string
extraMethods []string
routes []vroute
deprecation DeprecationOptions
}
)
// Information not currently in-use.
version string
deprecation DeprecationOptions
}
// NewGroup returns a ptr to Group based on the given "version".
// It sets the API Version for the "r" Party.
//
// See `Handle` and `RegisterGroups` for more.
func NewGroup(version string) *Group {
func NewGroup(r router.Party, version string) *Group {
// Note that this feature alters the RouteRegisterRule to RouteOverlap
// the RouteOverlap rule does not contain any performance downside
// but it's good to know that if you registered other mode, this wanna change it.
r.SetRegisterRule(router.RouteOverlap)
r.UseOnce(Handler(version)) // this is required in order to not populate this middleware to the next group.
return &Group{
Party: r,
version: version,
}
}
// Deprecated marks this group and all its versioned routes
// as deprecated versions of that endpoint.
// It can be called in the end just before `RegisterGroups`
// or first by `NewGroup(...).Deprecated(...)`. It returns itself.
func (g *Group) Deprecated(options DeprecationOptions) *Group {
// if `Deprecated` is called in the end.
for _, r := range g.routes {
r.versions[g.version] = Deprecated(r.versions[g.version], options)
}
// store the options if called before registering any versioned routes.
// store it for future use, e.g. collect all deprecated APIs and notify the developer.
g.deprecation = options
return g
}
// AllowMethods can be called before `Handle/Get/Post...`
// to tell the underline router that all routes should be registered
// to these "methods" as well.
func (g *Group) AllowMethods(methods ...string) *Group {
g.extraMethods = append(g.extraMethods, methods...)
return g
}
func (g *Group) addVRoute(method, path string, handler context.Handler) {
for _, r := range g.routes { // check if route already exists.
if r.method == method && r.path == path {
return
}
}
g.routes = append(g.routes, vroute{
method: method,
path: path,
versions: Map{g.version: handler},
g.Party.UseOnce(func(ctx *context.Context) {
WriteDeprecated(ctx, options)
ctx.Next()
})
}
// Handle registers a versioned route to the group.
// A call of `RegisterGroups` is necessary in order to register the actual routes
// when the group is complete.
//
// `RegisterGroups` for more.
func (g *Group) Handle(method string, path string, handler context.Handler) {
if g.deprecation.ShouldHandle() { // if `Deprecated` called first.
handler = Deprecated(handler, g.deprecation)
}
methods := append(g.extraMethods, method)
for _, method := range methods {
g.addVRoute(method, path, handler)
}
}
// None registers an "offline" versioned route
// see `context#ExecRoute(routeName)` and routing examples.
func (g *Group) None(path string, handler context.Handler) {
g.Handle(router.MethodNone, path, handler)
}
// Get registers a versioned route for the Get http method.
func (g *Group) Get(path string, handler context.Handler) {
g.Handle(http.MethodGet, path, handler)
}
// Post registers a versioned route for the Post http method.
func (g *Group) Post(path string, handler context.Handler) {
g.Handle(http.MethodPost, path, handler)
}
// Put registers a versioned route for the Put http method
func (g *Group) Put(path string, handler context.Handler) {
g.Handle(http.MethodPut, path, handler)
}
// Delete registers a versioned route for the Delete http method.
func (g *Group) Delete(path string, handler context.Handler) {
g.Handle(http.MethodDelete, path, handler)
}
// Connect registers a versioned route for the Connect http method.
func (g *Group) Connect(path string, handler context.Handler) {
g.Handle(http.MethodConnect, path, handler)
}
// Head registers a versioned route for the Head http method.
func (g *Group) Head(path string, handler context.Handler) {
g.Handle(http.MethodHead, path, handler)
}
// Options registers a versioned route for the Options http method.
func (g *Group) Options(path string, handler context.Handler) {
g.Handle(http.MethodOptions, path, handler)
}
// Patch registers a versioned route for the Patch http method.
func (g *Group) Patch(path string, handler context.Handler) {
g.Handle(http.MethodPatch, path, handler)
}
// Trace registers a versioned route for the Trace http method.
func (g *Group) Trace(path string, handler context.Handler) {
g.Handle(http.MethodTrace, path, handler)
}
// Any registers a versioned route for ALL of the http methods
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
func (g *Group) Any(registeredPath string, handler context.Handler) {
g.Get(registeredPath, handler)
g.Post(registeredPath, handler)
g.Put(registeredPath, handler)
g.Delete(registeredPath, handler)
g.Connect(registeredPath, handler)
g.Head(registeredPath, handler)
g.Options(registeredPath, handler)
g.Patch(registeredPath, handler)
g.Trace(registeredPath, handler)
}
// RegisterGroups registers one or more groups to an `iris.Party` or to the root router.
// See `NewGroup` and `NotFoundHandler` too.
func RegisterGroups(r router.Party, notFoundHandler context.Handler, groups ...*Group) (actualRoutes []*router.Route) {
var total []vroute
for _, g := range groups {
inner:
for _, r := range g.routes {
for i, tr := range total {
if tr.method == r.method && tr.path == r.path {
total[i].versions[g.version] = r.versions[g.version]
continue inner
}
}
total = append(total, r)
}
}
for _, vr := range total {
if notFoundHandler != nil {
vr.versions[NotFound] = notFoundHandler
}
route := r.Handle(vr.method, vr.path, NewMatcher(vr.versions))
actualRoutes = append(actualRoutes, route)
}
return
return g
}

View File

@@ -1,6 +1,7 @@
package versioning
import (
"errors"
"strings"
"github.com/kataras/iris/v12/context"
@@ -31,6 +32,10 @@ const (
NotFound = "iris.api.version.notfound"
)
// ErrNotFound reports whether a requested version
// does not match with any of the server's implemented ones.
var ErrNotFound = errors.New("version not found")
// NotFoundHandler is the default version not found handler that
// is executed from `NewMatcher` when no version is registered as available to dispatch a resource.
var NotFoundHandler = func(ctx *context.Context) {
@@ -46,8 +51,7 @@ var NotFoundHandler = func(ctx *context.Context) {
recognize the request method and is not capable of supporting it for any resource.
*/
ctx.StatusCode(501)
ctx.WriteString("version not found")
ctx.StopWithPlainError(501, ErrNotFound)
}
// GetVersion returns the current request version.

View File

@@ -44,6 +44,22 @@ func Match(ctx *context.Context, expectedVersion string) bool {
return true
}
// Handler returns a handler which stop the execution
// when the given "version" does not match with the requested one.
func Handler(version string) context.Handler {
return func(ctx *context.Context) {
if !Match(ctx, version) {
// Any overlapped handler
// can just clear the status code
// and the error to ignore this (see `NewGroup`).
NotFoundHandler(ctx)
return
}
ctx.Next()
}
}
// Map is a map of versions targets to a handlers,
// a handler per version or constraint, the key can be something like ">1, <=2" or just "1".
type Map map[string]context.Handler

View File

@@ -79,7 +79,7 @@ func TestNewGroup(t *testing.T) {
userAPI := app.Party("/api/user")
// [... static serving, middlewares and etc goes here].
userAPIV10 := versioning.NewGroup("1.0").Deprecated(versioning.DefaultDeprecationOptions)
userAPIV10 := versioning.NewGroup(userAPI, "1.0").Deprecated(versioning.DefaultDeprecationOptions)
// V10middlewareResponse := "m1"
// userAPIV10.Use(func(ctx iris.Context) {
// println("exec userAPIV10.Use - midl1")
@@ -97,7 +97,7 @@ func TestNewGroup(t *testing.T) {
// })
userAPIV10.Get("/", sendHandler(v10Response))
userAPIV2 := versioning.NewGroup(">= 2, < 3")
userAPIV2 := versioning.NewGroup(userAPI, ">= 2, < 3")
// V2middlewareResponse := "m2"
// userAPIV2.Use(func(ctx iris.Context) {
// println("exec userAPIV2.Use - midl1")
@@ -113,8 +113,6 @@ func TestNewGroup(t *testing.T) {
userAPIV2.Post("/", sendHandler(v2Response))
userAPIV2.Put("/other", sendHandler(v2Response))
versioning.RegisterGroups(userAPI, versioning.NotFoundHandler, userAPIV10, userAPIV2)
e := httptest.New(t, app)
ex := e.GET("/api/user").WithHeader(versioning.AcceptVersionHeaderKey, "1").Expect()