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