doc-generate/internal/swagger/respdoc.go

250 lines
5.8 KiB
Go
Raw Permalink Normal View History

2025-09-30 09:33:29 +00:00
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)
}
}