API Versioning for Go
source link: https://github.com/kataras/versioning
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
API Versioning (Go)
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 e.t.c.
Getting started
The only requirement is the Go Programming Language .
$ go get github.com/kataras/versioning
Features
-
Per route version matching, an
http.Handler
with "switch" cases via versioning.Map for version => handler - Per group versioned routes and deprecation API
- Version matching like ">= 1.0, < 2.0" or just "2.0.1" and e.t.c.
-
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 through request's context key)
- 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.
Compare Versions
// If reports whether the "version" is a valid match to the "is". // The "is" can be a version constraint like ">= 1, < 3". If(version string, is string) bool
// Match reports whether the current version matches the "expectedVersion". Match(r *http.Request, expectedVersion string) bool
Example
router.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) { if versioning.Match(r, ">= 2.2.3") { // [logic for >= 2.2.3 version of your handler goes here] return } })
Determining The Current Version
Current request version is retrieved by versioning.GetVersion(r *http.Request)
.
By default the GetVersion
will try to read from:
-
Accept
header, i.eAccept: "application/json; version=1.0"
-
Accept-Version
header, i.eAccept-Version: "1.0"
func handler(w http.ResponseWriter, r *http.Request){ currentVersion := versioning.GetVersion(r) }
You can also set a custom version to a handler trough a middleware by setting a request context's value. For example:
import ( "context" "net/http" "github.com/kataras/versioning" ) func urlParamVersion(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){ version := r.URL.Query().Get("v") // ?v=2.3.5 if version == "" { // set a default version, e.g. 1.0 version = "1.0" } r = r.WithContext(context.WithValue(r.Context(), versioning.ContextKey, version)) next.ServeHTTP(w, r) }) }
Map Versions to Handlers
The versioning.NewMatcher(versioning.Map) http.Handler
creates a single handler which decides what handler need to be executed based on the requested version.
// middleware for all versions. func myMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){ // [...] next.ServeHTTP(w, r) }) } func myCustomVersionNotFound(w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) fmt.Fprintf(w, "%s version not found", versioning.GetVersion(r)) } router := http.NewServeMux() router.Handle("/", myMiddleware, versioning.NewMatcher(versioning.Map{ // v1Handler is a handler of yuors that will be executed only on version 1. "1.0": v1Handler, ">= 2, < 3": v2Handler, versioning.NotFound: http.HandlerFunc(myCustomNotVersionFound), }))
Deprecation
Using the versioning.Deprecated(handler http.Handler, options versioning.DeprecationOptions) http.Handler
function you can mark a specific handler version as deprecated.
v1Handler = versioning.Deprecated(v1Handler, versioning.DeprecationOptions{ // if empty defaults to: "WARNING! You are using a deprecated version of this API." WarnMessage string DeprecationDate time.Time DeprecationInfo string }) router.Handle("/", versioning.NewMatcher(versioning.Map{ "1.0": v1Handler, // [...] }))
This will make the handler to send these headers to the client:
"X-API-Warn": options.WarnMessage "X-API-Deprecation-Date": 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 *http.ServeMux, versionNotFoundHandler http.Handler, groups ...*versioning.Group)
must be called in the end in order to register the routes to a specific StdMux
.
router := http.NewServeMux() // version 1. usersAPIV1 := versioning.NewGroup(">= 1, < 2") usersAPIV1.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } w.Write([]byte("v1 resource: /api/users handler")) }) usersAPIV1.HandleFunc("/api/users/new", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } w.Write([]byte("v1 resource: /api/users/new post handler")) }) // version 2. usersAPIV2 := versioning.NewGroup(">= 2, < 3") usersAPIV2.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } w.Write([]byte("v2 resource: /api/users handler")) }) usersAPIV2.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } w.Write([]byte("v2 resource: /api/users post handler")) }) versioning.RegisterGroups(router, versioning.NotFoundHandler, usersAPIV1, usersAPIV2)
A middleware can be registered, 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 Group#Deprecated(versioning.DeprecationOptions)
on the group you want to notify your API consumers that this specific version is deprecated.
userAPIV1 := versioning.NewGroup("1.0").Deprecated(versioning.DefaultDeprecationOptions)
For a more detailed technical documentation you can head over to our godocs . And for executable code you can always visit the _examples repository's subdirectory.
License
kataras/versioning is free and open-source software licensed under the MIT License .
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK