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/swagTIP
เพื่อหลีกเลี่ยงปัญหา เวอร์ชันทั้งสองต้องเหมือนกัน
จากนั้นดาวน์โหลดไลบรารีไฟล์ 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 ใหม่ เขียนเนื้อหาดังต่อไปนี้
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
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ตัวอย่างมีดังนี้
@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 ได้
การตอบสนอง
รูปแบบพื้นฐานการกำหนดการตอบสนองอินเทอร์เฟซมีดังนี้
// @Success 200 {array} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPErrorประกอบด้วยรหัสสถานะ ประเภทพื้นฐาน ประเภทข้อมูล {array} หมายถึงเป็นอาร์เรย์ จะแสดงในรูปแบบอาร์เรย์ของประเภทข้อมูล {object} จะแสดงในรูปแบบเดิมของประเภทข้อมูล เช่น โดยทั่วไปเราจะกำหนด response body ที่เป็นหนึ่งเดียว
type JSONResult struct {
Code int `json:"code" `
Message string `json:"message"`
Data interface{} `json:"data"`
}ประเภทของฟิลด์ Data ไม่แน่นอน ในการอธิบายกรณีการใช้งานการตอบสนอง สามารถรวมเข้าด้วยกันได้ดังนี้
// รวม
@success 200 {object} jsonresult.JSONResult{data=Account} "desc"
// อาร์เรย์
@success 200 {object} jsonresult.JSONResult{data=[]Account} "desc"โมเดล
การเพิ่มคอมเมนต์ฟิลด์โครงสร้างจะถูก swagger scan เป็นคอมเมนต์ฟิลด์โมเดล
package model
type Account struct {
// account id
ID int `json:"id" example:"1"`
// username
Name string `json:"name" example:"account name"`
}ค่าของแท็ก example จะถูกแสดงเป็นค่าตัวอย่างในหน้าเว็บ แน่นอน它还รองรับข้อจำกัดฟิลด์
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 เราสามารถกำหนดได้ดังนี้
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
โดยพื้นฐานแล้วนี่เป็นเพียง apikey หากคุณส่ง bearer token ต้องเพิ่มคำนำหน้า Bearer ด้วยตนเอง

จากนั้นในอินเทอร์เฟซที่ต้องการการรับรองความถูกต้อง เพิ่มคอมเมนต์ดังนี้
// @security Bearerค่าของมันคือชื่อที่กำหนดไว้ใน securityDefinitions ของคุณ
การกำหนดค่า
swag จริงๆ แล้วเก็บ swagger instance ที่แตกต่างกันหลายตัวไว้ใน map เดียว ginSwagger ทำหน้าที่เป็น adapter อ่าน doc.json จาก instance ซึ่งคือไฟล์นิยาม API interface swaggerFiles ให้ไฟล์ HTML แบบ static สำหรับแสดงเว็บเพจ วิเคราะห์นิยาม API และสร้างอินเทอร์เฟซ เมื่อเข้าใจกระบวนการทั้งหมดแล้ว ก็สามารถดำเนินการปรับแต่งได้
// Name is a unique name be used to register swag instance.
// ชื่อ instance โดยค่าเริ่มต้น
const Name = "swagger"
var (
swaggerMu sync.RWMutex
// ตาราง instance
swags map[string]Swagger
)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
// 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 เพื่อสร้างโค้ดโดยอัตโนมัติ ดังนี้
// 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 เพิ่มจากตัวก่อนหน้า ความหมายมีดังนี้
// 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 โดยเฉพาะต้องทำเช่นนี้
engine.GET(openapi.ApiDoc, ginSwagger.CustomWrapHandler(openapi.Config, swaggerFiles.NewHandler()))นอกจากนี้ยังสามารถดำเนินการเขียนทับประเภทและอื่นๆ ได้อีกมากมาย ซึ่งค่อนข้างง่าย เนื้อหาเพิ่มเติมสามารถอ่านได้จากเอกสารทางการ
ข้อควรระวัง
swag สร้างไฟล์อธิบายอินเทอร์เฟซ openapi จากคอมเมนต์ เมื่อสร้าง ไดเรกทอรีที่ระบุต้องมีข้อมูลพื้นฐานของเอกสารอินเทอร์เฟซ โดยค่าเริ่มต้นจะค้นหาใน
main.goswag initกำหนดไดเรกทอรีปัจจุบันโดยค่าเริ่มต้น ค่าคือ./สามารถใช้swag init -dระบุหลายไดเรกทอรี ใช้ลูกน้ำคั่น ไดเรกทอรีแรกที่ระบุต้องมีข้อมูลพื้นฐานของเอกสารอินเทอร์เฟซ เช่นswag init -d ./,./api-gไฟล์ที่เก็บข้อมูลพื้นฐานของเอกสารอินเทอร์เฟซสามารถกำหนดชื่อไฟล์เองได้ ค่าเริ่มต้นคือmain.goเมื่อสร้างเอกสาร ใช้พารามิเตอร์-gระบุชื่อไฟล์swag init -g api.go -d ./,./apiความหมายของคำสั่งนี้คือวิเคราะห์ข้อมูลพื้นฐานของเอกสารอินเทอร์เฟซใน
./api.goในขณะเดียวกันค้นหาและวิเคราะห์คอมเมนต์อินเทอร์เฟซอื่นใต้ไดเรกทอรี./และ./apiและสร้างเอกสารที่สอดคล้องกันพารามิเตอร์
-oสามารถระบุเส้นทางส่งออกของไฟล์อธิบายเอกสาร โดยค่าเริ่มต้นคือ./docsเช่น:swag init -o ./api/docs--otสามารถระบุประเภทไฟล์ส่งออก โดยค่าเริ่มต้นคือ (docs.go, swagger.json, swagger.yaml) หากต้องการใช้โปรแกรม go โหลด swagger ui ไฟล์ go เป็นสิ่งที่ขาดไม่ได้swag init --ot go,yamlไฟล์ json และ yaml ที่สร้างที่เหลือสามารถสะดวกต่อการนำเข้าข้อมูลในซอฟต์แวร์จัดการอินเทอร์เฟซอื่น
การเขียนคอมเมนต์ที่ไหนก็เหมือนกัน แม้ไม่เขียนบนฟังก์ชันก็สามารถวิเคราะห์ได้เช่นกัน เพียงแต่เขียนบนฟังก์ชันจะอ่านง่ายกว่า โดยพื้นฐานแล้วยังเป็น DSL ที่ฝังอยู่ในคอมเมนต์และ source code go
swag รองรับพารามิเตอร์อื่นๆ อีกมากมาย สามารถใช้
swag init -hเพื่อดู
