- Введение
- Необходимые условия
- Как Swagger работает с Gin
- Настройка Swagger CLI
- Документирование сервера Gin
- Добавление маршрута для Swagger Docs
- Генерировать документацию swagger каждый раз при изменении строки doc
- Добавление общей информации об API на сервер Gin API
- Добавьте информацию об операциях API к конечным точкам API
- Бонус: Группа маршрутизаторов для /api/v1
- Заключение
Введение
В отличие от FastAPI, Gin не имеет встроенной интеграции OpenAPI. В FastAPI при добавлении маршрута уже создается документация. В Gin это не так. Но недавно я интегрировал Swagger UI в один из своих бэкендов Go и хотел задокументировать этот процесс.
Необходимые условия
- Уже существующий сервер Gin
Если вы начинаете с нуля, вы можете рассмотреть статью Building a Book Store API in Golang With Gin. Я буду использовать тот же книжный магазин и расширять его для интеграции Swagger UI.
Как Swagger работает с Gin
То, как Swagger работает с другими бэкендами Go, не отличается, у всех один и тот же механизм. Но как это работает на самом деле и что нам нужно делать на самом деле?
Swagger использует систему комментариев Go, которая, как мы знаем, очень хорошо интегрирована с документацией. Мы пишем комментарии заранее определенным способом, подробности которого мы увидим далее в посте. Но в основном все делится на 2 части. Сам сервер и маршруты.
Swagger имеет бинарник CLI, который при запуске преобразует эти документы комментариев в документацию, совместимую с OpenAPI. Результирующий файл также включает спецификации сервера OpenAPI в формате JSON и YAML.
Наконец, мы направляем сгенерированный контент через обработчик в нашем бэкенде.
Как мы это делаем? Давайте посмотрим.
Настройка Swagger CLI
Главный репозиторий, который вы должны держать под подушкой, – это https://github.com/swaggo/swag. Как в плане CLI, так и в плане документации.
Мы установим CLI-приложение под названием swag
. Вот как:
go get -u github.com/swaggo/swag/cmd/swag
# 1.16 or newer
go install github.com/swaggo/swag/cmd/swag@latest
Первая команда загрузит зависимости для интеграции в серверное приложение.
Вторая команда установит CLI.
Теперь вы должны быть в состоянии запустить команду swag:
$ swag -h
NAME:
swag - Automatically generate RESTful API documentation with Swagger 2.0 for Go.
USAGE:
swag [global options] command [command options] [arguments...]
VERSION:
v1.8.1
COMMANDS:
init, i Create docs.go
fmt, f format swag comments
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help (default: false)
--version, -v print the version (default: false)
Документирование сервера Gin
Если вы посмотрите на документацию Swagger UI для любого сайта с поддержкой OpenAPI, вы увидите нечто подобное:
Вы можете быть знакомы с нижней половиной пользовательского интерфейса. Именно для этого и предназначена документация Swagger, т. е. для документирования маршрутов.
Но прежде чем мы разберемся с нижней 50%-ной частью, я хочу, чтобы вы обратили внимание на верхнюю 50%-ную часть на рисунке выше. Прямо от заголовка Swagger Petstore до ссылки Find out more about Swagger anchor.
Эта часть показывает метаданные о самом сервере API. В этом разделе мы собираемся построить эту часть. Вы можете найти код для начала здесь. Мы продолжим работу над этим кодом.
Прежде всего, нам нужно получить пакеты, с которыми мы должны работать:
go get -u github.com/swaggo/files
go get -u github.com/swaggo/gin-swagger
Теперь, после того как мы это сделали, давайте посмотрим на наш файл main.go. На данный момент это main.go
:
package main
import "github.com/santosh/gingo/routes"
func main() {
router := routes.SetupRouter()
router.Run(":8080")
}
Добавление маршрута для Swagger Docs
Мы добавляем новый маршрутизатор в функции main:
router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
Измененный файл будет выглядеть следующим образом:
-import "github.com/santosh/gingo/routes"
+import (
+ "github.com/santosh/gingo/routes"
+ swaggerFiles "github.com/swaggo/files"
+ ginSwagger "github.com/swaggo/gin-swagger"
+)
func main() {
router := routes.SetupRouter()
+ router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
+
router.Run(":8080")
}
swaggerFiles.Handler
здесь находится обработчик, в который встроены все активы. Такие активы, как файлы HTML, CSS и javascript, отображаются в документации.
ginSwagger.WrapHandler
– это обертка вокруг http.Handler
, но для Gin.
Этого должно быть достаточно для тестирования. Давайте посмотрим, как отображается документация по адресу <адрес сервера>/docs/index.html
.
Хорошая новость – сайт документации API работает. Плохая новость – он не похож на обычный сайт документации API. Что мы пропустили?
Генерировать документацию swagger каждый раз при изменении строки doc
Документация Swagger хранится в собственной doc-строке Go. У нас есть специальный синтаксис, которому мы следуем. Подробнее об этом позже. Но сначала давайте узнаем, как генерировать документацию.
$ swag init
2022/05/25 23:59:16 Generate swagger docs....
2022/05/25 23:59:16 Generate general API Info, search dir:./
2022/05/25 23:59:16 create docs.go at docs/docs.go
2022/05/25 23:59:16 create swagger.json at docs/swagger.json
2022/05/25 23:59:16 create swagger.yaml at docs/swagger.yaml
Мы запускаем swag init
каждый раз, когда обновляем документацию для нашего API. Это генерирует 3 файла в подкаталоге docs/
.
$ tree docs
docs
├── docs.go
├── swagger.json
└── swagger.yaml
0 directories, 3 files
swagger.json
и swagger.yaml
– это фактическая спецификация, которую вы можете загрузить на такие сервисы, как AWS API Gateway и подобные. docs.go
– это код клея, который нам нужно импортировать в наш сервер.
Давайте теперь импортируем документацию в наш main.go.
package main
import (
+ _ "github.com/santosh/gingo/docs"
"github.com/santosh/gingo/routes"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
+// @title Gingo Bookstore API
func main() {
router := routes.SetupRouter()
Как вы видите, мы импортировали модуль docs
, указав полный путь к модулю. Мы также должны дополнить этот импорт с помощью _
, потому что он не используется в файле main.go в явном виде.
Вы также могли заметить строку // @title Gingo Bookstore API
. Обратите внимание на ее положение, так как это важно. Она находится прямо над функцией main()
. Также @title
здесь не случайно. Это одно из ключевых слов, которое документируется в разделе Общая информация об API. По ходу дела мы увидим больше подобных примеров, а пока давайте перегенерируем нашу документацию и просмотрим документацию API.
Похоже, у нас что-то получается. 😀
Добавление общей информации об API на сервер Gin API
В прошлом разделе мы видели аннотацию @title
. Она используется для задания заголовка для сервера API. Но это не единственная доступная аннотация. В Swagger существует широкий спектр аннотаций. Вы можете найти их применение в реальной жизни здесь.
Я собираюсь использовать некоторые из них для построения метаданных на моем сайте doc.
// @title Gin Book Service
// @version 1.0
// @description A book management service API in Go using Gin framework.
// @termsOfService https://tos.santoshk.dev
// @contact.name Santosh Kumar
// @contact.url https://twitter.com/sntshk
// @contact.email sntshkmr60@gmail.com
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
- Запустите
swag init
. - Повторно запустите сервер.
- Проверьте наличие обновлений:
Там есть некоторые метаданные.
Вы также могли заметить, что спецификация API внутри каталога docs/ изменилась. Например, вот файл swagger.yaml
из каталога docs/
.
{
"swagger": "2.0",
"info": {
- "title": "Gingo Bookstore API",
- "contact": {}
+ "description": "A book management service API in Go using Gin framework.",
+ "title": "Gin Book Service",
+ "termsOfService": "https://tos.santoshk.dev",
+ "contact": {
+ "name": "Santosh Kumar",
+ "url": "https://twitter.com/sntshk",
+ "email": "sntshkmr60@gmail.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+ },
+ "version": "1.0"
},
+ "host": "localhost:8080",
+ "basePath": "/api/v1",
"paths": {}
}
Теперь пришло время добавить документацию для конечных точек API.
Добавьте информацию об операциях API к конечным точкам API
Подобно общей информации об API для основного сервера, существует также информация об операциях API для отдельных маршрутов/конечных точек.
Они более разнообразны, чем те, которые я собираюсь использовать здесь. Те, которые я собираюсь использовать, являются лишь их подмножеством. Если вы хотите посмотреть несколько реальных примеров, вы можете найти их здесь на каждом из обработчиков маршрутов.
Вот модифицированный handlers/books.go
:
"github.com/santosh/gingo/models"
)
-// GetBooks responds with the list of all books as JSON.
+// GetBooks godoc
+// @Summary Get books array
+// @Description Responds with the list of all books as JSON.
+// @Tags books
+// @Produce json
+// @Success 200 {array} models.Book
+// @Router /books [get]
func GetBooks(c *gin.Context) {
c.JSON(http.StatusOK, db.Books)
}
-// PostBook takes a book JSON and store in DB.
+// PostBook godoc
+// @Summary Store a new book
+// @Description Takes a book JSON and store in DB. Return saved JSON.
+// @Tags books
+// @Produce json
+// @Param book body models.Book true "Book JSON"
+// @Success 200 {object} models.Book
+// @Router /books [post]
func PostBook(c *gin.Context) {
var newBook models.Book
@@ -28,7 +41,14 @@ func PostBook(c *gin.Context) {
c.JSON(http.StatusCreated, newBook)
}
-// GetBookByISBN locates the book whose ISBN value matches the isbn
+// GetBookByISBN godoc
+// @Summary Get single book by isbn
+// @Description Returns the book whose ISBN value matches the isbn.
+// @Tags books
+// @Produce json
+// @Param isbn path string true "search book by isbn"
+// @Success 200 {object} models.Book
+// @Router /books/{isbn} [get]
func GetBookByISBN(c *gin.Context) {
isbn := c.Param("isbn")
- Запустите
swag init
. - Повторно запустите сервер.
- Проверьте наличие обновлений:
Вот обновленный swagger.yaml
:
basePath: /api/v1
+definitions:
+ models.Book:
+ properties:
+ author:
+ type: string
+ isbn:
+ type: string
+ title:
+ type: string
+ type: object
host: localhost:8080
info:
contact:
@@ -12,5 +22,58 @@ info:
termsOfService: https://tos.santoshk.dev
title: Gin Book Service
version: "1.0"
-paths: {}
+paths:
+ /books:
+ get:
+ description: Responds with the list of all books as JSON.
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.Book'
+ type: array
+ summary: Get books array
+ tags:
+ - books
+ post:
+ description: Takes a book JSON and store in DB. Return saved JSON.
+ parameters:
+ - description: Book JSON
+ in: body
+ name: book
+ required: true
+ schema:
+ $ref: '#/definitions/models.Book'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.Book'
+ summary: Store a new book
+ tags:
+ - books
+ /books/{isbn}:
+ get:
+ description: Returns the book whose ISBN value matches the isbn.
+ parameters:
+ - description: search book by isbn
+ in: path
+ name: isbn
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.Book'
+ summary: Get single book by isbn
+ tags:
+ - books
swagger: "2.0"
Обратите внимание на добавление новых разделов definitions
и paths
. Они предназначены для моделей и конечных точек соответственно.
Бонус: Группа маршрутизаторов для /api/v1
Всегда полезно добавлять к маршрутам api/v1
. Бит v1
имеет логическое обоснование. Он предназначен для тех случаев, когда вам нужно внести изменение, которое не имеет обратной совместимости. Возможно, на входе или выходе есть изменения, которые могут сломать миллионы зависимых клиентов.
В таких ситуациях вы увеличиваете версию до чего-то вроде api/v2
и позволяете старому API-серверу обслуживать старые запросы от старого обработчика.
Сейчас все маршруты в gingo server начинаются с /book
. Мы собираемся изменить это.
Вот измененный routes/routes.go
.
func SetupRouter() *gin.Engine {
router := gin.Default()
- router.GET("/books", handlers.GetBooks)
- router.GET("/books/:isbn", handlers.GetBookByISBN)
- // router.DELETE("/books/:isbn", handlers.DeleteBookByISBN)
- // router.PUT("/books/:isbn", handlers.UpdateBookByISBN)
- router.POST("/books", handlers.PostBook)
+
+ v1 := router.Group("/api/v1")
+ {
+ v1.GET("/books", handlers.GetBooks)
+ v1.GET("/books/:isbn", handlers.GetBookByISBN)
+ // router.DELETE("/books/:isbn", handlers.DeleteBookByISBN)
+ // router.PUT("/books/:isbn", handlers.UpdateBookByISBN)
+ v1.POST("/books", handlers.PostBook)
+ }
return router
}
Здесь мы используем router.Group
для создания группы с путем /api/v1
. Затем мы переносим все определения маршрутов в группу и окружаем ее скобками. Вот как мы создаем маршрут пути в Gin.
Заключение
Документация API – это важная часть документации API. Вместо того чтобы документировать конечные точки где-либо еще, мы можем документировать маршруты прямо в коде. Таким образом, у нас будет только один единственный источник правды. Нет необходимости поддерживать код и документацию отдельно. В свою очередь, мы получаем всегда актуальную документацию.
Каждый бэкенд-сервер в той или иной степени поддерживает Swagger UI. Я описал основы для Gin, но если вы используете какой-либо другой фреймворк, я советую вам поискать свой собственный фреймворк.