1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-23 21:07:03 +00:00

API versioning improvements

Replace the go-version package with a regex-free alternative semver

the result: versioned apis have almost zero performance cost now

thanks @motogo for your kind donation ❤️ - please check your github notifications
This commit is contained in:
Gerasimos (Makis) Maropoulos
2021-01-07 04:14:41 +02:00
parent b409f7807e
commit 240fdb6dc3
21 changed files with 381 additions and 463 deletions

View File

@@ -5,6 +5,8 @@ import (
"strings"
"github.com/kataras/iris/v12/context"
"github.com/blang/semver/v4"
)
const (
@@ -52,11 +54,89 @@ var NotFoundHandler = func(ctx *context.Context) {
ctx.StopWithPlainError(501, ErrNotFound)
}
// FromQuery is a simple helper which tries to
// set the version constraint from a given URL Query Parameter.
// The X-Api-Version is still valid.
func FromQuery(urlQueryParameterName string, defaultVersion string) context.Handler {
return func(ctx *context.Context) {
version := ctx.URLParam(urlQueryParameterName)
if version == "" {
version = defaultVersion
}
if version != "" {
SetVersion(ctx, version)
}
ctx.Next()
}
}
// If reports whether the "got" matches the "expected" one.
// the "expected" can be a constraint like ">=1.0.0 <2.0.0".
// This function is just a helper, better use the Group instead.
func If(got string, expected string) bool {
v, err := semver.Make(got)
if err != nil {
return false
}
validate, err := semver.ParseRange(expected)
if err != nil {
return false
}
return validate(v)
}
// Match reports whether the request matches the expected version.
// This function is just a helper, better use the Group instead.
func Match(ctx *context.Context, expectedVersion string) bool {
validate, err := semver.ParseRange(expectedVersion)
if err != nil {
return false
}
return matchVersionRange(ctx, validate)
}
func matchVersionRange(ctx *context.Context, validate semver.Range) bool {
gotVersion := GetVersion(ctx)
alias, aliasFound := GetVersionAlias(ctx, gotVersion)
if aliasFound {
SetVersion(ctx, alias) // set the version so next routes have it already.
gotVersion = alias
}
if gotVersion == "" {
return false
}
v, err := semver.Make(gotVersion)
if err != nil {
return false
}
if !validate(v) {
return false
}
versionString := v.String()
if !aliasFound { // don't lose any time to set if already set.
SetVersion(ctx, versionString)
}
ctx.Header(APIVersionResponseHeader, versionString)
return true
}
// GetVersion returns the current request version.
//
// By default the `GetVersion` will try to read from:
// - "Accept" header, i.e Accept: "application/json; version=1.0"
// - "Accept-Version" header, i.e Accept-Version: "1.0"
// - "Accept" header, i.e Accept: "application/json; version=1.0.0"
// - "Accept-Version" header, i.e Accept-Version: "1.0.0"
//
// However, the end developer can also set a custom version for a handler via a middleware by using the context's store key
// for versions (see `Key` for further details on that).
@@ -108,7 +188,7 @@ func GetVersion(ctx *context.Context) string {
// Example of how you can change the default behavior to extract a requested version (which is by headers)
// from a "version" url parameter instead:
// func(ctx iris.Context) { // &version=1
// version := ctx.URLParamDefault("version", "1")
// version := ctx.URLParamDefault("version", "1.0.0")
// versioning.SetVersion(ctx, version)
// ctx.Next()
// }
@@ -129,15 +209,15 @@ type AliasMap = map[string]string
//
// api := app.Party("/api")
// api.Use(Aliases(map[string]string{
// versioning.Empty: "1", // when no version was provided by the client.
// versioning.Empty: "1.0.0", // when no version was provided by the client.
// "beta": "4.0.0",
// "stage": "5.0.0-alpha"
// }))
//
// v1 := NewGroup(api, ">= 1, < 2")
// v1 := NewGroup(api, ">=1.0.0 < 2.0.0")
// v1.Get/Post...
//
// v4 := NewGroup(api, ">= 4, < 5")
// v4 := NewGroup(api, ">=4.0.0 < 5.0.0")
// v4.Get/Post...
//
// stage := NewGroup(api, "5.0.0-alpha")
@@ -154,6 +234,23 @@ func Aliases(aliases AliasMap) context.Handler {
}
}
// Handler returns a handler which is only fired
// when the "version" is matched with the requested one.
// It is not meant to be used by end-developers
// (exported for version controller feature).
// Use `NewGroup` instead.
func Handler(version string) context.Handler {
validate, err := semver.ParseRange(version)
if err != nil {
return func(ctx *context.Context) {
ctx.StopWithError(500, err)
return
}
}
return makeHandler(validate)
}
// GetVersionAlias returns the version alias of the given "gotVersion"
// or empty. It Reports whether the alias was found.
// See `SetVersionAliases`, `Aliases` and `Match` for more.
@@ -196,7 +293,7 @@ func GetVersionAlias(ctx *context.Context, gotVersion string) (string, bool) {
//
// The last "override" input argument indicates whether any
// existing aliases, registered by previous handlers in the chain,
// should be overriden or copied to the previous map one.
// should be overridden or copied to the previous map one.
func SetVersionAliases(ctx *context.Context, aliases AliasMap, override bool) {
key := ctx.Application().ConfigurationReadOnly().GetVersionAliasesContextKey()
if key == "" {