Skip to content

swag

swaggo/swag เป็นการ implement Swagger API 2.0 ในภาษา go โดยการเขียนคอมเมนต์ในรูปแบบที่กำหนดจะสร้างเอกสารอินเทอร์เฟซประเภท swagger.json และ swagger.yaml สะดวกต่อการ export และ import

repository: 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)

web framework ที่ swag รองรับโดยค่าเริ่มต้นมีดังต่อไปนี้ บทความนี้จะใช้ gin เป็นตัวอย่าง เพื่อสาธิตการรวม gin กับ swagger เพื่อสร้างเอกสารอินเทอร์เฟซอย่างรวดเร็ว

TIP

หากไม่คุ้นเคยกับไวยากรณ์ swagger สามารถไปที่ About Swagger Specification | Documentation | Swagger

การติดตั้ง

ก่อนอื่นดาวน์โหลดเครื่องมือ swagger command line

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

จากนั้นดาวน์โหลด source code dependency ของ swagger

go get github.com/swaggo/swag

TIP

เพื่อหลีกเลี่ยงปัญหา เวอร์ชันทั้งสองต้องเหมือนกัน

จากนั้นดาวน์โหลดไลบรารีไฟล์ static ของ swagger html, css, js และอื่นๆ ถูกฝังอยู่ในโค้ด go

go get github.com/swaggo/files@latest

สุดท้ายดาวน์โหลดไลบรารีปรับแต่ง gin ของ swagger

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

เนื่องจากบทความนี้ใช้เฉพาะ gin เป็นตัวอย่าง สำหรับ adapter ของ web framework อื่นๆ โปรดศึกษาด้วยตนเอง โดยพื้นฐานแล้วไม่แตกต่างกันมาก

การใช้งาน

ใช้ go mod สร้างโปรเจกต์ go ที่ง่ายที่สุด สร้าง 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"
    // นำเข้าแพ็กเกจเอกสารอินเทอร์เฟซที่สร้างแบบ anonymous
    _ "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()
    // ลงทะเบียนเส้นทาง static file ของ 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)
  • user defined struct

ประเภทพารามิเตอร์ยังสามารถเป็นประเภทของคุณเองได้ ตราบใดที่สามารถถูก swagger scan ได้

การตอบสนอง

รูปแบบพื้นฐานการกำหนดการตอบสนองอินเทอร์เฟซมีดังนี้

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

ประกอบด้วยรหัสสถานะ ประเภทพื้นฐาน ประเภทข้อมูล {array} หมายถึงเป็นอาร์เรย์ จะแสดงในรูปแบบอาร์เรย์ของประเภทข้อมูล {object} จะแสดงในรูปแบบเดิมของประเภทข้อมูล เช่น โดยทั่วไปเราจะกำหนด response body ที่เป็นหนึ่งเดียว

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 scan เป็นคอมเมนต์ฟิลด์โมเดล

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 scan ได้ มิฉะนั้นจะไม่ทำงาน

การรับรองความถูกต้อง

ในส่วนการรับรองความถูกต้องรองรับ

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

หากการรับรองความถูกต้องของอินเทอร์เฟซใช้ JWT เก็บในฟิลด์ Authorization ของ header เราสามารถกำหนดได้ดังนี้

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

โดยพื้นฐานแล้วนี่เป็นเพียง apikey หากคุณส่ง bearer token ต้องเพิ่มคำนำหน้า Bearer ด้วยตนเอง

จากนั้นในอินเทอร์เฟซที่ต้องการการรับรองความถูกต้อง เพิ่มคอมเมนต์ดังนี้

go
// @security Bearer

ค่าของมันคือชื่อที่กำหนดไว้ใน securityDefinitions ของคุณ

การกำหนดค่า

swag จริงๆ แล้วเก็บ swagger instance ที่แตกต่างกันหลายตัวไว้ใน map เดียว ginSwagger ทำหน้าที่เป็น adapter อ่าน doc.json จาก instance ซึ่งคือไฟล์นิยาม API interface swaggerFiles ให้ไฟล์ HTML แบบ static สำหรับแสดงเว็บเพจ วิเคราะห์นิยาม API และสร้างอินเทอร์เฟซ เมื่อเข้าใจกระบวนการทั้งหมดแล้ว ก็สามารถดำเนินการปรับแต่งได้

go
// Name is a unique name be used to register swag instance.
// ชื่อ instance โดยค่าเริ่มต้น
const Name = "swagger"

var (
  swaggerMu sync.RWMutex
    // ตาราง 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"
    }

    // สร้าง template พร้อมชื่อ
    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 โดยอัตโนมัติเพื่อเสร็จสิ้นการลงทะเบียน instance ด้านล่างเป็นโค้ดบางส่วนที่สร้างโดยอัตโนมัติของ docs.go

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() {
    // ลงทะเบียน instance
  swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

จะเห็นว่าในฟังก์ชัน init มีฟังก์ชัน Register ใช้สำหรับลงทะเบียน instance ปัจจุบัน หากต้องการแก้ไขชื่อ instance ไม่แนะนำให้แก้ไขในไฟล์นี้ เพราะไฟล์ 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

หากต้องการหลาย instance ต้องแน่ใจว่าชื่อ instance ไม่ซ้ำกัน มิฉะนั้นจะเกิด panic

เพื่อปรับแต่งการกำหนดค่าบางอย่าง ต้องใช้ ginSwagger.CustomWrapHandler ซึ่งมีพารามิเตอร์ Config เพิ่มจากตัวก่อนหน้า ความหมายมีดังนี้

go
// Config stores ginSwagger configuration variables.
type Config struct {
  // URL ที่ชี้ไปยังนิยาม API (ปกติคือ swagger.json หรือ swagger.yaml) ค่าเริ่มต้นคือ `doc.json`
  URL                      string
    // สถานะการ展開รายการอินเทอร์เฟซ
  DocExpansion             string
    // ชื่อ instance
  InstanceName             string
    // ชื่อเรื่อง
  Title                    string
    // ความลึกการ展開
  DefaultModelsExpandDepth int
    // ตามชื่อ
  DeepLinking              bool
  PersistAuthorization     bool
  Oauth2DefaultClientID    string
}

ใช้ swaggerFiles.NewHandler() แทน Handler เริ่มต้น ในกรณีที่มีหลาย instance โดยเฉพาะต้องทำเช่นนี้

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) หากต้องการใช้โปรแกรม go โหลด swagger ui ไฟล์ go เป็นสิ่งที่ขาดไม่ได้

    swag init --ot go,yaml

    ไฟล์ json และ yaml ที่สร้างที่เหลือสามารถสะดวกต่อการนำเข้าข้อมูลในซอฟต์แวร์จัดการอินเทอร์เฟซอื่น

  6. การเขียนคอมเมนต์ที่ไหนก็เหมือนกัน แม้ไม่เขียนบนฟังก์ชันก็สามารถวิเคราะห์ได้เช่นกัน เพียงแต่เขียนบนฟังก์ชันจะอ่านง่ายกว่า โดยพื้นฐานแล้วยังเป็น DSL ที่ฝังอยู่ในคอมเมนต์และ source code go

  7. swag รองรับพารามิเตอร์อื่นๆ อีกมากมาย สามารถใช้ swag init -h เพื่อดู

Golang by www.golangdev.cn edit