Swagger-UI mit OpenAPI Specification 3.1 in Go

| Foto von Elena Rouame auf Unsplash

Swagger-UI für OpenAPI Specification 3.1 mit Golang hosten

Vorab: ein großes Lob gilt Mario Carrion, der die Grundlagen dieser Anleitung in seinem Blogpost beschrieben hat. Ein paar Dinge haben sich seit 2021 geändert, aber der Prozess ist grundlegend derselbe. Wem das Problem bekannt ist, kann auch gerne direkt zur Anleitung springen.

Was ist OpenAPI Specification?

Ehemalig unter dem Namen ‘Swagger’ bekannt, ist die OpenAPI Specification ein offener Standard, um HTTP Schnittstellen (APIs) zu beschreiben. Die Spezifikation wird seit 2015 von der OpenAPI Initiative verwaltet.

Das Problem

Die OpenAPI Spezifikation ist quelloffen und programmiersprachenagnostisch, was dazu führt, dass es viele Tools, Packages, Libraries, etc. gibt um mit der Spezifikation zu arbeiten.

Eines dieser Tools ist swaggo, ein Golang-Projekt, welches es sich zum Ziel gesetzt hat, aus Go Quellcode automatisch REST-API-Dokumentationen mit der OpenAPI Spezifikation zu erstellen.
Dabei unterstützt swaggo viele beliebte Go-Router Implementationen, wie zum Beispiel gin, echo, gorilla/mux oder auch das zur Go-Standardbibliothek gehörende net/http-package, um automatisch eine Swagger-UI zur Verfügung zu stellen.
Aber leider ist es aktuell mit swaggo nicht möglich eine OAS 3.1 kompatible Swagger-UI zu hosten.
Auch andere Alternative OSS Go-Tools tun sich schwer, up-to-date mit dem neuestens Standard zu sein.
Macht aber nichts, denn das können wir auch selbst in die Hand nehmen.

Anleitung

Einen kleinen Server aufsetzen

Als allererstes brauchen wir einen kleinen funktionsfähigen Server, welcher dank gorilla/mux schnell aufgesetzt ist:

go get -u github.com/gorilla/mux

package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/success", getSuccess).Methods(http.MethodGet)
    _ = http.ListenAndServe(":8003", r)
}

func getSuccess(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "success")
}

OpenAPI Spezifikation generieren

Dankenswerterweise können wir swaggo trotz der anfangs beschriebenen Probleme nutzen! Denn das Tool (swaggo/swag) der Spezifikation unterstützt seit seiner v2.0.0-beta das Generieren von OpenAPI Spezifikationen der Version 3.1.

⚠️ Achtung: Wir arbeiten ab jetzt mit einem RC (Release Candidate), der Fehler haben kann!

Da es noch keinen offiziellen Release gibt, müssen wir uns hier ein wenig selbst helfen: Quellcode aus dem Release v2.0.0-rc3 herunterladen, entpacken, öffnen und eine ausführbare Binärdatei bauen mit dem Befehl go build -o swag.exe .\cmd\swag\main.go.

Linux: go build -o swag.bin ./cmd/swag/main.go
macOS: go build -o swag.app ./cmd/swag/main.go

Danach können wir das gebaute Tool swag.exe neben unseren Server legen oder zum Pfad hinzufügen.

Bevor wir unsere Spezifikation generieren lassen können, müssen wir unseren Quellcode noch mit sinnvollen ‘Kommentaren’ versehen, die vom Generator gelesen werden können. Da dies hier keine OpenAPI-Anleitung ist, beschränken wir uns auf das Mindeste:

// @title       OpenAPI 3.1 Example
// @version     1.0
func main() {
    r := mux.NewRouter()
    r.HandleFunc("/success", GetSuccess).Methods(http.MethodGet)
    _ = http.ListenAndServe(":8003", r)
}

func GetSuccess(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "success")
}

Die Spezifikation generieren wir dann mit .\swag.exe init --v3.1.

Dies legt uns einen Ordner ./docs/ an, indem wir unsere swagger.json und swagger.yaml finden. Einmal kurz die go.mod aktualisieren: go mod tidy - und unser Code sollte wieder fehlerfrei sein.

Swagger-UI statisch hosten

Damit wir die Swagger-Spezifikations visuell wiedergeben können, brauchen wir Swagger-UI.

Eine UI-Distribution können wir uns via auf Github herunterladen.
Den Ordnerinhalt dist legen wir nun neben unseren Router in einen beliebig genannten Ordner (in unserem Fall swaggerui).
Als nächstes binden wir die Dateien über Embedding ein

//go:embed swaggerui
var Swaggerui embed.FS

und hosten das Dateisystem unter dem Pfad “/swaggerui/” über unseren Server:

// @title       OpenAPI 3.1 Example
// @version     1.0
func main() {
    r := mux.NewRouter()
    r.HandleFunc("/success", GetSuccess).Methods(http.MethodGet)

    fsys, _ := fs.Sub(Swaggerui, "swaggerui")
    r.PathPrefix("/swaggerui/").Handler(http.StripPrefix("/swaggerui/", http.FileServer(http.FS(fsys))))

    _ = http.ListenAndServe(":8003", r)
}

Eigene Spezifikation einfügen

Zuletzt müssen wir noch unsere eigene generierte Spezifikation bereitstellen.
Das machen wir, indem wir die Datei swagger.json aus dem Ordner /docs/ in den Ordner /swaggerui/ kopieren und in der Datei swagger-initialize.js, die den Wert der url ändern, der aktuell auf “https://petstore.swagger.io/v2/swagger.json” zeigt:

url: "./swagger.json",

Testen

Jetzt können wir den Server mit go run main.go laufen lassen und über unseren Webbrowser “https://localhost:8003/swaggerui/index.html” die Swagger-UI aufrufen.

Das ganze Codebeispiel

package main

import (
    "embed"
    "fmt"
    "io/fs"
    "net/http"

    "github.com/gorilla/mux"
)

//go:embed swaggerui
var swaggerui embed.FS

// @title       OpenAPI 3.1 Example
// @version     1.0
func main() {
    r := mux.NewRouter()
    r.HandleFunc("/success", GetSuccess).Methods(http.MethodGet)

    fsys, _ := fs.Sub(swaggerui, "swaggerui")
    r.PathPrefix("/swaggerui/").Handler(http.StripPrefix("/swaggerui/", http.FileServer(http.FS(fsys))))

    _ = http.ListenAndServe(":8003", r)
}

func GetSuccess(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "success")
}