# API Versioning

The [versioning](https://github.com/kataras/iris/tree/main/versioning) subpackage provides [semver](https://semver.org/) versioning for your APIs.

All best practice conventions are implemented, including the suggestions written in the [api-guidelines document](https://github.com/byrondover/api-guidelines/blob/master/Guidelines.md#versioning).

* The version is retrieved from the `"Accept"` and/or `"Accept-Version"` request header(s).
* If a version matched, the server responds with a custom `"X-Api-Version"` header with the normalized version inside.
* If a version was not found (missing version header or unimplemented version) the server responds with status code of 501 and plain text of "version not found" by default.
* If a specific version of the resource is deprecated by the server, the client receives the custom `"X-Api-Warn"`, `"X-Api-Deprecation-Date"` and `"X-Api-Deprecation-Info"` headers, the request is handled as expected.

## Introduction

Internally, the version validation is done by the regex-free and fast [github.com/blang/semver/v4](https://github.com/blang/semver/v4) third-party package.

Valid version 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`.

## Getting Started

Import the `versioning` package.

```go
import (
    // [...]

    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/versioning"
)
```

Using the `versioning.NewGroup(version string) *versioning.Group` function you can create a group of routes to register the application's routes based on version requested by clients. The `versioning.Group` completes the `iris.Party` interface.

Example Code:

```go
package main

import (
    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/versioning"
)

func main() {
    app := iris.New()

    app.OnErrorCode(iris.StatusNotFound, func(ctx iris.Context) {
        ctx.WriteString(`Root not found handler.
        This will be applied everywhere except the /api/* requests.`)
    })

    api := app.Party("/api")
    // Optional, set version aliases (literal strings).
    // We use `UseRouter` instead of `Use`
    // to handle HTTP errors per version, but it's up to you.
    api.UseRouter(versioning.Aliases(versioning.AliasMap{
        // If no version provided by the client, default it to the "1.0.0".
        versioning.Empty: "1.0.0",
        // If a "latest" version is provided by the client,
        // set the version to be compared to "3.0.0".
        "latest": "3.0.0",
    }))

    // |----------------|
    // | The fun begins |
    // |----------------|

    // Create a new Group, which is a compatible Party,
    // based on version constraints.
    v1 := versioning.NewGroup(api, ">=1.0.0 <2.0.0")

    // Optionally, set custom view engine and path
    // for templates based on the version.
    v1.RegisterView(iris.HTML("./v1", ".html"))

    // Optionally, set custom error handler(s) based on the version.
    // Keep in mind that if you do this, you will
    // have to register error handlers
    // for the rest of the parties as well.
    v1.OnErrorCode(iris.StatusNotFound, testError("v1"))

    // Register resources based on the version.
    v1.Get("/", testHandler("v1"))
    v1.Get("/render", testView)

    // Do the same for version 2 and version 3,
    // for the sake of the example.
    v2 := versioning.NewGroup(api, ">=2.0.0 <3.0.0")
    v2.RegisterView(iris.HTML("./v2", ".html"))
    v2.OnErrorCode(iris.StatusNotFound, testError("v2"))
    v2.Get("/", testHandler("v2"))
    v2.Get("/render", testView)

    v3 := versioning.NewGroup(api, ">=3.0.0 <4.0.0")
    v3.RegisterView(iris.HTML("./v3", ".html"))
    v3.OnErrorCode(iris.StatusNotFound, testError("v3"))
    v3.Get("/", testHandler("v3"))
    v3.Get("/render", testView)

    app.Listen(":8080")
}

func testHandler(v string) iris.Handler {
    return func(ctx iris.Context) {
        ctx.JSON(iris.Map{
            "version": v,
            "message": "Hello, world!",
        })
    }
}

func testError(v string) iris.Handler {
    return func(ctx iris.Context) {
        ctx.Writef("not found: %s", v)
    }
}

func testView(ctx iris.Context) {
    ctx.View("index.html")
}
```

## Version Aliases

Optional, set version aliases (literal strings).

```go
api.UseRouter(versioning.Aliases(versioning.AliasMap{
    // If no version provided by the client, default it to the "1.0.0".
    versioning.Empty: "1.0.0",
    // If a "latest" version is provided by the client,
    // set the version to be compared to "3.0.0".
    "latest": "3.0.0",
}))
```

We use `UseRouter` instead of `Use` to handle HTTP errors per version, but it's up to you.

## Get Current Version

The version is extracted through the `versioning.GetVersion` function. You can use it anywhere, even if you don't use the versioning feature. By default `GetVersion` will try to read from:

* `Accept` header, e.g. `Accept: "application/json; version=1.0.0"`
* `Accept-Version` header, e.g. `Accept-Version: "1.0.0"`

You can customize it by **setting a version** based on the request context:

```go
api.Use(func(ctx *context.Context) {
    if version := ctx.URLParam("version"); version != "" {
        versioning.SetVersion(ctx, version)
    }

    ctx.Next()
})
```

Or by using the `FromQuery(version, default)` helper:

```go
api.Use(versioning.FromQuery("version", "1.0.0"))
```

### Deprecation

To mark an API version as deprecated use the `Deprecated` method.

```go
v1.Deprecated(versioning.DefaultDeprecationOptions)
```

This will make the route handlers to send some extra headers to the client.

* `"X-API-Warn": options.WarnMessage`
* `"X-API-Deprecation-Date": context.FormatTime(ctx, options.DeprecationDate))`
* `"X-API-Deprecation-Info": options.DeprecationInfo`
