Skip to content

swag

swaggo/swag é uma implementação da Swagger API 2.0 na linguagem Go, gerando documentos de interface nos formatos swagger.json e swagger.yaml através de comentários em formato específico, facilitando exportação e importação.

Repositório: swaggo/swag: Automatically generate RESTful API documentation with Swagger 2.0 for Go. (github.com)

Documentação: swaggo/swag: Automatically generate RESTful API documentation with Swagger 2.0 for Go. (github.com)

O swag suporta por padrão os seguintes frameworks web listados abaixo, este artigo usa gin como exemplo para demonstrar como gerar rapidamente documentação de API com gin e swagger.

TIP

Se não estiver familiarizado com a sintaxe swagger, consulte About Swagger Specification | Documentation | Swagger

Instalação

Primeiro, baixe a ferramenta de linha de comando swagger

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

Em seguida, baixe as dependências do código-fonte swagger

go get github.com/swaggo/swag

TIP

Para evitar problemas, as versões de ambos devem ser consistentes.

Em seguida, baixe a biblioteca de arquivos estáticos swagger, html, css, js, etc., todos incorporados no código Go.

go get github.com/swaggo/files@latest

Finalmente, baixe a biblioteca de adaptação swagger para gin

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

Como este artigo usa apenas gin como exemplo, adaptadores para outros frameworks web devem ser consultados individualmente, basicamente são muito similares.

Uso

Crie um projeto Go mais básico usando go mod, crie main.go e escreva o seguinte conteúdo.

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")),
  })
}

Este é um exemplo simples de gin web, os comentários na função main são informações básicas do documento, a função Ping é uma interface comum. Em seguida, execute o comando para gerar o documento, por padrão está no diretório docs no mesmo nível de main.go

swag init

Modifique o código main.go

go
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    // importação anônima do pacote de documentação de interface gerado
    _ "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()
    // registra rota de arquivos estáticos 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")),
    })
}

Execute o programa e acesse 127.0.0.1/swagger/index.html, a interface é a seguinte

Assim, um documento de interface básico foi executado. A seguir, exceto por alguns pontos especialmente importantes, basicamente não há muita diferença em relação ao uso em outras linguagens.

Parâmetros

O formato para definir parâmetros é

@param name paramtype datatype isRequired comment

Um exemplo é o seguinte

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

Os tipos de parâmetro suportados são

  • query
  • path
  • header
  • body
  • formData

Os tipos de dados são

  • string (string)
  • integer (int, uint, uint32, uint64)
  • number (float32)
  • boolean (bool)
  • struct definido pelo usuário

O tipo de parâmetro também pode ser seu próprio tipo, desde que possa ser escaneado pelo swagger.

Resposta

O formato básico para definir resposta de interface é o seguinte

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

Composto por código de status, tipo básico e tipo de dados. {array} indica que é um array e mostrará a forma de array do tipo de dados, {object} mostrará a forma original do tipo de dados. Por exemplo, geralmente definimos um corpo de resposta unificado

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

O tipo do campo Data é incerto, ao descrever casos de uso de resposta, pode combiná-lo, como segue

go
// combinação
@success 200 {object} jsonresult.JSONResult{data=Account} "desc"

// array
@success 200 {object} jsonresult.JSONResult{data=[]Account} "desc"

Modelos

Adicionar comentários a campos de struct será escaneado pelo swagger como comentários de campo de modelo

go
package model

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

O valor da tag example será exibido como valor de exemplo na página, claro, também suporta limitações de campo

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

Todos os modelos devem ser escaneados pelo swagger ao usar, caso contrário não funcionarão.

Autenticação

Na parte de autenticação, suporta

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

Se a autenticação de interface usar JWT, armazenada no campo Authorization do header, podemos definir da seguinte forma

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

Essencialmente, isso é apenas uma apikey, se você transmitir um bearer token, precisará adicionar manualmente o prefixo Bearer.

Em seguida, adicione os seguintes comentários nas interfaces que requerem autenticação

go
// @security Bearer

Seu valor é o nome definido em seu securityDefinitions.

Configuração

O swag na verdade armazena múltiplas instâncias swagger diferentes em um map, ginSwagger como adaptador lê doc.json do arquivo de definição de API, swaggerFiles fornece arquivos HTML estáticos para exibição de páginas, analisa a definição de API e gera a interface. Após entender todo o fluxo, é possível realizar operações customizadas.

