Skip to content

swag

swaggo/swag — это реализация Swagger API 2.0 для языка Go, позволяющая генерировать документы интерфейсов типа swagger.json и swagger.yaml путём написания комментариев в определённом формате, что удобно для экспорта и импорта.

Репозиторий: swaggo/swag: Automatically generate RESTful API documentation with Swagger 2.0 for Go. (github.com)

Документация: swaggo/swag: Automatically generate RESTful API documentation with Swagger 2.0 for Go. (github.com)

swag по умолчанию поддерживает следующие веб-фреймворки. В этой статье в качестве примера используется gin для демонстрации быстрой генерации документов интерфейсов с помощью gin и swagger.

TIP

Если вы не знакомы с синтаксисом swagger, можете ознакомиться: About Swagger Specification | Documentation | Swagger

Установка

Сначала загрузите инструмент командной строки swagger

go install github.com/swaggo/swag/cmd/swag@latest

Затем загрузите зависимости исходного кода swagger

go get github.com/swaggo/swag

TIP

Во избежание проблем версии обоих должны совпадать.

Затем загрузите библиотеку статических файлов swagger: html, css, js и т.д., встроенные в код Go.

go get github.com/swaggo/files@latest

Наконец, загрузите библиотеку адаптации swagger для gin

go get github.com/swaggo/gin-swagger@latest

Поскольку в этой статье используется только gin в качестве примера, адаптеры для других веб-фреймворков можно изучить самостоятельно, в основном они похожи.

Использование

Создайте минимальный проект Go с помощью go mod, создайте main.go, запишите следующее содержимое.

go
package main

import (
  "fmt"
  "github.com/gin-gonic/gin"
)

// @title           Swagger Example API
// @version         1.0
// @description     This is a sample server celler server.

// @contact.name   API Support
// @contact.url    http://www.swagger.io/support
// @contact.email  support@swagger.io

// @BasePath  /api/v1
func main() {
  engine := gin.Default()
  engine.GET("/api/v1/ping", Ping)
  engine.Run(":80")
}

// Ping godoc
// @Summary      say hello world
// @Description  return hello world json format content
// @param       name query    string  true  "name"
// @Tags         system
// @Produce      json
// @Router       /ping [get]
func Ping(ctx *gin.Context) {
  ctx.JSON(200, gin.H{
    "message": fmt.Sprintf("Hello World!%s", ctx.Query("name")),
  })
}

Это очень простой пример gin web. Комментарии в функции main — это базовая информация документа, Ping — обычный интерфейс. Далее выполните команду для генерации документа, по умолчанию в каталоге docs на одном уровне с main.go

swag init

Измените код main.go

go
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    // Анонимный импорт сгенерированного пакета документов интерфейсов
    _ "golearn/docs"
)

// @title           Swagger Example API
// @version         1.0
// @description     This is a sample server celler server.

// @contact.name   API Support
// @contact.url    http://www.swagger.io/support
// @contact.email  support@swagger.io

// @BasePath  /api/v1
func main() {
    engine := gin.Default()
    // Зарегистрировать маршрут статических файлов swagger
    engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    engine.GET("/api/v1/ping", Ping)
    engine.Run(":80")
}

// Ping godoc
// @Summary      say hello world
// @Description  return hello world json format content
// @param       name query    string  true  "name"
// @Tags         system
// @Produce      json
// @Router       /ping [get]
func Ping(ctx *gin.Context) {
    ctx.JSON(200, gin.H{
       "message": fmt.Sprintf("Hello World!%s", ctx.Query("name")),
    })
}

Запустите программу, посетите 127.0.0.1/swagger/index.html, интерфейс будет следующим

Таким образом, запущен базовый документ интерфейсов. Далее, кроме некоторых особых моментов, в основном нет большой разницы с использованием на других языках.

Параметры

Формат определения параметров

@param name paramtype datatype isRequired comment

Пример

go
@param userId query int true "user unique id"

Поддерживаемые типы параметров

  • query
  • path
  • header
  • body
  • formData

Типы данных

  • string (string)
  • integer (int, uint, uint32, uint64)
  • number (float32)
  • boolean (bool)
  • пользовательская структура

Тип параметра также может быть вашим типом, при условии, что он может быть просканирован swagger.

Ответы

Базовый формат определения ответа интерфейса

go
// @Success      200  {array}   model.Account
// @Failure      400  {object}  httputil.HTTPError
// @Failure      404  {object}  httputil.HTTPError
// @Failure      500  {object}  httputil.HTTPError

