mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
add versioning/README.md
Former-commit-id: 7ea92fadc982038533675996704b6bf89e149aae
This commit is contained in:
parent
fc9e5b3c05
commit
70610af6fd
86
versioning/README.md
Normal file
86
versioning/README.md
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# Versioning
|
||||||
|
|
||||||
|
The [versioning](https://github.com/kataras/iris/tree/master/versioning) package provides [semver](https://semver.org/) versioning for your APIs. It implements all the suggestions written at [api-guidelines](https://github.com/byrondover/api-guidelines/blob/master/Guidelines.md#versioning) and more.
|
||||||
|
|
||||||
|
|
||||||
|
The version comparison is done by the [go-version](https://github.com/hashicorp/go-version) package. It supports matching over patterns like `">= 1.0, < 3"` and etc.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- per route version matching, a normal iris handler with "switch" cases via Map for version => handler
|
||||||
|
- per group versioned routes and deprecation API
|
||||||
|
- version matching like ">= 1.0, < 2.0" or just "2.0.1" and etc.
|
||||||
|
- version not found handler (can be customized by simply adding the versioning.NotFound: customNotMatchVersionHandler on the Map)
|
||||||
|
- version is retrieved from the "Accept" and "Accept-Version" headers (can be customized via middleware)
|
||||||
|
- respond with "X-API-Version" header, if version found.
|
||||||
|
- deprecation options with customizable "X-API-Warn", "X-API-Deprecation-Date", "X-API-Deprecation-Info" headers via `Deprecated` wrapper.
|
||||||
|
|
||||||
|
## Get version
|
||||||
|
|
||||||
|
Current request version is retrieved by `versioning.GetVersion(ctx)`.
|
||||||
|
|
||||||
|
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"`
|
||||||
|
|
||||||
|
You can also set a custom version for a handler via a middleware by using the context's store values.
|
||||||
|
For example:
|
||||||
|
```go
|
||||||
|
func(ctx iris.Context) {
|
||||||
|
ctx.Values().Set(versioning.Key, ctx.URLParamDefault("version", "1.0"))
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Match version to handler
|
||||||
|
|
||||||
|
The `versioning.NewMatcher(versioning.Map) iris.Handler` creates a single handler which decides what handler need to be executed based on the requested version.
|
||||||
|
|
||||||
|
```go
|
||||||
|
app := iris.New()
|
||||||
|
|
||||||
|
// middleware for all versions.
|
||||||
|
myMiddleware := func(ctx iris.Context) {
|
||||||
|
// [...]
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
myCustomNotVersionFound := func(ctx iris.Context) {
|
||||||
|
ctx.StatusCode(404)
|
||||||
|
ctx.Writef("%s version not found", versioning.GetVersion(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
userAPI := app.Party("/api/user")
|
||||||
|
userAPI.Get("/", myMiddleware, versioning.NewMatcher(versioning.Map{
|
||||||
|
"1.0": sendHandler(v10Response),
|
||||||
|
">= 2, < 3": sendHandler(v2Response),
|
||||||
|
versioning.NotFound: myCustomNotVersionFound,
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Grouping versioned routes
|
||||||
|
|
||||||
|
Impl & tests done, example not. **TODO**
|
||||||
|
|
||||||
|
## Compare version manually from inside your handlers
|
||||||
|
|
||||||
|
```go
|
||||||
|
// reports if the "version" is matching to the "is".
|
||||||
|
// the "is" can be a constraint like ">= 1, < 3".
|
||||||
|
If(version string, is string) bool
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// same as `If` but expects a Context to read the requested version.
|
||||||
|
Match(ctx iris.Context, expectedVersion string) bool
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
app.Get("/api/user", func(ctx iris.Context) {
|
||||||
|
if versioning.Match(ctx, ">= 2.2.3") {
|
||||||
|
// [logic for >= 2.2.3 version of your handler goes here]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
|
@ -6,6 +6,24 @@ import (
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func If(v string, is string) bool {
|
||||||
|
ver, err := version.NewVersion(v)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
constraints, err := version.NewConstraint(is)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return constraints.Check(ver)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Match(ctx context.Context, expectedVersion string) bool {
|
||||||
|
return If(GetVersion(ctx), expectedVersion)
|
||||||
|
}
|
||||||
|
|
||||||
type Map map[string]context.Handler
|
type Map map[string]context.Handler
|
||||||
|
|
||||||
func NewMatcher(versions Map) context.Handler {
|
func NewMatcher(versions Map) context.Handler {
|
||||||
|
@ -18,15 +36,15 @@ func NewMatcher(versions Map) context.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
version, err := version.NewVersion(versionString)
|
ver, err := version.NewVersion(versionString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
notFoundHandler(ctx)
|
notFoundHandler(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ch := range constraintsHandlers {
|
for _, ch := range constraintsHandlers {
|
||||||
if ch.constraints.Check(version) {
|
if ch.constraints.Check(ver) {
|
||||||
ctx.Header("X-API-Version", version.String())
|
ctx.Header("X-API-Version", ver.String())
|
||||||
ch.handler(ctx)
|
ch.handler(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,14 @@ func sendHandler(contents string) iris.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIf(t *testing.T) {
|
||||||
|
if expected, got := true, versioning.If("1.0", ">=1"); expected != got {
|
||||||
|
t.Fatalf("expected %s to be %s", "1.0", ">= 1")
|
||||||
|
}
|
||||||
|
if expected, got := true, versioning.If("1.2.3", "> 1.2"); expected != got {
|
||||||
|
t.Fatalf("expected %s to be %s", "1.2.3", "> 1.2")
|
||||||
|
}
|
||||||
|
}
|
||||||
func TestNewMatcher(t *testing.T) {
|
func TestNewMatcher(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
|
@ -33,6 +41,17 @@ func TestNewMatcher(t *testing.T) {
|
||||||
versioning.NotFound: notFoundHandler,
|
versioning.NotFound: notFoundHandler,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// middleware as usual.
|
||||||
|
myMiddleware := func(ctx iris.Context) {
|
||||||
|
ctx.Header("X-Custom", "something")
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
myVersions := versioning.Map{
|
||||||
|
"1.0": sendHandler(v10Response),
|
||||||
|
}
|
||||||
|
|
||||||
|
userAPI.Get("/with_middleware", myMiddleware, versioning.NewMatcher(myVersions))
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
|
|
||||||
e.GET("/api/user").WithHeader(versioning.AcceptVersionHeaderKey, "1").Expect().
|
e.GET("/api/user").WithHeader(versioning.AcceptVersionHeaderKey, "1").Expect().
|
||||||
|
@ -44,32 +63,17 @@ func TestNewMatcher(t *testing.T) {
|
||||||
e.GET("/api/user").WithHeader(versioning.AcceptVersionHeaderKey, "2.9.9").Expect().
|
e.GET("/api/user").WithHeader(versioning.AcceptVersionHeaderKey, "2.9.9").Expect().
|
||||||
Status(iris.StatusOK).Body().Equal(v2Response)
|
Status(iris.StatusOK).Body().Equal(v2Response)
|
||||||
|
|
||||||
|
// middleware as usual.
|
||||||
|
ex := e.GET("/api/user/with_middleware").WithHeader(versioning.AcceptVersionHeaderKey, "1.0").Expect()
|
||||||
|
ex.Status(iris.StatusOK).Body().Equal(v10Response)
|
||||||
|
ex.Header("X-Custom").Equal("something")
|
||||||
|
|
||||||
e.GET("/api/user").WithHeader(versioning.AcceptVersionHeaderKey, "3.0").Expect().
|
e.GET("/api/user").WithHeader(versioning.AcceptVersionHeaderKey, "3.0").Expect().
|
||||||
Status(iris.StatusNotFound).Body().Equal("Not Found")
|
Status(iris.StatusNotFound).Body().Equal("Not Found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewGroup(t *testing.T) {
|
func TestNewGroup(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
// userAPI := app.Party("/api/user")
|
|
||||||
|
|
||||||
// userAPIV10 := versioning.NewGroup("1.0", userAPI)
|
|
||||||
// userAPIV10.Get("/", sendHandler(v10Response))
|
|
||||||
// userAPIV2 := versioning.NewGroup(">= 2, < 3", userAPI)
|
|
||||||
// userAPIV2.Get("/", sendHandler(v2Response))
|
|
||||||
|
|
||||||
// ---
|
|
||||||
|
|
||||||
// userAPI := app.Party("/api/user")
|
|
||||||
// userVAPI := versioning.NewGroup(userAPI)
|
|
||||||
// userAPIV10 := userVAPI.Version("1.0")
|
|
||||||
// userAPIV10.Get("/", sendHandler(v10Response))
|
|
||||||
|
|
||||||
// userAPIV10 := userVAPI.Version("2.0")
|
|
||||||
// userAPIV10.Get("/", sendHandler(v10Response))
|
|
||||||
// userVAPI.NotFound(...)
|
|
||||||
// userVAPI.Build()
|
|
||||||
|
|
||||||
// --
|
|
||||||
|
|
||||||
userAPI := app.Party("/api/user")
|
userAPI := app.Party("/api/user")
|
||||||
// [... static serving, middlewares and etc goes here].
|
// [... static serving, middlewares and etc goes here].
|
||||||
|
|
Loading…
Reference in New Issue
Block a user