In this article, we explore the powerful tool 'go-swagger', and how it can be used to generate Swagger documentation files directly from Go code. Swagger, also known as OpenAPI, is a widely-accepted standard for designing, building, and documenting REST APIs, providing interactive documentation, client SDK generation, and API discoverability.
As a beginner software engineer, understanding and implementing efficient documentation practices is critical. Documenting your commercial code helps your colleagues better understand the design and function of your codebase, making it easier to maintain and expand upon. However, manually writing API documentation can be time-consuming and error-prone.
That's where tools like go-swagger come in. This open-source project can generate Swagger specification documentation automatically from your Go code. This means you'll be able to focus on developing your code, while also maintaining clear, accurate, and up-to-date API documentation with minimal effort.
The generation of a Swagger specification from the source code of a Go program is done by two things:
using go-swagger
utility, which is called from the command line;
API wrappers in Go-code, i.e., some preparation needs to be done before in the code so that the generation utility will generate what you expect to see;
go get -u github.com/go-swagger/go-swagger/cmd/swagger
We need to create a docs
directory at the external level of the project, in which we create a single docs.go
file inside. In fact, you can put the directory anywhere, as long as the package is called docs
, and the name of the file does not matter. I will give here an example of the content of the docs.go
file, so that it will be clear enough for understanding:
// Package classification Hellow Hachernoon
//
// Documentation for my go project
//
// Schemes: http
// BasePath: /
// Version: 1.0.0
// Host:
//
// Consumes:
// - application/json
// - multipart/form-data
//
// Produces:
// - application/json
//
// Security:
// - basic
//
// SecurityDefinitions:
// basic:
// type: basic
//
// swagger:meta
package docs
A couple of comments:
Security
and SecurityDefinitions
sections at all if there is no authorization on the server;Consumes
specifies a list of content types that are used in your requests. In most cases, a single application/json
is sufficient, but don't forget to include multipart/form-data
or something else if you're using other content types in requests. The same with Produces
.
Importing docs
In your main.go
looks like this:
import _ "path/to/docs"
Don’t forget to import it, otherwise, you will catch the joy of the fact that empty documentation will be generated without errors.
Write all the binding in the same docs
package, it can be in one file, it can be in several, or even in docs.go
, it doesn't matter. Perhaps, you can write wrappers right where your handlers are, but I haven’t tried it, so I won’t argue with it, but in this case, you need to start importing these wrappers in the docs
package.
For example, let's create a docs/routes.go
file. Let's describe a wrapper for one /payloads
route in it:
// swagger:route GET /payload payloads GetPayload
// Get payload with given ID.
// responses:
// 200: GetPayloadRes200
// swagger:parameters GetPayload
type GetPayloadReq struct {
core.GetPayloadReq
}
// swagger:response GetPayloadRes200
type GetPayloadRes200 struct {
// in:body
Body core.GetPayloadRes
}
/payload
- a route URL;payloads
- a section name (for grouping in Swagger UI);GetPayload
- request identifier, the name can be anything. It just must be unique for each route, and it is used below for linking with the request parameter description block;Get payload with given ID.
- request description for Swagger UI;GetPayloadRes200
. The status (200) and the name of the Go-type corresponding to the description of this response (GetPayloadRes200
) are indicated. It is important to note that it is not necessary to describe literally all possible responses, such as 500 Internal Server Error, for example, since this is already clear that it can always happen. It is logical to describe those responses that are separately registered in the request handler, such as 200, 400, 404;// swagger:parameters GetPayload
says that the type, written after this comment, determines the format of the request input with the GetPayload
ID. It describes both Body-parameters and Query-parameters, in general, all input is there;GetPayloadReq
- type name with input parameters, can be any, no restrictions, but probably should be public;core.GetPayloadReq
- just embedding a type with a real description of the input parameters into a given type used to generate documentation. The idea is simple. You write your own route handlers, they have input parameters for which you described the structure, which, of course, is stored somewhere else, convenient for you. In this structure, for generation, we simply refer to this structure with input parameters, so that we do not have to go into this docs package every time we change the input parameters. And this is very cool: when adding, changing, or deleting parameters, both input, and output, nothing needs to be edited in docs
at all! But note that new types of query responses will still require edits in this package;// swagger:response GetPayloadRes200
says that the type, written after this comment, determines the format of the requested output. The status code is described above, so the name GetPayloadRes200
does not have to follow any rules. Call it whatever you want, but in order not to get confused and to make everything as generic as possible, I recommend naming such things according to some pattern, for example, the name of the request + Req, + ResXXX, etc.;core.GetPayloadRes
see the POST request section below.
This type of request is characterized by the following details:
When describing input parameters, embed the type with the description of the parameters, i.e., not Body core.GetPayload
, just core.GetPayload
;
Each parameter must be a Query parameter, i.e. /payload?id=...,
these are the parameters we are talking about. Well, the parameters that are inside the URL. If we discard the parameters inside the URL, then the description of the GET request parameters should look like this:
type GetPayloadReq struct {
// in:query
PayloadID string `query:"PayloadID"`
}
It is important here, firstly, to write // in:query
(once for the entire list of Query parameters), and secondly, to specify the query:"paramName"
tag for each field. The structure of the output parameters follows the same rules as the input parameters for a POST request, since the output is always Body.
This type of request is characterized by the following details:
When describing input parameters, you should not use embedding, but specify the Body field:
// swagger:parameters NewPayload
type NewPayloadReq struct {
// in:body
Body core.NewPayloadReq
}
// in:body
, and secondly, to put the name of the Body
field;json:"paramName"
JSON tag for each input field, if you, of course, use JSON in requests. For form data, do not use a tag, use a // in: formData
comment before the parameter. For details such as the description of the parameter for uploading a file to the server, see the documentation or ask your colleagues.cd /path/to/your/project
SWAGGER_GENERATE_EXTENSION=false swagger generate spec -o /path/to/swagger.yaml
It is important that your project has main.go
file at the top level, because the go-swagger utility relies on when the generation starts.
SWAGGER_GENERATE_EXTENSION
is an optional setting that removes the following default behavior: Go type descriptions in swagger model descriptions. In other words, by default, the utility saves information about what Go-types are behind each field of each model and stores this in special attributes that Swagger UI
. In my opinion, it is absolutely unnecessary information for users of the Swagger specification.