Состоит из кода состояния, базового типа, типа данных. {array} означает, что это массив, будет показана форма массива типа данных, {object} покажет оригинальную форму типа данных. Например, обычно мы определяем единый ответ

go
type JSONResult struct {
    Code    int          `json:"code" `
    Message string       `json:"message"`
    Data    interface{}  `json:"data"`
}

Тип поля Data не определён, при описании примеров ответа можно комбинировать следующим образом

go
// Комбинация
@success 200 {object} jsonresult.JSONResult{data=Account} "desc"

// Массив
@success 200 {object} jsonresult.JSONResult{data=[]Account} "desc"

Модели

Комментарии к полям структур будут просканированы swagger как комментарии полей модели

go
package model

type Account struct {
  // account id
    ID   int    `json:"id" example:"1"`
    // username
    Name string `json:"name" example:"account name"`
}

Значение тега example будет отображаться как пример значения на странице, конечно, он также поддерживает ограничения полей

go
type Foo struct {
    Bar string `minLength:"4" maxLength:"16"`
    Baz int `minimum:"10" maximum:"20" default:"15"`
    Qux []string `enums:"foo,bar,baz"`
}

Все модели должны быть просканированы swagger при использовании, иначе не будут работать.

Аутентификация

В части аутентификации поддерживается

  • Basic Auth
  • API Key
  • OAuth2 app auth
  • OAuth2 implicit auth
  • OAuth2 password auth
  • OAuth2 access code auth

Если аутентификация интерфейса использует JWT, хранится в поле заголовка Authorization, можно определить следующим образом

go
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization

По сути это просто apikey, если вы передаёте bearer token, нужно вручную добавить префикс Bearer.

Затем в интерфейсах, требующих аутентификации, добавьте следующие комментарии

go
// @security Bearer

Его значение — имя, определённое в securityDefinitions.

Конфигурация

swag фактически хранит несколько различных экземпляров swagger в map, ginSwagger как адаптер читает doc.json из экземпляра, то есть файл определения API интерфейсов, swaggerFiles предоставляет статические HTML-файлы для отображения веб-страниц, разбирает определение API и генерирует интерфейс. Поняв весь процесс, можно выполнять пользовательские операции.

go
// Name — уникальное имя для регистрации экземпляра swag.
// Имя экземпляра по умолчанию
const Name = "swagger"

var (
  swaggerMu sync.RWMutex
    // Таблица экземпляров
  swags     map[string]Swagger
)
go
func CustomWrapHandler(config *Config, handler *webdav.Handler) gin.HandlerFunc {
    var once sync.Once

    if config.InstanceName == "" {
       config.InstanceName = swag.Name
    }

    if config.Title == "" {
       config.Title = "Swagger UI"
    }

    // создать шаблон с именем
    index, _ := template.New("swagger_index.html").Parse(swaggerIndexTpl)

    var matcher = regexp.MustCompile(`(.*)(index\.html|doc\.json|favicon-16x16\.png|favicon-32x32\.png|/oauth2-redirect\.html|swagger-ui\.css|swagger-ui\.css\.map|swagger-ui\.js|swagger-ui\.js\.map|swagger-ui-bundle\.js|swagger-ui-bundle\.js\.map|swagger-ui-standalone-preset\.js|swagger-ui-standalone-preset\.js\.map)[?|.]*`)

    return func(ctx *gin.Context) {
       if ctx.Request.Method != http.MethodGet {
          ctx.AbortWithStatus(http.StatusMethodNotAllowed)

          return
       }

       // Сопоставление маршрута
       matches := matcher.FindStringSubmatch(ctx.Request.RequestURI)

       if len(matches) != 3 {
          ctx.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))

          return
       }

       path := matches[2]
       once.Do(func() {
          handler.Prefix = matches[1]
       })

       switch filepath.Ext(path) {
       case ".html":
          ctx.Header("Content-Type", "text/html; charset=utf-8")
       case ".css":
          ctx.Header("Content-Type", "text/css; charset=utf-8")
       case ".js":
          ctx.Header("Content-Type", "application/javascript")
       case ".png":
          ctx.Header("Content-Type", "image/png")
       case ".json":
          ctx.Header("Content-Type", "application/json; charset=utf-8")
       }

       switch path {
       // Главная страница
       case "index.html":
          _ = index.Execute(ctx.Writer, config.toSwaggerConfig())
       // Файл описания API
       case "doc.json":
          doc, err := swag.ReadDoc(config.InstanceName)
          if err != nil {
             ctx.AbortWithStatus(http.StatusInternalServerError)

             return
          }

          ctx.String(http.StatusOK, doc)
       default:
          handler.ServeHTTP(ctx.Writer, ctx.Request)
       }
    }
}

