123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- package versioning
- import (
- "strings"
- "github.com/kataras/iris/v12/context"
- "github.com/kataras/iris/v12/core/router"
- "github.com/blang/semver/v4"
- )
- // API is a type alias of router.Party.
- // This is required in order for a Group instance
- // to implement the Party interface without field conflict.
- type API = router.Party
- // Group represents a group of resources that should
- // be handled based on a version requested by the client.
- // See `NewGroup` for more.
- type Group struct {
- API
- validate semver.Range
- deprecation DeprecationOptions
- }
- // NewGroup returns a version Group based on the given "version" constraint.
- // Group completes the Party interface.
- // The returned Group wraps a cloned Party of the given "r" Party therefore,
- // any changes to its parent won't affect this one (e.g. register global middlewares afterwards).
- //
- // A version is extracted through the versioning.GetVersion function:
- //
- // Accept-Version: 1.0.0
- // Accept: application/json; version=1.0.0
- //
- // You can customize it by setting a version based on the request context:
- //
- // api.Use(func(ctx *context.Context) {
- // if version := ctx.URLParam("version"); version != "" {
- // SetVersion(ctx, version)
- // }
- //
- // ctx.Next()
- // })
- //
- // OR:
- //
- // api.Use(versioning.FromQuery("version", ""))
- //
- // Examples at: _examples/routing/versioning
- // Usage:
- //
- // app := iris.New()
- // api := app.Party("/api")
- // v1 := versioning.NewGroup(api, ">=1.0.0 <2.0.0")
- // v1.Get/Post/Put/Delete...
- //
- // Valid ranges are:
- // - "<1.0.0"
- // - "<=1.0.0"
- // - ">1.0.0"
- // - ">=1.0.0"
- // - "1.0.0", "=1.0.0", "==1.0.0"
- // - "!1.0.0", "!=1.0.0"
- //
- // A Range can consist of multiple ranges separated by space:
- // Ranges can be linked by logical AND:
- // - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7"
- //
- // but not "1.0.0" or "2.0.0"
- // - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0
- //
- // except 2.0.3-beta.2
- //
- // Ranges can also be linked by logical OR:
- // - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
- //
- // AND has a higher precedence than OR. It's not possible to use brackets.
- //
- // Ranges can be combined by both AND and OR
- //
- // - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`,
- //
- // but not `4.2.1`, `2.1.1`
- func NewGroup(r API, version string) *Group {
- version = strings.ReplaceAll(version, ",", " ")
- version = strings.TrimSpace(version)
- verRange, err := semver.ParseRange(version)
- if err != nil {
- r.Logger().Errorf("versioning: %s: %s", r.GetRelPath(), strings.ToLower(err.Error()))
- return &Group{API: r}
- }
- // Clone this one.
- r = r.Party("/")
- // 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)
- handler := makeHandler(verRange)
- // This is required in order to not populate this middleware to the next group.
- r.UseOnce(handler)
- // This is required for versioned custom error handlers,
- // of course if the parent registered one then this will do nothing.
- r.UseError(handler)
- return &Group{
- API: r,
- validate: verRange,
- }
- }
- // Deprecated marks this group and all its versioned routes
- // as deprecated versions of that endpoint.
- func (g *Group) Deprecated(options DeprecationOptions) *Group {
- // store it for future use, e.g. collect all deprecated APIs and notify the developer.
- g.deprecation = options
- g.API.UseOnce(func(ctx *context.Context) {
- WriteDeprecated(ctx, options)
- ctx.Next()
- })
- return g
- }
- func makeHandler(validate semver.Range) context.Handler {
- return func(ctx *context.Context) {
- if !matchVersionRange(ctx, validate) {
- // The overlapped handler has an exception
- // of a type of context.NotFound (which versioning.ErrNotFound wraps)
- // to clear the status code
- // and the error to ignore this
- // when available match version exists (see `NewGroup`).
- if h := NotFoundHandler; h != nil {
- h(ctx)
- return
- }
- }
- ctx.Next()
- }
- }
|