layout | category | author | tags | title | ||||||
---|---|---|---|---|---|---|---|---|---|---|
post |
documentation |
Lucas Natraj |
|
Swagger Generation from a Go Web Application |
Application Structure:
. <------------------------------ GOPATH
├── bin
│ └── ...
├── pkg
│ └── ...
├── src
│ ├── ...
│ └── notes
│ ├── app
│ │ ├── app.go
│ │ └── notes.yaml
│ ├── vendor <------------- 3rd party dependencies
│ │ └── ...
│ ├── glide.lock <--------- Dependency versions
│ ├── glide.yaml <--------- Dependency list
│ └── main.go
├── swagger-aux.json
└── swagger.json <--------------- Generated Swagger
The implementation below is a fairly straightforward note management application, with the following REST-ful api -
- POST /notes - Add a new note
- GET /notes - Get all notes
- GET /notes/:index - Get note at specified index
- PUT /notes/:index - Update note at specified index
- DELETE /notes/:index - Delete note at specified index
// ------- src/notes/app/app.go -------
// Package app Notes Service.
//
// Notes management service
//
// Schemes: http
// Host: localhost:3000
// BasePath: /
// Version: 1.0.0
//
// Produces:
// - application/json
//
//
// swagger:meta
//
// go:generate swagger generate spec -o swagger.json
package app
import (
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/pat"
"github.com/rs/cors"
)
// NotesService is the main service that manages the Notes
type NotesService struct {
Notes []Note
}
// NewNotesService creates a new instance of the NotesService
func NewNotesService() *NotesService {
t := new(NotesService)
t.Notes = make([]Note, 0)
return t
}
// Singleton to store all the notes
var noteService = NewNotesService()
// Info swagger:route GET /info info info-status
//
// Get service information
//
// Get service information
//
// Security:
// my_auth: email
//
// Responses:
// 200: InfoResponse
func (ns *NotesService) Info(w http.ResponseWriter, r *http.Request) {
info := &Info{
Service: "Notes",
Status: "ok",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(info)
}
// AddNote swagger:route POST /notes notes notes-add
//
// Add a new note
//
// Adds a new note
//
// Security:
// my_auth: email
//
// Responses:
// 200: Success
func (ns *NotesService) AddNote(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
var body AddNoteRequestBody
err := decoder.Decode(&body)
if err != nil {
panic(nil)
}
var Note = Note{
Title: body.Title,
Content: body.Content,
}
ns.Notes = append(noteService.Notes, Note)
}
// FetchAllNotes swagger:route GET /notes notes notes-fetchAll
//
// Fetches all the notes
//
// Returns the entire list of notes
//
// Security:
// my_auth: email
//
// Responses:
// 200: FetchAllNotesResponse
func (ns *NotesService) FetchAllNotes(w http.ResponseWriter, r *http.Request) {
body := &FetchAllNotesResponseBody{
Notes: ns.Notes,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(body)
}
// GetNote swagger:route GET /notes/{id} notes notes-fetchOne
//
// Fetch a single note
//
// Fetch a single note by index
//
// Security:
// my_auth: email
//
// Responses:
// 200: FetchNoteResponse
func (ns *NotesService) GetNote(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
id := params.Get(":id")
if len(id) == 0 {
http.Error(w, "missing note id", http.StatusBadRequest)
return
}
index, err := strconv.Atoi(id)
if err != nil || (index < 0 || index >= len(ns.Notes)) {
http.Error(w, "invalid note id", http.StatusBadRequest)
return
}
body := &FetchNoteResponse{
Body: &Note{
Title: ns.Notes[index].Title,
Content: ns.Notes[index].Content,
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(body)
}
// RemoveNote swagger:route DELETE /notes/{id} notes notes-remove
//
// Removes a single note
//
// Removes a single note by index
//
// Security:
// my_auth: email
//
// Responses:
// 200: Success
func (ns *NotesService) RemoveNote(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
id := params.Get(":id")
if len(id) == 0 {
http.Error(w, "missing note id", http.StatusBadRequest)
return
}
index, err := strconv.Atoi(id)
if err != nil || (index < 0 || index >= len(ns.Notes)) {
http.Error(w, "invalid note id", http.StatusBadRequest)
return
}
ns.Notes = append(ns.Notes[:index], ns.Notes[index+1:]...)
}
// UpdateNote swagger:route PUT /notes/{id} notes notes-update
//
// Updates a single note
//
// Updates the note at the specified index
//
// Security:
// my_auth: email
//
// Responses:
// 200: Success
func (ns *NotesService) UpdateNote(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
id := params.Get(":id")
if len(id) == 0 {
http.Error(w, "missing note id", http.StatusBadRequest)
return
}
index, err := strconv.Atoi(id)
if err != nil || (index < 0 || index >= len(ns.Notes)) {
http.Error(w, "invalid note id", http.StatusBadRequest)
return
}
decoder := json.NewDecoder(r.Body)
var body AddNoteRequestBody
err = decoder.Decode(&body)
if err != nil {
panic(nil)
}
ns.Notes[index] = Note{
Title: body.Title,
Content: body.Content,
}
}
// initialize the identity service endpoints
func init() {
r := pat.New()
r.Get("/info", noteService.Info)
r.Get("/notes/{id}", noteService.GetNote)
r.Delete("/notes/{id}", noteService.RemoveNote)
r.Put("/notes/{id}", noteService.UpdateNote)
r.Get("/notes", noteService.FetchAllNotes)
r.Post("/notes", noteService.AddNote)
c := cors.New(cors.Options{
AllowedHeaders: []string{"*"},
AllowedMethods: []string{"GET", "POST", "DELETE", "PUT"},
})
handler := c.Handler(r)
http.Handle("/", handler)
}
// ------- src/notes/app/messages.go -------
package app
// Note is the persistence format of a note
type Note struct {
Title string `json:"title"`
Content string `json:"content"`
}
// Info is the response format for the Info request
type Info struct {
Service string `json:"service"`
Status string `json:"status"`
}
// InfoResponse is the response to the Info request
//
// swagger:response
type InfoResponse struct {
// required: true
// in: body
Body *Info `json:"body"`
}
// AddNoteRequest is the request structure
//
// swagger:parameters notes-add
type AddNoteRequest struct {
// Payload
//
// required: true
// in: body
Body *AddNoteRequestBody
}
// AddNoteRequestBody is the body of the AddNoteRequest request
//
// swagger:model
type AddNoteRequestBody struct {
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
}
// FetchAllNotesResponse is the response containing the list of Notes
//
// swagger:response
type FetchAllNotesResponse struct {
// required: true
// in: body
Notes *FetchAllNotesResponseBody `json:"notes"`
}
// FetchAllNotesResponseBody is the body of the FetchAllNotesResponse response
type FetchAllNotesResponseBody struct {
// The Note List
Notes []Note `json:"notes"`
}
// FetchNoteRequest is the request for the FetchNote request
// swagger:parameters notes-fetchOne
type FetchNoteRequest struct {
// Index of Note
//
// required: true
// in: path
// minimum: 0
// default: 0
NoteID int `json:"id" binding:"required"`
}
// FetchNoteResponse is the response when fetching a specific note
//
// swagger:response
type FetchNoteResponse struct {
// required: true
// in: body
Body *Note `json:"body"`
}
// DeleteNoteRequest is the request for deleting a note
//
// swagger:parameters notes-remove
type DeleteNoteRequest struct {
// Index of the note to be removed
//
// required: true
// in: path
// minimum: 0
// default: 0
NoteID int `json:"id" binding:"required"`
}
// UpdateNoteRequest is the request for updating a specific note
//
// swagger:parameters notes-update
type UpdateNoteRequest struct {
// Index of Note to be updated
//
// required: true
// in: path
// minimum: 0
// default: 0
NoteID int `json:"id" binding:"required"`
// Payload
//
// required: true
// in: body
Body *AddNoteRequestBody
}
// Success is the response for a successful request
//
// swagger:response
type Success struct {
}
Providing security definitions is done via an auxilliary file that is merged in during the spec generation process.
------- swagger-aux.json -------
{
"securityDefinitions": {
"my_auth": {
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth",
"scopes": {
"email": "default scope"
}
}
}
}
Instructions at https://github.com/swagger-api/swagger-ui
-
To run the Swagger Editor locally using docker:
docker pull swaggerapi/swagger-editor docker run -d -p 80:8080 swaggerapi/swagger-editor
Then, navigate to
http://locahost
(or your local docker ip) -
To run the Swagger Editor locally using Node:
git clone https://github.com/swagger-api/swagger-editor.git cd swagger-editor npm install npm start
Use glide.
# in src/notes
$ glide install
Use gin.
go get github.com/codegangsta/gin
$GOPATH/bin/gin
Note: The web application should query the PORT environment variable to know on which port to listen:
// ------- src/notes/main.go -------
package main
import (
"net/http"
"os"
)
func getenv(key string, fallback string) string {
v := os.Getenv(key)
if len(v) != 0 {
return v
}
return fallback
}
// used for local testing / debugging
func main() {
http.ListenAndServe(":" + getenv("PORT", "8080"), nil)
}
Swagger generation fo Go applications uses go-swagger.
The documentation at https://goswagger.io describes the various go documentation tags that are required for generating swagger documentation.
The following commands should be executed at $GOPATH:
go get -u github.com/go-swagger/go-swagger/cmd/swagger
./bin/swagger generate spec --input=./swagger-aux.json --output=./swagger.json --base-path=./src/notes/app/
Note: The swagger editor does display some warnings for unused attributes and definitions, which I have not been able to eliminate. The good news is that these warnings have no noticeable impact on the resulting rendered swagger documentation page.