123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- package versioning
- import (
- "github.com/kataras/iris/context"
- "github.com/hashicorp/go-version"
- )
- // If reports whether the "version" is matching to the "is".
- // the "is" can be a constraint like ">= 1, < 3".
- func If(v string, is string) bool {
- _, ok := check(v, is)
- return ok
- }
- func check(v string, is string) (string, bool) {
- ver, err := version.NewVersion(v)
- if err != nil {
- return "", false
- }
- constraints, err := version.NewConstraint(is)
- if err != nil {
- return "", false
- }
- // return the extracted version from request, even if not matched.
- return ver.String(), constraints.Check(ver)
- }
- // Match acts exactly the same as `If` does but instead it accepts
- // a Context, so it can be called by a handler to determinate the requested version.
- //
- // If matched then it sets the "X-API-Version" response header and
- // stores the matched version into Context (see `GetVersion` too).
- func Match(ctx *context.Context, expectedVersion string) bool {
- versionString, matched := check(GetVersion(ctx), expectedVersion)
- if !matched {
- return false
- }
- SetVersion(ctx, versionString)
- ctx.Header("X-API-Version", versionString)
- 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
- // NewMatcher creates a single handler which decides what handler
- // should be executed based on the requested version.
- //
- // Use the `NewGroup` if you want to add many routes under a specific version.
- //
- // See `Map` and `NewGroup` too.
- func NewMatcher(versions Map) context.Handler {
- constraintsHandlers, notFoundHandler := buildConstraints(versions)
- return func(ctx *context.Context) {
- versionString := GetVersion(ctx)
- if versionString == "" || versionString == NotFound {
- notFoundHandler(ctx)
- return
- }
- ver, err := version.NewVersion(versionString)
- if err != nil {
- notFoundHandler(ctx)
- return
- }
- for _, ch := range constraintsHandlers {
- if ch.constraints.Check(ver) {
- ctx.Header("X-API-Version", ver.String())
- ch.handler(ctx)
- return
- }
- }
- // pass the not matched version so the not found handler can have knowedge about it.
- // SetVersion(ctx, versionString)
- // or let a manual cal of GetVersion(ctx) do that instead.
- notFoundHandler(ctx)
- }
- }
- type constraintsHandler struct {
- constraints version.Constraints
- handler context.Handler
- }
- func buildConstraints(versionsHandler Map) (constraintsHandlers []*constraintsHandler, notfoundHandler context.Handler) {
- for v, h := range versionsHandler {
- if v == NotFound {
- notfoundHandler = h
- continue
- }
- constraints, err := version.NewConstraint(v)
- if err != nil {
- panic(err)
- }
- constraintsHandlers = append(constraintsHandlers, &constraintsHandler{
- constraints: constraints,
- handler: h,
- })
- }
- if notfoundHandler == nil {
- notfoundHandler = NotFoundHandler
- }
- // no sort, the end-dev should declare
- // all version constraint, i.e < 4.0 may be catch 1.0 if not something like
- // >= 3.0, < 4.0.
- // I can make it ordered but I do NOT like the final API of it:
- /*
- app.Get("/api/user", NewMatcher( // accepts an array, ordered, see last elem.
- V("1.0", vHandler("v1 here")),
- V("2.0", vHandler("v2 here")),
- V("< 4.0", vHandler("v3.x here")),
- ))
- instead we have:
- app.Get("/api/user", NewMatcher(Map{ // accepts a map, unordered, see last elem.
- "1.0": Deprecated(vHandler("v1 here")),
- "2.0": vHandler("v2 here"),
- ">= 3.0, < 4.0": vHandler("v3.x here"),
- VersionUnknown: customHandlerForNotMatchingVersion,
- }))
- */
- return
- }
|