Skip to content

swag

swaggo/swag là một triển khai của Swagger API 2.0 trong ngôn ngữ Go, thông qua việc viết chú thích theo định dạng chỉ định có thể tạo tài liệu API kiểu swagger.jsonswagger.yaml, thuận tiện cho việc xuất và nhập.

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

Tài liệu: swaggo/swag: Automatically generate RESTful API documentation with Swagger 2.0 for Go. (github.com)

swag mặc định hỗ trợ các framework web sau đây, bài viết này lấy gin làm ví dụ, để trình bày ví dụ gin kết hợp swagger nhanh chóng tạo tài liệu API.

TIP

Nếu không quen với cú pháp swagger, có thể đến About Swagger Specification | Documentation | Swagger

Cài đặt

Đầu tiên tải công cụ dòng lệnh swagger

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

Sau đó tải phụ thuộc mã nguồn swagger

go get github.com/swaggo/swag

TIP

Để tránh gặp vấn đề, phiên bản của cả hai phải giữ nhất quán.

Sau đó tải thư viện file tĩnh của swagger, html, css, js, v.v., đều được nhúng vào mã Go.

go get github.com/swaggo/files@latest

Cuối cùng tải thư viện thích ứng gin của swagger

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

Vì bài viết này chỉ dùng gin làm ví dụ, các adapter framework web khác vui lòng tự tìm hiểu, về cơ bản đều tương tự nhau.

Sử dụng

Sử dụng go mod để tạo một dự án Go cơ bản nhất, tạo main.go, viết nội dung sau.

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

Đây là một ví dụ web gin rất đơn giản, chú thích trên hàm main là thông tin cơ bản của tài liệu, hàm Ping là một interface bình thường. Tiếp theo thực thi lệnh để tạo tài liệu, mặc định là trong thư mục docs cùng cấp với main.go

swag init

Sửa đổi mã main.go

go
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
    // Import ẩn danh gói tài liệu API đã tạo
    _ "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()
    // Đăng ký route file tĩnh 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")),
    })
}

Chạy chương trình, truy cập 127.0.0.1/swagger/index.html, giao diện như sau

Như vậy là đã chạy một tài liệu API cơ bản. Tiếp theo ngoài một số điểm đặc biệt cần lưu ý, về cơ bản không có khác biệt quá lớn so với việc sử dụng các ngôn ngữ khác.

Tham số

Định dạng định nghĩa tham số là

@param name paramtype datatype isRequired comment

Một ví dụ như sau

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

Trong đó các loại tham số được hỗ trợ là

  • query
  • path
  • header
  • body
  • formData

Các kiểu dữ liệu là

  • string (string)
  • integer (int, uint, uint32, uint64)
  • number (float32)
  • boolean (bool)
  • user defined struct

Kiểu tham số cũng có thể là kiểu của bạn, miễn là có thể bị swagger quét đến.

Phản hồi

Định dạng cơ bản để định nghĩa phản hồi interface như sau

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

Gồm mã trạng thái, kiểu cơ bản, kiểu dữ liệu. {array} biểu thị là một mảng, sẽ hiển thị dưới dạng mảng của kiểu dữ liệu, {object} sẽ hiển thị dưới dạng gốc của kiểu dữ liệu. Ví dụ thông thường chúng ta sẽ định nghĩa một body phản hồi thống nhất

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

Kiểu của trường Data không xác định, khi mô tả ví dụ phản hồi, có thể kết hợp nó, như sau

go
// Kết hợp
@success 200 {object} jsonresult.JSONResult{data=Account} "desc"

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

Model

Thêm chú thích cho trường struct sẽ bị swagger quét thành chú thích trường model

go
package model

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

Trong đó giá trị của tag example sẽ được hiển thị làm giá trị ví dụ trong trang, tất nhiên nó cũng hỗ trợ giới hạn trường

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

Tất cả các model khi sử dụng đều phải đảm bảo có thể bị swagger quét đến, nếu không sẽ không có tác dụng.

Xác thực

Phần xác thực hỗ trợ

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

Giả sử xác thực interface dùng JWT, lưu trữ trong trường Authorization của header, chúng ta có thể định nghĩa như sau

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

Về bản chất đây chỉ là một apikey, nếu bạn truyền vào bearer token thì cần tự thêm tiền tố Bearer.

Sau đó thêm chú thích sau vào interface cần xác thực

go
// @security Bearer

Giá trị của nó là tên được định nghĩa trong securityDefinitions của bạn.

Cấu hình

swag thực chất là lưu trữ nhiều instance swagger khác nhau trong một map, ginSwagger làm adapter đọc doc.json từ instance, tức là file định nghĩa API interface, swaggerFiles cung cấp file HTML tĩnh để hiển thị trang web, phân tích định nghĩa API và tạo giao diện, sau khi hiểu toàn bộ quy trình, có thể thực hiện các thao tác tùy chỉnh.

