versioning

package
v0.0.0-...-1a5f2c1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 11, 2024 License: BSD-3-Clause, BSD-3-Clause Imports: 6 Imported by: 0

README

Versioning

The versioning package provides semver versioning for your APIs. It implements all the suggestions written at api-guidelines and more.

The version comparison is done by the 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:

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.

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,
}))
Deprecation

Using the versioning.Deprecated(handler iris.Handler, options versioning.DeprecationOptions) iris.Handler function you can mark a specific handler version as deprecated.

v10Handler := versioning.Deprecated(sendHandler(v10Response), versioning.DeprecationOptions{
    // if empty defaults to: "WARNING! You are using a deprecated version of this API."
    WarnMessage string 
    DeprecationDate time.Time
    DeprecationInfo string
})

userAPI.Get("/", versioning.NewMatcher(versioning.Map{
    "1.0": v10Handler,
    // [...]
}))

This will make the handler to send these headers to the client:

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

versioning.DefaultDeprecationOptions can be passed instead if you don't care about Date and Info.

Grouping routes by version

Grouping routes by version is possible as well.

Using the versioning.NewGroup(version string) *versioning.Group function you can create a group to register your versioned routes. The versioning.RegisterGroups(r iris.Party, versionNotFoundHandler iris.Handler, groups ...*versioning.Group) must be called in the end in order to register the routes to a specific Party.

app := iris.New()

userAPI := app.Party("/api/user")
// [... static serving, middlewares and etc goes here].

userAPIV10 := versioning.NewGroup("1.0")
userAPIV10.Get("/", sendHandler(v10Response))

userAPIV2 := versioning.NewGroup(">= 2, < 3")
userAPIV2.Get("/", sendHandler(v2Response))
userAPIV2.Post("/", sendHandler(v2Response))
userAPIV2.Put("/other", sendHandler(v2Response))

versioning.RegisterGroups(userAPI, versioning.NotFoundHandler, userAPIV10, userAPIV2)

A middleware can be registered to the actual iris.Party only, using the methods we learnt above, i.e by using the versioning.Match in order to detect what code/handler you want to be executed when "x" or no version is requested.

Deprecation for Group

Just call the Deprecated(versioning.DeprecationOptions) on the group you want to notify your API consumers that this specific version is deprecated.

userAPIV10 := versioning.NewGroup("1.0").Deprecated(versioning.DefaultDeprecationOptions)

Compare version manually from inside your handlers

// reports if the "version" is matching to the "is".
// the "is" can be a constraint like ">= 1, < 3".
If(version string, is string) bool
// same as `If` but expects a Context to read the requested version.
Match(ctx iris.Context, expectedVersion string) bool
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
    }
})

Documentation

Index

Constants

View Source
const (
	// AcceptVersionHeaderKey is the header key of "Accept-Version".
	AcceptVersionHeaderKey = "Accept-Version"
	// AcceptHeaderKey is the header key of "Accept".
	AcceptHeaderKey = "Accept"
	// AcceptHeaderVersionValue is the Accept's header value search term the requested version.
	AcceptHeaderVersionValue = "version"

	// Key is the context key of the version, can be used to manually modify the "requested" version.
	// 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
	// 	ctx.Values().Set(versioning.Key, ctx.URLParamDefault("version", "1"))
	// 	ctx.Next()
	// }
	Key = "iris.api.version" // for use inside the ctx.Values(), not visible by the user.
	// NotFound is the key that can be used inside a `Map` or inside `ctx.Values().Set(versioning.Key, versioning.NotFound)`
	// to tell that a version wasn't found, therefore the not found handler should handle the request instead.
	NotFound = Key + ".notfound"
)

Variables

View Source
var DefaultDeprecationOptions = DeprecationOptions{
	WarnMessage: "WARNING! You are using a deprecated version of this API.",
}

DefaultDeprecationOptions are the default deprecation options, it defaults the "X-API-Warn" header to a generic message.

View Source
var NotFoundHandler = func(ctx context.Context) {

	ctx.StatusCode(501)
	ctx.WriteString("version not found")
}