Автоматическая регистрация экземпляров через сгенерированный код Go. Ниже приведена часть автоматически сгенерированного кода docs.go

go
// SwaggerInfo хранит экспортированную информацию Swagger для модификации клиентами
var SwaggerInfo = &swag.Spec{
  Version:          "",
  Host:             "",
  BasePath:         "",
  Schemes:          []string{},
  Title:            "",
  Description:      "",
  InfoInstanceName: "swagger",
  SwaggerTemplate:  docTemplate,
  LeftDelim:        "{{",
  RightDelim:       "}}",
}

func init() {
    // Зарегистрировать экземпляр
  swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

Можно видеть, что в функции init есть функция Register для регистрации текущего экземпляра. Если хотите изменить имя экземпляра, не рекомендуется редактировать этот файл, поскольку файл docs.go генерируется автоматически. Просто используйте параметр --instanceName appapi при генерации кода. Для удобства можно использовать команду go generate, встроенную в файл Go, для удобной автоматической генерации кода, как ниже.

go
// swagger declarative api comment

// @title App Internal API Documentation
// @version v1.0.0
// @description Wilson api documentation
// @BasePath /api/v1
//go:generate swag init --generatedTime --instanceName appapi -g api.go -d ./ --output ./swagger

Лично мне не нравится писать общие комментарии swagger в main.go или функции main, лучше всего написать эти комментарии над go generate.

TIP

Если требуется несколько экземпляров, обязательно сохраняйте уникальность имён экземпляров, иначе будет panic

Для настройки конфигурации нужно использовать ginSwagger.CustomWrapHandler, он по сравнению с первым имеет дополнительный параметр Config, описание如下

go
// Config хранит переменные конфигурации ginSwagger.
type Config struct {
  // URL, указывающий на определение API (обычно swagger.json или swagger.yaml). По умолчанию `doc.json`.
  URL                      string
    // Состояние развёртывания списка интерфейсов
  DocExpansion             string
    // Имя экземпляра
  InstanceName             string
    // Заголовок
  Title                    string
    // Глубина развёртывания
  DefaultModelsExpandDepth int
    // Как следует из названия
  DeepLinking              bool
  PersistAuthorization     bool
  Oauth2DefaultClientID    string
}

Используйте swaggerFiles.NewHandler() вместо обработчика по умолчанию, особенно при нескольких экземплярах.

go
engine.GET(openapi.ApiDoc, ginSwagger.CustomWrapHandler(openapi.Config, swaggerFiles.NewHandler()))

Кроме того, можно выполнять переопределение типов и другие операции, всё довольно просто. Больше содержимого можно прочитать в официальной документации.

Примечания

  1. swag генерирует файл описания интерфейсов openapi на основе комментариев. При генерации указанный каталог должен содержать базовую информацию документа интерфейсов, по умолчанию ищется в main.go

  2. swag init по умолчанию указывает текущий каталог, значение ./, можно использовать swag init -d для указания нескольких каталогов, разделённых запятой. Первый указанный каталог должен содержать базовую информацию документа интерфейсов. Например

    swag init -d ./,./api
  3. -g, файл хранения базовой информации документа интерфейсов можно настроить, по умолчанию main.go, при генерации документа используйте параметр -g для указания имени файла

    swag init -g api.go -d ./,./api

    Эта команда означает разбор базовой информации документа интерфейсов в ./api.go, одновременно поиск и разбор комментариев других интерфейсов в каталогах ./ и ./api и генерацию соответствующих документов.

  4. Параметр -o может указать путь вывода файла описания документа, по умолчанию ./docs, например:

    swag init -o ./api/docs
  5. --ot может указать тип выходного файла, по умолчанию (docs.go, swagger.json, swagger.yaml). Если хотите загрузить swagger ui программой Go, файл go необходим.

    swag init --ot go,yaml

    Остальные сгенерированные json и yaml файлы удобны для импорта данных в другие системы управления интерфейсами.

  6. Где писать комментарии — неважно, даже если не писать в функции, всё равно будет разобрано, просто запись в функции улучшает читаемость. По сути это DSL, встроенный в комментарии и исходный код Go.

  7. swag поддерживает много других параметров, можно использовать swag init -h для просмотра.

Golang by www.golangdev.cn edit