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