go
// Name é um nome único usado para registrar instância swag.
// nome de instância padrão
const Name = "swagger"

var (
  swaggerMu sync.RWMutex
    // tabela de instâncias
  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"
    }

    // cria um template com nome
    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
       }

       // correspondência de rota
       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 {
       // página principal
       case "index.html":
          _ = index.Execute(ctx.Writer, config.toSwaggerConfig())
       // arquivo de descrição de 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)
       }
    }
}

Através do código Go gerado automaticamente para completar o registro de instância, abaixo está parte do código de docs.go gerado automaticamente

go
// SwaggerInfo mantém informações Swagger exportadas para que clientes possam modificá-las
var SwaggerInfo = &swag.Spec{
  Version:          "",
  Host:             "",
  BasePath:         "",
  Schemes:          []string{},
  Title:            "",
  Description:      "",
  InfoInstanceName: "swagger",
  SwaggerTemplate:  docTemplate,
  LeftDelim:        "{{",
  RightDelim:       "}}",
}

func init() {
    // registra instância
  swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

Pode-se ver que há uma função Register na função init usada para registrar a instância atual, se desejar modificar o nome da instância, não é recomendado editar neste arquivo, pois o arquivo docs.go é gerado automaticamente, basta usar o parâmetro --instanceName appapi ao gerar o código. Para conveniência, pode-se usar o comando go generate embutido em arquivos Go para facilitar a geração automática de código, como segue.

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

Pessoalmente, não gosto de escrever comentários de informações genéricas do swagger em main.go ou na função main, escrever esses comentários acima de go generate é mais apropriado.

TIP

Se precisar de múltiplas instâncias, certifique-se de que os nomes das instâncias sejam únicos, caso contrário ocorrerá panic

Para customizar algumas configurações, é necessário usar ginSwagger.CustomWrapHandler, que possui um parâmetro Config a mais em relação ao anterior, com a seguinte definição

go
// Config armazena variáveis de configuração ginSwagger.
type Config struct {
  // A URL apontando para definição de API (normalmente swagger.json ou swagger.yaml). Padrão é `doc.json`.
  URL                      string
    // estado de expansão da lista de interfaces
  DocExpansion             string
    // nome da instância
  InstanceName             string
    // título
  Title                    string
    // profundidade de expansão
  DefaultModelsExpandDepth int
    // autoexplicativo
  DeepLinking              bool
  PersistAuthorization     bool
  Oauth2DefaultClientID    string
}

Use swaggerFiles.NewHandler() para substituir o Handler padrão, especialmente ao usar múltiplas instâncias.

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

Além disso, é possível realizar uma série de operações como reescrita de tipos, todas bastante simples, mais conteúdo pode ser consultado na documentação oficial.

Notas

  1. O swag gera o arquivo de descrição de interface openapi baseado em comentários, ao gerar, o diretório especificado deve conter as informações básicas do documento de interface, por padrão é procurado em main.go

  2. swag init por padrão especifica o diretório atual, valor é ./, pode usar swag init -d para especificar múltiplos diretórios, separados por vírgula, o primeiro diretório especificado deve conter as informações básicas do documento de interface. Por exemplo

    swag init -d ./,./api
  3. -g, o arquivo de armazenamento das informações básicas do documento de interface pode ser customizado, por padrão é main.go, ao gerar o documento, use o parâmetro -g para especificar o nome do arquivo

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

    Este comando significa analisar as informações básicas do documento de interface em ./api.go, e ao mesmo tempo procurar e analisar outras informações de comentários de interface nos diretórios ./ e ./api para gerar os documentos correspondentes.

  4. O parâmetro -o pode especificar o caminho de saída do arquivo de descrição do documento, por padrão é ./docs, exemplo:

    swag init -o ./api/docs
  5. --ot pode especificar o tipo de arquivo de saída, por padrão é (docs.go,swagger.json,swagger.yaml), se desejar carregar swagger ui com programa Go, o arquivo Go é essencial.

    swag init --ot go,yaml

    Os arquivos json e yaml gerados podem facilitar a importação de dados em outros softwares de gerenciamento de interface.

  6. Não importa onde os comentários sejam escritos, mesmo que não sejam escritos em funções, ainda podem ser analisados, apenas escrever em funções tem melhor legibilidade, essencialmente é uma DSL embutida junto com comentários e código-fonte Go.

  7. O swag suporta muitos outros parâmetros, pode usar swag init -h para consultar.

Golang por www.golangdev.cn edit