NotFoundHandler is the default version not found handler that is executed from `NewMatcher` when no version is registered as available to dispatch a resource.

Functions

func Deprecated

func Deprecated(handler context.Handler, options DeprecationOptions) context.Handler

Deprecated marks a specific handler as a deprecated. Deprecated can be used to tell the clients that a newer version of that specific resource is available instead.

func GetVersion

func GetVersion(ctx context.Context) string

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"

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).

func If

func If(v string, is string) bool

If reports whether the "version" is matching to the "is". the "is" can be a constraint like ">= 1, < 3".

func Match

func Match(ctx context.Context, expectedVersion string) bool

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.

func NewMatcher

func NewMatcher(versions Map) 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 RegisterGroups

func RegisterGroups(r router.Party, notFoundHandler context.Handler, groups ...*Group) (actualRoutes []*router.Route)

RegisterGroups registers one or more groups to an `iris.Party` or to the root router. See `NewGroup` and `NotFoundHandler` too.

Types

type DeprecationOptions

type DeprecationOptions struct {
	WarnMessage     string
	DeprecationDate time.Time
	DeprecationInfo string
}

DeprecationOptions describes the deprecation headers key-values. - "X-API-Warn": options.WarnMessage - "X-API-Deprecation-Date": context.FormatTime(ctx, options.DeprecationDate)) - "X-API-Deprecation-Info": options.DeprecationInfo

func (DeprecationOptions) ShouldHandle

func (opts DeprecationOptions) ShouldHandle() bool

ShouldHandle reports whether the deprecation headers should be present or no.

type Group

type Group struct {
	// contains filtered or unexported fields
}

Group is a group of version-based routes. One version per one or more routes.

func NewGroup

func NewGroup(version string) *Group

NewGroup returns a ptr to Group based on the given "version".

See `Handle` and `RegisterGroups` for more.

func (*Group) AllowMethods

func (g *Group) AllowMethods(methods ...string) *Group

AllowMethods can be called before `Handle/Get/Post...` to tell the underline router that all routes should be registered to these "methods" as well.

func (*Group) Any

func (g *Group) Any(registeredPath string, handler context.Handler)

Any registers a versioned route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete).

func (*Group) Connect

func (g *Group) Connect(path string, handler context.Handler)

Connect registers a versioned route for the Connect http method.

func (*Group) Delete

func (g *Group) Delete(path string, handler context.Handler)

Delete registers a versioned route for the Delete http method.

func (*Group) Deprecated

func (g *Group) Deprecated(options DeprecationOptions) *Group

Deprecated marks this group and all its versioned routes as deprecated versions of that endpoint. It can be called in the end just before `RegisterGroups` or first by `NewGroup(...).Deprecated(...)`. It returns itself.

func (*Group) Get

func (g *Group) Get(path string, handler context.Handler)

Get registers a versioned route for the Get http method.

func (*Group) Handle

func (g *Group) Handle(method string, path string, handler context.Handler)

Handle registers a versioned route to the group. A call of `RegisterGroups` is necessary in order to register the actual routes when the group is complete.

`RegisterGroups` for more.

func (*Group) Head

func (g *Group) Head(path string, handler context.Handler)

Head registers a versioned route for the Head http method.

func (*Group) None

func (g *Group) None(path string, handler context.Handler)

None registers an "offline" versioned route see `context#ExecRoute(routeName)` and routing examples.

func (*Group) Options

func (g *Group) Options(path string, handler context.Handler)

Options registers a versioned route for the Options http method.

func (*Group) Patch

func (g *Group) Patch(path string, handler context.Handler)

Patch registers a versioned route for the Patch http method.

func (*Group) Post

func (g *Group) Post(path string, handler context.Handler)

Post registers a versioned route for the Post http method.

func (*Group) Put

func (g *Group) Put(path string, handler context.Handler)

Put registers a versioned route for the Put http method

func (*Group) Trace

func (g *Group) Trace(path string, handler context.Handler)

Trace registers a versioned route for the Trace http method.

type Map

type Map map[string]context.Handler

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".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL