mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +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"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
func NewMatcher(versions Map) context.Handler {
|
||||
|
@ -18,15 +36,15 @@ func NewMatcher(versions Map) context.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
version, err := version.NewVersion(versionString)
|
||||
ver, err := version.NewVersion(versionString)
|
||||
if err != nil {
|
||||
notFoundHandler(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
for _, ch := range constraintsHandlers {
|
||||
if ch.constraints.Check(version) {
|
||||
ctx.Header("X-API-Version", version.String())
|
||||
if ch.constraints.Check(ver) {
|
||||
ctx.Header("X-API-Version", ver.String())
|
||||
ch.handler(ctx)
|
||||
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) {
|
||||
app := iris.New()
|
||||
|
||||
|
@ -33,6 +41,17 @@ func TestNewMatcher(t *testing.T) {
|
|||
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.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().
|
||||
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().
|
||||
Status(iris.StatusNotFound).Body().Equal("Not Found")
|
||||
}
|
||||
|
||||
func TestNewGroup(t *testing.T) {
|
||||
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")
|
||||
// [... static serving, middlewares and etc goes here].
|
||||
|
|
Loading…
Reference in New Issue
Block a user