250 lines
5.8 KiB
Go
250 lines
5.8 KiB
Go
package swagger
|
||
|
||
import (
|
||
"fmt"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
|
||
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||
)
|
||
|
||
// RespDoc 表示一個回應文檔定義
|
||
type RespDoc struct {
|
||
StatusCode int // HTTP 狀態碼
|
||
Schema string // 回應類型名稱(單一回應)
|
||
BizCodes map[string]BizCode // 業務錯誤碼映射(多個回應)
|
||
Description string // 描述
|
||
}
|
||
|
||
// BizCode 表示業務錯誤碼定義
|
||
type BizCode struct {
|
||
Code string // 業務錯誤碼(如 300101)
|
||
Schema string // 對應的類型名稱
|
||
Description string // 描述
|
||
}
|
||
|
||
var (
|
||
// @respdoc-200 (TypeName) description
|
||
respdocSimpleRegex = regexp.MustCompile(`@respdoc-(\d+)\s+\((\w+)\)(?:\s+(.+))?`)
|
||
|
||
// 業務錯誤碼格式: 300101: (ValidationError) description
|
||
bizCodeRegex = regexp.MustCompile(`(\d+):\s+\((\w+)\)(?:\s+(.+))?`)
|
||
)
|
||
|
||
// parseRespDocsFromDoc 從 Doc 數組中解析所有 @respdoc 定義
|
||
func parseRespDocsFromDoc(doc apiSpec.Doc) []RespDoc {
|
||
// Doc 是字符串數組,合併為一個文本
|
||
var text string
|
||
for _, line := range doc {
|
||
text += line + "\n"
|
||
}
|
||
return parseRespDocsFromText(text)
|
||
}
|
||
|
||
// parseRespDocs 從 AtDoc 註解中解析所有 @respdoc 定義
|
||
func parseRespDocs(atDoc apiSpec.AtDoc) []RespDoc {
|
||
return parseRespDocsFromText(atDoc.Text)
|
||
}
|
||
|
||
// parseRespDocsFromText 從文本中解析所有 @respdoc 定義
|
||
func parseRespDocsFromText(text string) []RespDoc {
|
||
var respDocs []RespDoc
|
||
|
||
if text == "" {
|
||
return respDocs
|
||
}
|
||
|
||
lines := strings.Split(text, "\n")
|
||
var currentRespDoc *RespDoc
|
||
var inMultiLine bool
|
||
|
||
for _, line := range lines {
|
||
line = strings.TrimSpace(line)
|
||
|
||
// 跳過空行和純註釋符號
|
||
if line == "" || line == "/*" || line == "*/" || strings.HasPrefix(line, "//") {
|
||
continue
|
||
}
|
||
|
||
// 移除行首的註釋符號
|
||
line = strings.TrimPrefix(line, "*")
|
||
line = strings.TrimSpace(line)
|
||
|
||
// 檢查是否為 @respdoc 開頭
|
||
if strings.HasPrefix(line, "@respdoc-") {
|
||
// 保存上一個 respDoc
|
||
if currentRespDoc != nil {
|
||
respDocs = append(respDocs, *currentRespDoc)
|
||
}
|
||
|
||
// 解析新的 respdoc
|
||
currentRespDoc = parseRespDocLine(line)
|
||
|
||
// 檢查是否為多行格式(含有左括號但不含右括號,或右括號後還有註釋符號)
|
||
if strings.Contains(line, "(") && !strings.Contains(line, ")") {
|
||
inMultiLine = true
|
||
} else {
|
||
inMultiLine = false
|
||
}
|
||
|
||
// 如果是單行格式,直接添加
|
||
if !inMultiLine && currentRespDoc != nil {
|
||
respDocs = append(respDocs, *currentRespDoc)
|
||
currentRespDoc = nil
|
||
}
|
||
} else if inMultiLine && currentRespDoc != nil {
|
||
// 處理多行格式中的業務錯誤碼
|
||
if strings.Contains(line, ":") && strings.Contains(line, "(") {
|
||
bizCode := parseBizCodeLine(line)
|
||
if bizCode != nil {
|
||
if currentRespDoc.BizCodes == nil {
|
||
currentRespDoc.BizCodes = make(map[string]BizCode)
|
||
}
|
||
currentRespDoc.BizCodes[bizCode.Code] = *bizCode
|
||
}
|
||
}
|
||
|
||
// 檢查是否為多行結束(含有右括號和註釋)
|
||
if strings.Contains(line, ")") && strings.Contains(line, "//") {
|
||
// 提取描述
|
||
parts := strings.SplitN(line, "//", 2)
|
||
if len(parts) == 2 {
|
||
currentRespDoc.Description = strings.TrimSpace(parts[1])
|
||
}
|
||
respDocs = append(respDocs, *currentRespDoc)
|
||
currentRespDoc = nil
|
||
inMultiLine = false
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加最後一個 respDoc
|
||
if currentRespDoc != nil {
|
||
respDocs = append(respDocs, *currentRespDoc)
|
||
}
|
||
|
||
return respDocs
|
||
}
|
||
|
||
// parseRespDocLine 解析單行 @respdoc 定義
|
||
func parseRespDocLine(line string) *RespDoc {
|
||
// 移除 @respdoc- 前綴
|
||
line = strings.TrimPrefix(line, "@respdoc-")
|
||
|
||
// 提取狀態碼
|
||
parts := strings.SplitN(line, " ", 2)
|
||
if len(parts) == 0 {
|
||
return nil
|
||
}
|
||
|
||
statusCode, err := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
|
||
respDoc := &RespDoc{
|
||
StatusCode: statusCode,
|
||
}
|
||
|
||
if len(parts) < 2 {
|
||
return respDoc
|
||
}
|
||
|
||
rest := strings.TrimSpace(parts[1])
|
||
|
||
// 檢查是否為簡單格式: (TypeName) description
|
||
matches := respdocSimpleRegex.FindStringSubmatch("@respdoc-" + line)
|
||
if len(matches) >= 3 {
|
||
respDoc.Schema = matches[2]
|
||
if len(matches) >= 4 {
|
||
respDoc.Description = strings.TrimSpace(matches[3])
|
||
}
|
||
return respDoc
|
||
}
|
||
|
||
// 檢查是否為多行格式開始: (
|
||
if strings.HasPrefix(rest, "(") && !strings.Contains(rest, ")") {
|
||
// 多行格式,等待後續行處理
|
||
return respDoc
|
||
}
|
||
|
||
return respDoc
|
||
}
|
||
|
||
// parseBizCodeLine 解析業務錯誤碼行
|
||
func parseBizCodeLine(line string) *BizCode {
|
||
matches := bizCodeRegex.FindStringSubmatch(line)
|
||
if len(matches) < 3 {
|
||
return nil
|
||
}
|
||
|
||
bizCode := &BizCode{
|
||
Code: matches[1],
|
||
Schema: matches[2],
|
||
}
|
||
|
||
if len(matches) >= 4 {
|
||
bizCode.Description = strings.TrimSpace(matches[3])
|
||
}
|
||
|
||
return bizCode
|
||
}
|
||
|
||
// findTypeByName 在 API 規範中查找類型定義
|
||
func findTypeByName(api *apiSpec.ApiSpec, typeName string) apiSpec.Type {
|
||
if typeName == "" {
|
||
return nil
|
||
}
|
||
|
||
for _, tp := range api.Types {
|
||
if defStruct, ok := tp.(apiSpec.DefineStruct); ok {
|
||
if defStruct.Name() == typeName {
|
||
return tp
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// getHTTPStatusText 獲取 HTTP 狀態碼的標準文本
|
||
func getHTTPStatusText(code int) string {
|
||
switch code {
|
||
case 200:
|
||
return "OK"
|
||
case 201:
|
||
return "Created"
|
||
case 202:
|
||
return "Accepted"
|
||
case 204:
|
||
return "No Content"
|
||
case 400:
|
||
return "Bad Request"
|
||
case 401:
|
||
return "Unauthorized"
|
||
case 403:
|
||
return "Forbidden"
|
||
case 404:
|
||
return "Not Found"
|
||
case 405:
|
||
return "Method Not Allowed"
|
||
case 409:
|
||
return "Conflict"
|
||
case 422:
|
||
return "Unprocessable Entity"
|
||
case 429:
|
||
return "Too Many Requests"
|
||
case 500:
|
||
return "Internal Server Error"
|
||
case 502:
|
||
return "Bad Gateway"
|
||
case 503:
|
||
return "Service Unavailable"
|
||
case 504:
|
||
return "Gateway Timeout"
|
||
default:
|
||
return fmt.Sprintf("HTTP %d", code)
|
||
}
|
||
}
|