go
// Name là một tên duy nhất được sử dụng để đăng ký instance swag.
// Tên instance mặc định
const Name = "swagger"

var (
  swaggerMu sync.RWMutex
    // Bảng instance
  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"
    }

    // tạo template với tên
    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
       }

       // Khớp route
       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 {
       // Trang chính
       case "index.html":
          _ = index.Execute(ctx.Writer, config.toSwaggerConfig())
       // File mô tả 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)
       }
    }
}

Thông qua mã Go được tạo để tự động hoàn thành việc đăng ký instance, dưới đây là một phần mã docs.go được tự động tạo

go
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
  Version:          "",
  Host:             "",
  BasePath:         "",
  Schemes:          []string{},
  Title:            "",
  Description:      "",
  InfoInstanceName: "swagger",
  SwaggerTemplate:  docTemplate,
  LeftDelim:        "{{",
  RightDelim:       "}}",
}

func init() {
    // Đăng ký instance
  swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

Có thể thấy trong hàm init có một hàm Register dùng để đăng ký instance hiện tại, nếu muốn sửa đổi tên instance không khuyến nghị chỉnh sửa trong file này, vì file docs.go được tự động tạo, chỉ cần sử dụng tham số --instanceName appapi khi tạo mã. Để thuận tiện, có thể sử dụng lệnh go generate nhúng vào file Go, thuận tiện cho việc tự động tạo mã, như sau.

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

Cá nhân không thích viết chú thích thông tin chung swagger trong main.go hoặc trên hàm main, viết những chú thích này ở phía trên go generate là phù hợp nhất.

TIP

Nếu cần nhiều instance,务必 giữ tên instance duy nhất, nếu không sẽ panic

Để tùy chỉnh một số cấu hình, cần sử dụng ginSwagger.CustomWrapHandler, nó so với cái trước có thêm một tham số Config, ý nghĩa như sau

go
// Config stores ginSwagger configuration variables.
type Config struct {
  // URL trỏ đến định nghĩa API (thường là swagger.json hoặc swagger.yaml). Mặc định là `doc.json`.
  URL                      string
    // Trạng thái mở rộng danh sách interface
  DocExpansion             string
    // Tên instance
  InstanceName             string
    // Tiêu đề
  Title                    string
    // Độ sâu mở rộng
  DefaultModelsExpandDepth int
    // Như tên gọi
  DeepLinking              bool
  PersistAuthorization     bool
  Oauth2DefaultClientID    string
}

Sử dụng swaggerFiles.NewHandler() để thay thế Handler mặc định, đặc biệt khi có nhiều instance.

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

Ngoài ra còn có thể thực hiện một loạt các thao tác như ghi đè kiểu, đều khá đơn giản, nội dung khác có thể đọc tài liệu chính thức.

Lưu ý

  1. swag tạo file mô tả API openapi dựa trên chú thích, khi tạo, thư mục được chỉ định phải chứa thông tin cơ bản của tài liệu API, mặc định tìm kiếm trong main.go

  2. swag init mặc định chỉ định thư mục hiện tại, giá trị là ./, có thể sử dụng swag init -d để chỉ định nhiều thư mục, sử dụng dấu phẩy phân cách, thư mục được chỉ định đầu tiên phải chứa thông tin cơ bản của tài liệu API. Ví dụ

    swag init -d ./,./api
  3. -g, file lưu trữ thông tin cơ bản của tài liệu API có thể tùy chỉnh tên file, mặc định là main.go, khi tạo tài liệu, sử dụng tham số -g để chỉ định tên file

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

    Ý nghĩa của lệnh này là phân tích thông tin cơ bản của tài liệu API trong ./api.go, đồng thời tìm kiếm và phân tích thông tin chú thích của các interface khác dưới hai thư mục ././api và tạo tài liệu tương ứng.

  4. Tham số -o có thể chỉ định đường dẫn đầu ra của file mô tả tài liệu, mặc định là ./docs, ví dụ:

    swag init -o ./api/docs
  5. --ot có thể chỉ định kiểu file đầu ra, mặc định là (docs.go,swagger.json,swagger.yaml), nếu muốn sử dụng chương trình Go để tải swagger ui, file Go là không thể thiếu.

    swag init --ot go,yaml

    Các file json và yaml được tạo ra có thể thuận tiện cho việc nhập dữ liệu trên các phần mềm quản lý API khác.

  6. Viết chú thích ở đâu cũng được,就算 không viết trên hàm cũng có thể phân tích, chỉ là viết trên hàm có tính đọc tốt hơn, về bản chất vẫn là một DSL được nhúng cùng với mã nguồn Go dưới dạng chú thích.

  7. swag còn hỗ trợ nhiều tham số khác, có thể sử dụng swag init -h để xem.

Golang by www.golangdev.cn edit