diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 1f093ae..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,69 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [1.1.0] - 2025-09-30 - -### Added -- **OpenAPI 3.0 Support** - Generate both Swagger 2.0 and OpenAPI 3.0 specifications -- New `--spec-version` (`-s`) flag to choose between `swagger2.0` and `openapi3.0` -- Automatic conversion from Swagger 2.0 to OpenAPI 3.0 format -- Support for OpenAPI 3.0 features: - - Server objects with complete URLs (instead of host/basePath) - - Components/schemas (instead of definitions) - - Components/securitySchemes (instead of securityDefinitions) - - RequestBody separated from parameters -- Integration with `github.com/getkin/kin-openapi` library for OpenAPI 3.0 - -### Changed -- Updated CLI help text to reflect dual specification support -- Enhanced Makefile examples to generate both Swagger 2.0 and OpenAPI 3.0 -- Version bumped to 1.1.0 - -## [1.0.0] - 2025-09-30 - -### Added -- Initial release as standalone tool -- Extracted from go-zero project and made independent -- Support for converting go-zero `.api` files to Swagger 2.0 specification -- JSON and YAML output formats -- Command-line interface using cobra -- Support for all go-zero API features: - - Info properties (title, description, version, host, basePath, etc.) - - Type definitions (structs, arrays, maps, pointers) - - Tag-based parameter handling (json, form, path, header) - - Validation options (range, enum, default, example, optional) - - Security definitions - - Code-msg wrapper for responses - - Definition references -- Internal utility functions to replace go-zero dependencies -- Comprehensive documentation -- Example API files -- Makefile for easy building -- MIT License - -### Changed -- Module name from `github.com/zeromicro/go-zero` to `go-doc` -- Removed dependency on `go-zero/tools/goctl/internal/version` -- Removed dependency on `go-zero/tools/goctl/util/*` -- Removed dependency on `google.golang.org/grpc/metadata` -- Project structure reorganized to follow Go best practices - - `cmd/go-doc/` for main entry point - - `internal/swagger/` for core logic - - `internal/util/` for utilities - -### Removed -- Dependency on go-zero runtime components -- Go-zero specific version information - ---- - -## Attribution - -This project was originally part of the [go-zero](https://github.com/zeromicro/go-zero) -project's swagger generation plugin. We are grateful to the go-zero team for their -excellent work. - diff --git a/MIGRATION.md b/MIGRATION.md deleted file mode 100644 index 3901b96..0000000 --- a/MIGRATION.md +++ /dev/null @@ -1,255 +0,0 @@ -# 🔄 Migration Guide: From go-zero Plugin to Standalone Tool - -This document explains how `go-doc` was extracted from go-zero and made independent. - -## 📊 What Changed? - -### 1. **Module Independence** - -**Before (go-zero plugin):** -```go -// Part of go-zero's internal tools -package swagger // in tools/goctl/api/plugin/swagger/ -``` - -**After (standalone):** -```go -module go-doc - -package swagger // in internal/swagger/ -``` - -### 2. **Dependency Reduction** - -#### Removed Internal Dependencies - -| Dependency | Replaced With | Reason | -|:-----------|:--------------|:-------| -| `go-zero/tools/goctl/internal/version` | Custom version in `main.go` | Internal package not accessible | -| `go-zero/tools/goctl/util` | `go-doc/internal/util` | Self-contained utilities | -| `go-zero/tools/goctl/util/stringx` | `go-doc/internal/util` | Custom string manipulation | -| `go-zero/tools/goctl/util/pathx` | `go-doc/internal/util` | Custom path utilities | -| `google.golang.org/grpc/metadata` | Direct map access | Simplified KV retrieval | - -#### Kept Essential Dependencies - -```go -require ( - github.com/go-openapi/spec v0.21.0 // Swagger spec - github.com/spf13/cobra v1.8.1 // CLI framework - github.com/zeromicro/go-zero/tools/goctl v1.9.0 // API parser only - gopkg.in/yaml.v2 v2.4.0 // YAML output -) -``` - -### 3. **Project Structure** - -**Before:** -``` -go-zero/ -└── tools/ - └── goctl/ - └── api/ - └── plugin/ - └── swagger/ - ├── swagger.go - ├── parameter.go - └── ... -``` - -**After:** -``` -go-doc/ -├── cmd/ -│ └── go-doc/ -│ └── main.go # Standalone entry point -├── internal/ -│ ├── swagger/ # Core logic (from go-zero) -│ │ ├── swagger.go -│ │ ├── parameter.go -│ │ └── ... -│ └── util/ # Self-contained utilities -│ ├── util.go -│ ├── stringx.go -│ └── pathx.go -└── example/ -``` - -### 4. **Import Path Changes** - -**Before:** -```go -import ( - "github.com/zeromicro/go-zero/tools/goctl/util" - "github.com/zeromicro/go-zero/tools/goctl/util/stringx" - "github.com/zeromicro/go-zero/tools/goctl/internal/version" - "google.golang.org/grpc/metadata" -) -``` - -**After:** -```go -import ( - "go-doc/internal/util" -) -``` - -### 5. **Metadata Handling** - -**Before (using gRPC metadata):** -```go -func getStringFromKV(properties map[string]string, key string) string { - md := metadata.New(properties) - val := md.Get(key) - if len(val) == 0 { - return "" - } - return val[0] -} -``` - -**After (direct map access):** -```go -func getStringFromKVOrDefault(properties map[string]string, key string, def string) string { - if len(properties) == 0 { - return def - } - val, ok := properties[key] - if !ok { - return def - } - str, err := strconv.Unquote(val) - if err != nil || len(str) == 0 { - return def - } - return str -} -``` - -### 6. **Version Information** - -**Before:** -```go -ext.Add("x-goctl-version", version.BuildVersion) -ext.Add("x-github", "https://github.com/zeromicro/go-zero") -``` - -**After:** -```go -ext.Add("x-generator", "go-doc") -ext.Add("x-github", "https://github.com/danielchan-25/go-doc") -``` - -## 🔧 Implementation Details - -### Custom Utilities Created - -#### 1. **util.TrimWhiteSpace** -```go -// Replaces: go-zero/tools/goctl/util.TrimWhiteSpace -func TrimWhiteSpace(s string) string { - return strings.Map(func(r rune) rune { - if unicode.IsSpace(r) { - return -1 - } - return r - }, s) -} -``` - -#### 2. **util.FieldsAndTrimSpace** -```go -// Replaces: go-zero/tools/goctl/util.FieldsAndTrimSpace -func FieldsAndTrimSpace(s string, fn func(rune) bool) []string { - fields := strings.FieldsFunc(s, fn) - result := make([]string, 0, len(fields)) - for _, field := range fields { - trimmed := strings.TrimSpace(field) - if len(trimmed) > 0 { - result = append(result, trimmed) - } - } - return result -} -``` - -#### 3. **util.String (for ToCamel/Untitle)** -```go -// Replaces: go-zero/tools/goctl/util/stringx -type String struct { - source string -} - -func From(s string) String { - return String{source: s} -} - -func (s String) ToCamel() string { /* implementation */ } -func (s String) Untitle() string { /* implementation */ } -``` - -#### 4. **util.MkdirIfNotExist** -```go -// Replaces: go-zero/tools/goctl/util/pathx.MkdirIfNotExist -func MkdirIfNotExist(dir string) error { - if _, err := os.Stat(dir); os.IsNotExist(err) { - return os.MkdirAll(dir, 0755) - } - return nil -} -``` - -## ✅ Compatibility - -### What's Still Compatible? - -✅ **API File Format** - 100% compatible with go-zero `.api` files -✅ **Swagger Output** - Generates identical OpenAPI 2.0 specifications -✅ **All Features** - All swagger generation features preserved -✅ **Tag Syntax** - Same tag options (range, enum, default, example, etc.) - -### What's Different? - -⚠️ **Generated Metadata** -- `x-generator: "go-doc"` instead of `x-goctl-version` -- `x-github` points to go-doc repository -- Build date format remains the same - -⚠️ **Module Name** -- Import as `go-doc` not part of go-zero - -⚠️ **Binary Name** -- `go-doc` instead of `goctl api plugin -plugin swagger` - -## 🚀 Migration Steps for Users - -If you were using go-zero's swagger plugin: - -### Old Way (go-zero plugin): -```bash -goctl api plugin -plugin swagger -api example.api -dir output -``` - -### New Way (standalone go-doc): -```bash -go-doc -a example.api -d output -``` - -## 📝 Benefits of Independence - -1. ✅ **Smaller Binary** - Only swagger generation, no full goctl -2. ✅ **Faster Installation** - Fewer dependencies -3. ✅ **Clearer Purpose** - Single responsibility -4. ✅ **Easier Maintenance** - Self-contained codebase -5. ✅ **Flexible Updates** - Independent release cycle - -## 🙏 Attribution - -This tool was extracted from the excellent [go-zero](https://github.com/zeromicro/go-zero) -project. We are grateful to the go-zero team for their foundational work. - ---- - -**Original Source:** https://github.com/zeromicro/go-zero/tree/master/tools/goctl/api/plugin/swagger -**License:** MIT (both original and this project) - diff --git a/OPENAPI3_GUIDE.md b/OPENAPI3_GUIDE.md deleted file mode 100644 index d03c1de..0000000 --- a/OPENAPI3_GUIDE.md +++ /dev/null @@ -1,279 +0,0 @@ -# OpenAPI 3.0 使用指南 - -## 📚 概述 - -從 v1.1.0 開始,`go-doc` 支援生成 **Swagger 2.0** 和 **OpenAPI 3.0** 兩種規格。 - -## 🚀 快速開始 - -### 生成 Swagger 2.0(預設) -```bash -go-doc -a example/example.api -d output -``` - -### 生成 OpenAPI 3.0 -```bash -go-doc -a example/example.api -d output -s openapi3.0 -``` - -### 生成 OpenAPI 3.0 YAML -```bash -go-doc -a example/example.api -d output -s openapi3.0 -y -``` - -## 🔍 兩種規格的差異 - -### 1. 版本宣告 -**Swagger 2.0:** -```json -{ - "swagger": "2.0" -} -``` - -**OpenAPI 3.0:** -```json -{ - "openapi": "3.0.3" -} -``` - -### 2. 伺服器定義 -**Swagger 2.0:** -```json -{ - "host": "example.com", - "basePath": "/v1", - "schemes": ["http", "https"] -} -``` - -**OpenAPI 3.0:** -```json -{ - "servers": [ - {"url": "http://example.com/v1"}, - {"url": "https://example.com/v1"} - ] -} -``` - -### 3. 資料模型定義 -**Swagger 2.0:** -```json -{ - "definitions": { - "User": { - "type": "object", - "properties": {...} - } - } -} -``` - -**OpenAPI 3.0:** -```json -{ - "components": { - "schemas": { - "User": { - "type": "object", - "properties": {...} - } - } - } -} -``` - -### 4. 安全定義 -**Swagger 2.0:** -```json -{ - "securityDefinitions": { - "apiKey": { - "type": "apiKey", - "in": "header", - "name": "Authorization" - } - } -} -``` - -**OpenAPI 3.0:** -```json -{ - "components": { - "securitySchemes": { - "apiKey": { - "type": "apiKey", - "in": "header", - "name": "Authorization" - } - } - } -} -``` - -### 5. 請求內容 -**Swagger 2.0:** -```json -{ - "parameters": [ - { - "in": "body", - "name": "body", - "schema": {"$ref": "#/definitions/User"} - } - ] -} -``` - -**OpenAPI 3.0:** -```json -{ - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - } - } -} -``` - -### 6. 回應內容 -**Swagger 2.0:** -```json -{ - "responses": { - "200": { - "description": "Success", - "schema": {"$ref": "#/definitions/User"} - } - } -} -``` - -**OpenAPI 3.0:** -```json -{ - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - } - } - } -} -``` - -## 🎯 使用範例 - -### 完整範例:生成多種格式 - -```bash -# Swagger 2.0 JSON -go-doc -a example/example.api -d output -f api-swagger2 - -# Swagger 2.0 YAML -go-doc -a example/example.api -d output -f api-swagger2 -y - -# OpenAPI 3.0 JSON -go-doc -a example/example.api -d output -f api-openapi3 -s openapi3.0 - -# OpenAPI 3.0 YAML -go-doc -a example/example.api -d output -f api-openapi3 -s openapi3.0 -y -``` - -### 使用 Makefile - -```bash -# 生成所有範例(包含 Swagger 2.0 和 OpenAPI 3.0) -make example - -# 僅編譯 -make build - -# 測試 -make test -``` - -## ⚙️ 轉換邏輯 - -`go-doc` 的內部轉換流程: - -1. **解析 `.api` 檔案** → 使用 go-zero 的 API parser -2. **生成 Swagger 2.0 結構** → 基礎的 OpenAPI 2.0 規格 -3. **轉換為 OpenAPI 3.0**(如果指定)→ 自動轉換: - - `definitions` → `components/schemas` - - `securityDefinitions` → `components/securitySchemes` - - `host/basePath/schemes` → `servers` - - body parameters → `requestBody` -4. **序列化輸出** → JSON 或 YAML 格式 - -## 🔧 技術細節 - -### 使用的函式庫 -- **Swagger 2.0**: `github.com/go-openapi/spec` -- **OpenAPI 3.0**: `github.com/getkin/kin-openapi` - -### 轉換函數 -- `spec2Swagger()`: 將 go-zero API 轉為 Swagger 2.0 -- `convertSwagger2ToOpenAPI3()`: 將 Swagger 2.0 轉為 OpenAPI 3.0 - -### 型別轉換 -```go -// Swagger 2.0 Type -type: "string" - -// OpenAPI 3.0 Type -type: &Types{"string"} -``` - -## 📖 參考資源 - -- [OpenAPI 3.0 Specification](https://spec.openapis.org/oas/v3.0.3) -- [Swagger 2.0 Specification](https://swagger.io/specification/v2/) -- [go-zero API 規範](https://go-zero.dev/docs/tutorials) - -## ❓ 常見問題 - -### Q: 應該使用哪個版本? -**A:** -- **Swagger 2.0**: 如果需要與舊工具或系統相容 -- **OpenAPI 3.0**: 推薦使用,功能更強大且為現代標準 - -### Q: 可以同時生成兩種格式嗎? -**A:** 可以!執行兩次命令即可: -```bash -go-doc -a api.api -d out -f api-v2 -go-doc -a api.api -d out -f api-v3 -s openapi3.0 -``` - -### Q: 轉換有什麼限制嗎? -**A:** 大部分功能都能完整轉換。唯一差異是: -- OpenAPI 3.0 的 server URLs 會根據 schemes 自動生成多個 -- 某些 Swagger 2.0 特定的擴展可能不會轉換 - -### Q: 如何驗證生成的文檔? -**A:** 可以使用以下工具: -- **Swagger Editor**: https://editor.swagger.io/ -- **Swagger UI**: 本地運行或線上版本 -- **OpenAPI Generator**: 生成客戶端/伺服器代碼來驗證 - -## 🎉 總結 - -`go-doc` 現在支援: -- ✅ Swagger 2.0 (OpenAPI 2.0) -- ✅ OpenAPI 3.0 -- ✅ JSON 和 YAML 輸出 -- ✅ 自動轉換 -- ✅ 完整的 go-zero API 特性支援 - -開始使用: -```bash -go-doc -a your-api.api -d output -s openapi3.0 -``` diff --git a/QUICK_START.md b/QUICK_START.md deleted file mode 100644 index 9f3b60a..0000000 --- a/QUICK_START.md +++ /dev/null @@ -1,103 +0,0 @@ -# 🚀 Quick Start Guide - -## 安裝 - -### 從原始碼編譯 -```bash -git clone -cd go-doc -make build -``` - -編譯完成後,執行檔位於 `bin/go-doc` - -## 基本使用 - -### 1️⃣ 生成 Swagger 2.0(預設) -```bash -./bin/go-doc -a example/example.api -d output -``` - -生成檔案:`output/example.json` - -### 2️⃣ 生成 OpenAPI 3.0 -```bash -./bin/go-doc -a example/example.api -d output -s openapi3.0 -``` - -### 3️⃣ 生成 YAML 格式 -```bash -./bin/go-doc -a example/example.api -d output -y -``` - -### 4️⃣ 自訂檔名 -```bash -./bin/go-doc -a example/example.api -d output -f my-api -``` - -## 常用命令組合 - -```bash -# Swagger 2.0 JSON -./bin/go-doc -a api.api -d docs - -# Swagger 2.0 YAML -./bin/go-doc -a api.api -d docs -y - -# OpenAPI 3.0 JSON -./bin/go-doc -a api.api -d docs -s openapi3.0 - -# OpenAPI 3.0 YAML -./bin/go-doc -a api.api -d docs -s openapi3.0 -y -``` - -## 完整參數 - -``` --a, --api string API 檔案路徑(必要) --d, --dir string 輸出目錄(必要) --f, --filename string 輸出檔名(不含副檔名) --s, --spec-version string 規格版本:swagger2.0 或 openapi3.0(預設:swagger2.0) --y, --yaml 生成 YAML 格式(預設:JSON) --h, --help 顯示說明 --v, --version 顯示版本 -``` - -## 使用 Makefile - -```bash -# 編譯 -make build - -# 生成範例 -make example - -# 清理 -make clean - -# 運行測試 -make test - -# 查看所有命令 -make help -``` - -## 測試所有格式 - -```bash -./test_all_formats.sh -``` - -## 查看範例 - -生成的範例檔案位於: -- `example/test_output/example_swagger2.json` - Swagger 2.0 JSON -- `example/test_output/example_swagger2.yaml` - Swagger 2.0 YAML -- `example/test_output/example_openapi3.json` - OpenAPI 3.0 JSON -- `example/test_output/example_openapi3.yaml` - OpenAPI 3.0 YAML - -## 更多資訊 - -- 完整文檔:`README.md` -- OpenAPI 3.0 指南:`OPENAPI3_GUIDE.md` -- 版本變更:`CHANGELOG.md` diff --git a/README.md b/README.md index cbd7dd5..74c8bf6 100644 --- a/README.md +++ b/README.md @@ -2,99 +2,128 @@ [![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.23-blue)](https://go.dev/) [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) +[![Version](https://img.shields.io/badge/version-1.2.0-blue)](CHANGELOG.md) -**go-doc** is a standalone tool that converts [go-zero](https://github.com/zeromicro/go-zero) `.api` files into OpenAPI specifications. +**go-doc** 是一個獨立的命令行工具,專門用於將 [go-zero](https://github.com/zeromicro/go-zero) `.api` 文件轉換為 OpenAPI 規範文檔。 -Originally part of the go-zero project, go-doc is now an independent, easy-to-use command-line tool for generating API documentation in both **Swagger 2.0** and **OpenAPI 3.0** formats. +原本是 go-zero 專案的一部分,現在已獨立出來,成為一個易於使用的工具,支援生成 **Swagger 2.0** 和 **OpenAPI 3.0** 兩種格式。 -## ✨ Features +--- -- 🚀 **Standalone Binary** - No dependencies on go-zero runtime -- 📝 **Dual Specification Support** - Generate both **Swagger 2.0** and **OpenAPI 3.0** formats -- 🎯 **Rich Type Support** - Handles structs, arrays, maps, pointers, and nested types -- 🏷️ **Tag-based Configuration** - Support for `json`, `form`, `path`, `header` tags -- 📊 **Advanced Validations** - Range, enum, default, example values -- 🔐 **Security Definitions** - Custom authentication configurations -- 📦 **Multiple Output Formats** - JSON or YAML output -- 🎨 **Definition References** - Optional use of definitions/schemas for cleaner output -- 🔄 **Automatic Conversion** - Seamless conversion from Swagger 2.0 to OpenAPI 3.0 +## 📑 目錄 -## 📦 Installation +- [✨ 特性](#-特性) +- [📦 安裝](#-安裝) +- [🚀 快速開始](#-快速開始) +- [📖 API 文件格式](#-api-文件格式) +- [🆕 @respdoc 多狀態碼回應](#-respdoc-多狀態碼回應) +- [🔧 進階功能](#-進階功能) +- [🎯 OpenAPI 3.0 vs Swagger 2.0](#-openapi-30-vs-swagger-20) +- [💡 使用範例](#-使用範例) +- [🧪 測試](#-測試) +- [📚 專案結構](#-專案結構) +- [🔄 從 go-zero 遷移](#-從-go-zero-遷移) +- [📝 版本記錄](#-版本記錄) +- [❓ 常見問題](#-常見問題) +- [🤝 貢獻](#-貢獻) +- [📄 授權](#-授權) -### From Source +--- + +## ✨ 特性 + +- 🚀 **獨立二進制** - 無需依賴 go-zero 運行時 +- 📝 **雙規格支援** - 生成 **Swagger 2.0** 和 **OpenAPI 3.0** 格式 +- 🎯 **豐富的類型支援** - 處理結構體、陣列、映射、指針和嵌套類型 +- 🏷️ **基於標籤的配置** - 支援 `json`、`form`、`path`、`header` 標籤 +- 📊 **進階驗證** - 範圍、枚舉、預設值、範例值 +- 🔐 **安全定義** - 自定義身份驗證配置 +- 📦 **多種輸出格式** - JSON 或 YAML 輸出 +- 🎨 **定義引用** - 可選使用 definitions/schemas 使輸出更簡潔 +- 🔄 **自動轉換** - Swagger 2.0 到 OpenAPI 3.0 的無縫轉換 +- **🆕 多狀態碼回應** - 使用 `@respdoc` 定義多個 HTTP 狀態碼 +- **🆕 業務錯誤碼映射** - 將業務錯誤碼映射到不同的錯誤類型,OpenAPI 3.0 使用 `oneOf` + +--- + +## 📦 安裝 + +### 從源碼編譯 ```bash -git clone https://github.com/danielchan-25/go-doc.git +git clone cd go-doc -go build -o bin/go-doc ./cmd/go-doc +make build ``` -### Using Go Install +編譯完成後,執行檔位於 `bin/go-doc` + +### 使用 Go Install ```bash go install github.com/danielchan-25/go-doc/cmd/go-doc@latest ``` -## 🚀 Quick Start +--- -### Basic Usage +## 🚀 快速開始 + +### 基本使用 ```bash -# Generate Swagger 2.0 (default) +# 生成 Swagger 2.0(預設) go-doc -a example/example.api -d output -# Generate OpenAPI 3.0 +# 生成 OpenAPI 3.0(推薦) go-doc -a example/example.api -d output -s openapi3.0 -# Generate YAML format +# 生成 YAML 格式 go-doc -a example/example.api -d output -y -# Generate OpenAPI 3.0 in YAML +# 生成 OpenAPI 3.0 YAML go-doc -a example/example.api -d output -s openapi3.0 -y -# Specify custom filename +# 自訂檔名 go-doc -a example/example.api -d output -f my-api ``` -### Command Line Options +### 命令行選項 ``` Flags: - -a, --api string API file path (required) - -d, --dir string Output directory (required) - -f, --filename string Output filename without extension (optional, defaults to API filename) - -h, --help help for go-doc - -s, --spec-version string OpenAPI specification version: swagger2.0 or openapi3.0 (default "swagger2.0") - -v, --version version for go-doc - -y, --yaml Generate YAML format instead of JSON + -a, --api string API 文件路徑(必要) + -d, --dir string 輸出目錄(必要) + -f, --filename string 輸出檔名(不含副檔名) + -h, --help 顯示說明 + -s, --spec-version string OpenAPI 規格版本:swagger2.0 或 openapi3.0(預設:swagger2.0) + -v, --version 顯示版本 + -y, --yaml 生成 YAML 格式(預設:JSON) ``` -### Specification Versions +### 使用 Makefile -#### Swagger 2.0 (Default) ```bash -go-doc -a example/example.api -d output -# or explicitly -go-doc -a example/example.api -d output -s swagger2.0 +# 編譯 +make build + +# 生成範例 +make example + +# 清理 +make clean + +# 運行測試 +make test + +# 查看所有命令 +make help ``` -#### OpenAPI 3.0 -```bash -go-doc -a example/example.api -d output -s openapi3.0 -``` +--- -**Key Differences:** -- **Swagger 2.0**: Uses `host`, `basePath`, `schemes` at root level -- **OpenAPI 3.0**: Uses `servers` array with complete URLs -- **Swagger 2.0**: Definitions under `definitions` -- **OpenAPI 3.0**: Schemas under `components/schemas` -- **Swagger 2.0**: Security definitions under `securityDefinitions` -- **OpenAPI 3.0**: Security schemes under `components/securitySchemes` -- **OpenAPI 3.0**: Request bodies are separated from parameters +## 📖 API 文件格式 -## 📖 API File Format - -### Basic Structure +### 基本結構 ```go syntax = "v1" @@ -127,46 +156,46 @@ service MyAPI { } ``` -### Supported Info Properties +### 支援的 Info 屬性 -- `title` - API title -- `description` - API description -- `version` - API version -- `host` - API host (e.g., "api.example.com") -- `basePath` - Base path (e.g., "/v1") -- `schemes` - Protocols (e.g., "http,https") -- `consumes` - Request content types -- `produces` - Response content types -- `contactName`, `contactURL`, `contactEmail` - Contact information -- `licenseName`, `licenseURL` - License information -- `useDefinitions` - Use Swagger definitions (true/false) -- `wrapCodeMsg` - Wrap response in `{code, msg, data}` structure -- `securityDefinitionsFromJson` - Security definitions in JSON format +- `title` - API 標題 +- `description` - API 描述 +- `version` - API 版本 +- `host` - API 主機(如 "api.example.com") +- `basePath` - 基礎路徑(如 "/v1") +- `schemes` - 協議(如 "http,https") +- `consumes` - 請求內容類型 +- `produces` - 回應內容類型 +- `contactName`, `contactURL`, `contactEmail` - 聯絡資訊 +- `licenseName`, `licenseURL` - 授權資訊 +- `useDefinitions` - 使用 Swagger definitions(true/false) +- `wrapCodeMsg` - 將回應包裝在 `{code, msg, data}` 結構中 +- `securityDefinitionsFromJson` - JSON 格式的安全定義 -### Tag Options +### 標籤選項 -#### JSON Tags +#### JSON 標籤 ```go type Example { - // Range validation + // 範圍驗證 Age int `json:"age,range=[1:150]"` - // Default value + // 預設值 Status string `json:"status,default=active"` - // Example value + // 範例值 Email string `json:"email,example=user@example.com"` - // Enum values + // 枚舉值 Role string `json:"role,options=admin|user|guest"` - // Optional field + // 可選欄位 Phone string `json:"phone,optional"` } ``` -#### Form Tags +#### Form 標籤 ```go type QueryRequest { @@ -176,7 +205,7 @@ type QueryRequest { } ``` -#### Path Tags +#### Path 標籤 ```go type PathRequest { @@ -184,7 +213,7 @@ type PathRequest { } ``` -#### Header Tags +#### Header 標籤 ```go type HeaderRequest { @@ -192,9 +221,225 @@ type HeaderRequest { } ``` -## 🔧 Advanced Features +--- -### Security Definitions +## 🆕 @respdoc 多狀態碼回應 + +### 概述 + +`@respdoc` 註解允許您為單個 API 端點定義多個 HTTP 狀態碼的回應,並支援為同一狀態碼定義多種業務錯誤格式。 + +**重要特性:** +- ✅ 支援多個 HTTP 狀態碼(200, 201, 400, 401, 404, 500 等) +- ✅ 支援業務錯誤碼映射(如 300101, 300102 等) +- ✅ **OpenAPI 3.0 使用 `oneOf` 表示多種錯誤格式** +- ✅ Swagger 2.0 在描述中列出所有可能的錯誤 + +### 基本語法 + +#### 單一回應類型 + +```go +service MyAPI { + @doc ( + description: "用戶查詢接口" + ) + /* + @respdoc-200 (UserResponse) // 成功 + @respdoc-400 (ErrorResponse) // 錯誤請求 + @respdoc-401 (UnauthorizedError) // 未授權 + @respdoc-500 (ServerError) // 服務器錯誤 + */ + @handler getUser + get /user/:id (UserRequest) returns (UserResponse) +} +``` + +#### 多業務錯誤碼(重點功能)⭐ + +```go +service MyAPI { + @doc ( + description: "創建訂單" + ) + /* + @respdoc-201 (OrderResponse) // 創建成功 + @respdoc-400 ( + 300101: (ValidationError) 參數驗證失敗 + 300102: (InsufficientStockError) 庫存不足 + 300103: (InvalidPaymentError) 支付方式無效 + ) // 客戶端錯誤 + @respdoc-401 (UnauthorizedError) // 未授權 + @respdoc-500 (ServerError) // 服務器錯誤 + */ + @handler createOrder + post /order (OrderRequest) returns (OrderResponse) +} +``` + +### 完整範例 + +```go +syntax = "v1" + +info ( + title: "訂單 API" + description: "訂單管理系統" + version: "v1" + host: "api.example.com" + basePath: "/v1" + useDefinitions: true +) + +type ( + CreateOrderReq { + ProductID string `json:"product_id"` + Quantity int `json:"quantity"` + PaymentMethod string `json:"payment_method"` + } + + OrderResponse { + OrderID string `json:"order_id"` + Status string `json:"status"` + Total float64 `json:"total"` + } + + ValidationError { + Code string `json:"code"` + Message string `json:"message"` + Fields []string `json:"fields"` + } + + InsufficientStockError { + Code string `json:"code"` + Message string `json:"message"` + ProductID string `json:"product_id"` + Required int `json:"required"` + Available int `json:"available"` + } + + InvalidPaymentError { + Code string `json:"code"` + Message string `json:"message"` + Method string `json:"method"` + } + + UnauthorizedError { + Code string `json:"code"` + Message string `json:"message"` + Reason string `json:"reason"` + } + + ServerError { + Code string `json:"code"` + Message string `json:"message"` + TraceID string `json:"trace_id"` + } +) + +@server ( + tags: "order" +) +service OrderAPI { + @doc ( + description: "創建訂單" + ) + /* + @respdoc-201 (OrderResponse) // 創建成功 + @respdoc-400 ( + 300101: (ValidationError) 參數驗證失敗 + 300102: (InsufficientStockError) 庫存不足 + 300103: (InvalidPaymentError) 支付方式無效 + ) // 客戶端錯誤 + @respdoc-401 (UnauthorizedError) // 未授權 + @respdoc-500 (ServerError) // 服務器錯誤 + */ + @handler createOrder + post /order (CreateOrderReq) returns (OrderResponse) +} +``` + +### 生成結果對比 + +#### Swagger 2.0 輸出 + +```json +{ + "paths": { + "/v1/order": { + "post": { + "responses": { + "201": { + "description": "// 創建成功", + "schema": { + "$ref": "#/definitions/OrderResponse" + } + }, + "400": { + "description": "客戶端錯誤\n\nPossible errors:\n300101: ValidationError - 參數驗證失敗\n300102: InsufficientStockError - 庫存不足\n300103: InvalidPaymentError - 支付方式無效", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + } + } + } +} +``` + +#### OpenAPI 3.0 輸出(使用 oneOf)⭐ + +```json +{ + "paths": { + "/v1/order": { + "post": { + "responses": { + "201": { + "description": "// 創建成功", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrderResponse" + } + } + } + }, + "400": { + "description": "客戶端錯誤", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ValidationError" + }, + { + "$ref": "#/components/schemas/InsufficientStockError" + }, + { + "$ref": "#/components/schemas/InvalidPaymentError" + } + ] + } + } + } + } + } + } + } + } +} +``` + +**優勢:** OpenAPI 3.0 使用 `oneOf` 精確表示多種可能的錯誤類型! + +--- + +## 🔧 進階功能 + +### 安全定義 ```go info ( @@ -212,11 +457,11 @@ info ( authType: apiKey ) service MyAPI { - // Routes here will use apiKey authentication + // 此處的路由將使用 apiKey 身份驗證 } ``` -### Code-Msg Wrapper +### Code-Msg 包裝 ```go info ( @@ -225,7 +470,7 @@ info ( ) ``` -Response will be wrapped as: +回應將被包裝為: ```json { "code": 0, @@ -234,48 +479,407 @@ Response will be wrapped as: } ``` -## 📂 Project Structure +--- + +## 🎯 OpenAPI 3.0 vs Swagger 2.0 + +### 規格版本 + +#### Swagger 2.0(預設) +```bash +go-doc -a example/example.api -d output +# 或明確指定 +go-doc -a example/example.api -d output -s swagger2.0 +``` + +#### OpenAPI 3.0(推薦) +```bash +go-doc -a example/example.api -d output -s openapi3.0 +``` + +### 主要差異 + +| 特性 | Swagger 2.0 | OpenAPI 3.0 | +|:---|:---|:---| +| **版本聲明** | `swagger: "2.0"` | `openapi: "3.0.3"` | +| **服務器** | `host`, `basePath`, `schemes` | `servers` 陣列 | +| **Schema 定義** | `definitions` | `components/schemas` | +| **安全定義** | `securityDefinitions` | `components/securitySchemes` | +| **請求體** | `parameters[in=body]` | `requestBody` | +| **多類型回應** | ❌ 不支援 | ✅ `oneOf`/`anyOf`/`allOf` | + +### 範例對比 + +**Swagger 2.0:** +```json +{ + "swagger": "2.0", + "host": "example.com", + "basePath": "/v1", + "definitions": { + "User": {...} + }, + "securityDefinitions": { + "apiKey": {...} + } +} +``` + +**OpenAPI 3.0:** +```json +{ + "openapi": "3.0.3", + "servers": [ + {"url": "http://example.com/v1"}, + {"url": "https://example.com/v1"} + ], + "components": { + "schemas": { + "User": {...} + }, + "securitySchemes": { + "apiKey": {...} + } + } +} +``` + +--- + +## 💡 使用範例 + +### 場景 1:RESTful CRUD + +```go +/* + @respdoc-200 (UserResponse) // 獲取成功 + @respdoc-404 (NotFoundError) // 用戶不存在 + @respdoc-401 (UnauthorizedError) // 未授權 +*/ +@handler getUser +get /user/:id (UserIdReq) returns (UserResponse) + +/* + @respdoc-201 (UserResponse) // 創建成功 + @respdoc-400 (ValidationError) // 參數錯誤 + @respdoc-409 (ConflictError) // 用戶已存在 +*/ +@handler createUser +post /user (CreateUserReq) returns (UserResponse) + +/* + @respdoc-200 (UserResponse) // 更新成功 + @respdoc-400 (ValidationError) // 參數錯誤 + @respdoc-404 (NotFoundError) // 用戶不存在 +*/ +@handler updateUser +put /user/:id (UpdateUserReq) returns (UserResponse) + +/* + @respdoc-204 // 刪除成功 + @respdoc-404 (NotFoundError) // 用戶不存在 +*/ +@handler deleteUser +delete /user/:id (UserIdReq) +``` + +### 場景 2:複雜業務流程 + +```go +/* + @respdoc-200 (PaymentResponse) // 支付成功 + @respdoc-400 ( + 400001: (InsufficientBalanceError) 餘額不足 + 400002: (InvalidCardError) 無效的卡號 + 400003: (ExpiredCardError) 卡已過期 + 400004: (DailyLimitError) 超過每日限額 + ) // 支付失敗 + @respdoc-401 (UnauthorizedError) // 未授權 + @respdoc-422 (ProcessingError) // 處理中請勿重複提交 +*/ +@handler processPayment +post /payment (PaymentRequest) returns (PaymentResponse) +``` + +### 場景 3:狀態機轉換 + +```go +/* + @respdoc-200 (OrderResponse) // 狀態更新成功 + @respdoc-400 ( + 400001: (InvalidStateError) 無效的狀態轉換 + 400002: (OrderCancelledError) 訂單已取消 + 400003: (OrderCompletedError) 訂單已完成 + ) // 狀態轉換失敗 +*/ +@handler updateOrderStatus +put /order/:id/status (UpdateStatusReq) returns (OrderResponse) +``` + +--- + +## 🧪 測試 + +### 測試所有格式 + +```bash +# 測試 Swagger 2.0 和 OpenAPI 3.0 +./test_all_formats.sh + +# 測試 @respdoc 功能 +./test_respdoc.sh +``` + +### 手動測試 + +```bash +# 生成並檢查 +go-doc -a example/example_respdoc.api -d output -s openapi3.0 + +# 查看生成的文檔 +cat output/example_respdoc.json | jq '.paths."/v1/order".post.responses' +``` + +--- + +## 📚 專案結構 ``` go-doc/ ├── cmd/ -│ └── go-doc/ # Main entry point +│ └── go-doc/ # 主程式入口 │ └── main.go ├── internal/ -│ ├── swagger/ # Core swagger generation logic -│ │ ├── swagger.go -│ │ ├── parameter.go -│ │ ├── path.go +│ ├── swagger/ # 核心邏輯 +│ │ ├── respdoc.go # @respdoc 解析 +│ │ ├── response.go # 回應生成 +│ │ ├── openapi3.go # OpenAPI 3.0 轉換 +│ │ ├── swagger.go # Swagger 2.0 生成 +│ │ ├── path.go # 路徑處理 +│ │ ├── parameter.go # 參數處理 +│ │ ├── definition.go # 定義處理 │ │ └── ... -│ └── util/ # Internal utilities +│ └── util/ # 工具函數 │ ├── util.go │ ├── stringx.go │ └── pathx.go -├── example/ # Example API files -│ ├── example.api -│ └── example_cn.api +├── example/ +│ ├── example.api # 範例 API +│ ├── example_cn.api # 中文範例 +│ ├── example_respdoc.api # @respdoc 範例 +│ └── test_output/ # 生成的文檔 +├── bin/ # 編譯後的執行檔 ├── go.mod -└── README.md +├── go.sum +├── Makefile +├── README.md +├── LICENSE +├── CHANGELOG.md +├── test_all_formats.sh # 測試腳本 +└── test_respdoc.sh # @respdoc 測試 ``` -## 🤝 Contributing - -Contributions are welcome! Please feel free to submit a Pull Request. - -## 📝 License - -This project is licensed under the MIT License. - -## 🙏 Acknowledgments - -- Original code from [go-zero](https://github.com/zeromicro/go-zero) project -- Built on top of [go-openapi/spec](https://github.com/go-openapi/spec) - -## 📧 Contact - -For questions or issues, please open an issue on GitHub. - --- -Made with ❤️ by extracting and enhancing go-zero's swagger generation capabilities. +## 🔄 從 go-zero 遷移 +### 主要變更 + +#### 1. 模組獨立 + +**之前(go-zero plugin):** +```go +// 屬於 go-zero 內部工具 +package swagger // in tools/goctl/api/plugin/swagger/ +``` + +**現在(standalone):** +```go +module go-doc + +package swagger // in internal/swagger/ +``` + +#### 2. 依賴簡化 + +| 依賴 | 替換為 | 原因 | +|:---|:---|:---| +| `go-zero/tools/goctl/internal/version` | 自定義版本 | 內部包不可訪問 | +| `go-zero/tools/goctl/util` | `go-doc/internal/util` | 自包含工具 | +| `go-zero/tools/goctl/util/stringx` | `go-doc/internal/util` | 自定義字串處理 | +| `go-zero/tools/goctl/util/pathx` | `go-doc/internal/util` | 自定義路徑處理 | +| `google.golang.org/grpc/metadata` | 原生 map 處理 | 移除外部依賴 | + +#### 3. 保留的依賴 + +- ✅ `github.com/go-openapi/spec` - Swagger 2.0 +- ✅ `github.com/getkin/kin-openapi` - OpenAPI 3.0 +- ✅ `github.com/spf13/cobra` - CLI 框架 +- ✅ `github.com/zeromicro/go-zero/tools/goctl/api/spec` - API 解析 +- ✅ `gopkg.in/yaml.v2` - YAML 支援 + +### 遷移步驟 + +1. **安裝 go-doc** + ```bash + go build -o bin/go-doc ./cmd/go-doc + ``` + +2. **替換命令** + + **之前:** + ```bash + goctl api plugin -plugin goctl-swagger="swagger -filename api.json" -api user.api -dir . + ``` + + **現在:** + ```bash + go-doc -a user.api -d . -f api + ``` + +3. **新功能** + + 使用 OpenAPI 3.0: + ```bash + go-doc -a user.api -d . -f api -s openapi3.0 + ``` + + 使用 @respdoc: + ```go + /* + @respdoc-200 (Response) // 成功 + @respdoc-400 (Error) // 錯誤 + */ + ``` + +--- + +## 📝 版本記錄 + +### [1.2.0] - 2025-09-30 + +#### 新增 +- **@respdoc 註解支援** - 為單個端點定義多個 HTTP 狀態碼 +- **業務錯誤碼映射** - 將不同的業務錯誤碼映射到特定錯誤類型 +- **OpenAPI 3.0 oneOf 支援** - 使用 `oneOf` 表示多種可能的錯誤回應 +- 測試腳本 `test_respdoc.sh` +- 支援解析 `HandlerDoc` 註釋(在 `@doc()` 外) + +#### 增強 +- 回應生成現在支援多個狀態碼(200, 201, 400, 401, 404, 500 等) +- OpenAPI 3.0 生成器為業務錯誤碼創建 `oneOf` schema +- Swagger 2.0 在描述中列出所有可能的錯誤 + +### [1.1.0] - 2025-09-30 + +#### 新增 +- **OpenAPI 3.0 支援** - 生成 Swagger 2.0 和 OpenAPI 3.0 規格 +- 新增 `--spec-version` (`-s`) 標誌 +- 自動從 Swagger 2.0 轉換到 OpenAPI 3.0 +- 整合 `github.com/getkin/kin-openapi` 函式庫 + +### [1.0.0] - 2025-09-30 + +#### 新增 +- 初始發布為獨立工具 +- 從 go-zero 專案提取並獨立 +- 支援將 go-zero `.api` 文件轉換為 Swagger 2.0 規格 +- JSON 和 YAML 輸出格式 +- 使用 cobra 的命令行介面 + +--- + +## ❓ 常見問題 + +### Q: 應該使用哪個版本? +**A:** +- **Swagger 2.0**: 如果需要與舊工具或系統相容 +- **OpenAPI 3.0**: 推薦使用,功能更強大且為現代標準 + +### Q: 為什麼 OpenAPI 3.0 使用 oneOf? +**A:** `oneOf` 是 OpenAPI 3.0 的標準方式來表示"多選一"的 schema。這讓 API 文檔更精確,工具(如 Swagger UI、代碼生成器)可以正確理解和處理多種可能的回應類型。 + +### Q: Swagger 2.0 為什麼不支援 oneOf? +**A:** Swagger 2.0 規範不包含 `oneOf` 關鍵字。我們在描述中列出所有可能的錯誤,並使用第一個類型作為 schema 示例。 + +### Q: 可以同時生成兩種格式嗎? +**A:** 可以!執行兩次命令即可: +```bash +go-doc -a api.api -d out -f api-v2 +go-doc -a api.api -d out -f api-v3 -s openapi3.0 +``` + +### Q: 可以不定義業務錯誤碼嗎? +**A:** 可以!如果只有一種錯誤類型,直接使用單一回應格式: +```go +@respdoc-400 (ValidationError) // 參數錯誤 +``` + +### Q: 如何驗證生成的文檔? +**A:** 可以使用以下工具: +- **Swagger Editor**: https://editor.swagger.io/ +- **Swagger UI**: 本地運行或線上版本 +- **OpenAPI Generator**: 生成客戶端/伺服器代碼來驗證 + +--- + +## 🤝 貢獻 + +歡迎貢獻!請隨時提交 Pull Request。 + +### 開發 + +```bash +# 克隆專案 +git clone +cd go-doc + +# 安裝依賴 +go mod download + +# 運行測試 +make test + +# 編譯 +make build + +# 運行範例 +make example +``` + +--- + +## 📄 授權 + +MIT License + +Copyright (c) 2025 Daniel Chan + +本專案最初是 [go-zero](https://github.com/zeromicro/go-zero) 專案 swagger 生成插件的一部分。我們感謝 go-zero 團隊的出色工作。 + +--- + +## 🎉 總結 + +**go-doc v1.2.0** 提供了強大的 API 文檔生成功能: + +✅ **雙規格支援** - Swagger 2.0 和 OpenAPI 3.0 +✅ **多狀態碼回應** - 使用 @respdoc 定義完整的 HTTP 回應 +✅ **業務錯誤碼映射** - 支援複雜的錯誤場景 +✅ **OpenAPI 3.0 oneOf** - 專業的多類型回應表示 +✅ **完整測試** - 自動化測試腳本 +✅ **詳細文檔** - 完整的使用指南 + +開始使用: +```bash +go-doc -a your-api.api -d docs -s openapi3.0 +``` + +查看範例: +- 基本範例:`example/example.api` +- @respdoc 範例:`example/example_respdoc.api` +- 測試腳本:`./test_respdoc.sh` + +--- + +**🌟 如果這個專案對您有幫助,請給個 Star!** \ No newline at end of file diff --git a/cmd/go-doc/main.go b/cmd/go-doc/main.go index e4f45dc..2d7e352 100644 --- a/cmd/go-doc/main.go +++ b/cmd/go-doc/main.go @@ -9,7 +9,7 @@ import ( ) var ( - version = "1.1.0" + version = "1.2.0" commit = "dev" date = "unknown" ) diff --git a/example/example_respdoc.api b/example/example_respdoc.api index f60a4f6..c18b6ab 100644 --- a/example/example_respdoc.api +++ b/example/example_respdoc.api @@ -34,39 +34,39 @@ type ( Id int `json:"id,example=10"` Name string `json:"name,example=keson.an"` } - // 錯誤響應 - ErrorResponse struct { - Code string `json:"code" swagger:"description:錯誤代碼"` - Message string `json:"message" swagger:"description:錯誤訊息"` - Details string `json:"details,omitempty" swagger:"description:詳細信息"` - } - - // 具體錯誤類型 - ValidationError struct { - Code string `json:"code" swagger:"description:驗證錯誤代碼"` - Message string `json:"message" swagger:"description:驗證錯誤訊息"` - Fields []string `json:"fields" swagger:"description:錯誤字段列表"` - } - - InsufficientStockError struct { - Code string `json:"code" swagger:"description:庫存不足錯誤代碼"` - Message string `json:"message" swagger:"description:庫存不足錯誤訊息"` - ProductID string `json:"product_id" swagger:"description:商品ID"` - Required int `json:"required" swagger:"description:需要數量"` - Available int `json:"available" swagger:"description:可用數量"` - } - - InvalidPaymentError struct { - Code string `json:"code" swagger:"description:支付錯誤代碼"` - Message string `json:"message" swagger:"description:支付錯誤訊息"` - Method string `json:"method" swagger:"description:支付方式"` - } - - UnauthorizedError struct { - Code string `json:"code" swagger:"description:未授權錯誤代碼"` - Message string `json:"message" swagger:"description:未授權錯誤訊息"` - Reason string `json:"reason" swagger:"description:未授權原因"` - } + // 錯誤響應 + ErrorResponse { + Code string `json:"code"` + Message string `json:"message"` + Details string `json:"details,omitempty"` + } + + // 具體錯誤類型 + ValidationError { + Code string `json:"code"` + Message string `json:"message"` + Fields []string `json:"fields"` + } + + InsufficientStockError { + Code string `json:"code"` + Message string `json:"message"` + ProductID string `json:"product_id"` + Required int `json:"required"` + Available int `json:"available"` + } + + InvalidPaymentError { + Code string `json:"code"` + Message string `json:"message"` + Method string `json:"method"` + } + + UnauthorizedError { + Code string `json:"code"` + Message string `json:"message"` + Reason string `json:"reason"` + } ) @server ( @@ -79,7 +79,6 @@ type ( service Swagger { @doc ( description: "query 接口" - bizCodeEnumDescription: " 1003-用不存在
1004-非法操作" // 接口级别业务错误码枚举描述,会覆盖全局的业务错误码,json 格式,key 为业务错误码,value 为该错误码的描述,仅当 wrapCodeMsg 为 true 且 useDefinitions 为 false 时生效 ) /* @respdoc-201 (QueryResp) // 創建成功 diff --git a/example/go-swagger-cn.sh b/example/go-swagger-cn.sh deleted file mode 100644 index 2a9ac64..0000000 --- a/example/go-swagger-cn.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# 1. 检查并安装 swagger -if ! command -v swagger &> /dev/null; then - echo "swagger 未安装,正在从 GitHub 安装..." - # 这里使用 go-swagger 的安装方式 - go install github.com/go-swagger/go-swagger/cmd/swagger@latest - if [ $? -ne 0 ]; then - echo "安装 swagger 失败" - exit 1 - fi - echo "swagger 安装成功" -else - echo "swagger 已安装" -fi - -mkdir bin output - -export GOBIN=$(pwd)/bin - -# 2. 安装最新版 goctl -go install ../../.. -if [ $? -ne 0 ]; then - echo "安装 goctl 失败" - exit 1 -fi -echo "goctl 安装成功" - -# 3. 生成 swagger 文件 -echo "正在生成 swagger 文件..." -./bin/goctl api swagger --api example_cn.api --dir output -if [ $? -ne 0 ]; then - echo "生成 swagger 文件失败" - exit 1 -fi - -# 4. 启动 swagger 服务 -echo "启动 swagger 服务..." -swagger serve ./output/example_cn.json \ No newline at end of file diff --git a/example/go-swagger-ui-cn.png b/example/go-swagger-ui-cn.png deleted file mode 100644 index 6e461c6..0000000 Binary files a/example/go-swagger-ui-cn.png and /dev/null differ diff --git a/example/go-swagger-ui.png b/example/go-swagger-ui.png deleted file mode 100644 index 0d37662..0000000 Binary files a/example/go-swagger-ui.png and /dev/null differ diff --git a/example/go-swagger.sh b/example/go-swagger.sh deleted file mode 100644 index d1f221b..0000000 --- a/example/go-swagger.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# 1. Check and install swagger if not exists -if ! command -v swagger &> /dev/null; then - echo "swagger not found, installing from GitHub..." - # Using go-swagger installation method - go install github.com/go-swagger/go-swagger/cmd/swagger@latest - if [ $? -ne 0 ]; then - echo "Failed to install swagger" - exit 1 - fi - echo "swagger installed successfully" -else - echo "swagger already installed" -fi - -mkdir bin output - -export GOBIN=$(pwd)/bin - -# 2. Install latest goctl version -go install ../../.. -if [ $? -ne 0 ]; then - echo "Failed to install goctl" - exit 1 -fi -echo "goctl installed successfully" - -# 3. Generate swagger files -echo "Generating swagger files..." -./bin/goctl api swagger --api example.api --dir output -if [ $? -ne 0 ]; then - echo "Failed to generate swagger files" - exit 1 -fi - -# 4. Start swagger server -echo "Starting swagger server..." -swagger serve ./output/example.json \ No newline at end of file diff --git a/example/swagger-ui-cn-example.png b/example/swagger-ui-cn-example.png deleted file mode 100644 index b704cbc..0000000 Binary files a/example/swagger-ui-cn-example.png and /dev/null differ diff --git a/example/swagger-ui-cn-model.png b/example/swagger-ui-cn-model.png deleted file mode 100644 index bbeb4b9..0000000 Binary files a/example/swagger-ui-cn-model.png and /dev/null differ diff --git a/example/swagger-ui-cn.sh b/example/swagger-ui-cn.sh deleted file mode 100644 index 74ca27a..0000000 --- a/example/swagger-ui-cn.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash - -# 检查Docker是否运行的函数 -is_docker_running() { - if ! docker info >/dev/null 2>&1; then - return 1 # Docker未运行 - else - return 0 # Docker正在运行 - fi -} - -mkdir bin output - -export GOBIN=$(pwd)/bin - -# 1. 检查并安装Docker(如果不存在) -if ! command -v docker &> /dev/null; then - echo "未检测到Docker,正在尝试安装..." - - # 使用官方脚本安装Docker - curl -fsSL https://get.docker.com -o get-docker.sh - sudo sh get-docker.sh - rm get-docker.sh - - # 验证安装 - if ! command -v docker &> /dev/null; then - echo "Docker安装失败" - exit 1 - fi - - # 将当前用户加入docker组(可能需要重新登录) - sudo usermod -aG docker $USER - echo "Docker安装成功。您可能需要注销并重新登录使更改生效。" -else - echo "Docker已安装" -fi - -# 2. 安装最新版goctl -go install ../../.. -if [ $? -ne 0 ]; then - echo "goctl安装失败" - exit 1 -fi -echo "goctl 安装成功" - -# 3. 生成swagger文件 -echo "正在生成swagger文件..." -./bin/goctl api swagger --api example_cn.api --dir output -if [ $? -ne 0 ]; then - echo "swagger文件生成失败" - exit 1 -fi - -# 检查Docker是否运行 -if ! is_docker_running; then - echo "Docker未运行,请先启动Docker服务" - exit 1 -fi - -# 4. 清理现有的swagger-ui容器 -echo "正在清理现有的swagger-ui容器..." -docker rm -f swagger-ui 2>/dev/null && echo "已移除现有的swagger-ui容器" - -# 5. 在Docker中运行swagger-ui -echo "正在启动swagger-ui容器..." -docker run -d --name swagger-ui -p 8080:8080 \ - -e SWAGGER_JSON=/tmp/example.json \ - -v $(pwd)/output/example_cn.json:/tmp/example.json \ - swaggerapi/swagger-ui - -if [ $? -ne 0 ]; then - echo "swagger-ui容器启动失败" - exit 1 -fi - -# 等待1秒确保服务就绪 -echo "等待swagger-ui初始化..." -sleep 1 - -# 显示访问信息并尝试打开浏览器 -SWAGGER_URL="http://localhost:8080" -echo -e "\nSwagger UI 已准备就绪,访问地址: \033[1;34m${SWAGGER_URL}\033[0m" -echo "正在尝试在默认浏览器中打开..." - -# 跨平台打开浏览器 -case "$(uname -s)" in - Linux*) xdg-open "$SWAGGER_URL";; - Darwin*) open "$SWAGGER_URL";; - CYGWIN*|MINGW*|MSYS*) start "$SWAGGER_URL";; - *) echo "无法在当前操作系统自动打开浏览器";; -esac diff --git a/example/swagger-ui-example.png b/example/swagger-ui-example.png deleted file mode 100644 index 129fe2d..0000000 Binary files a/example/swagger-ui-example.png and /dev/null differ diff --git a/example/swagger-ui-model.png b/example/swagger-ui-model.png deleted file mode 100644 index cc70683..0000000 Binary files a/example/swagger-ui-model.png and /dev/null differ diff --git a/example/swagger-ui.sh b/example/swagger-ui.sh deleted file mode 100644 index 57c0358..0000000 --- a/example/swagger-ui.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash - -is_docker_running() { - if ! docker info >/dev/null 2>&1; then - return 1 # Docker is not running - else - return 0 # Docker is running - fi -} - -mkdir bin output - -export GOBIN=$(pwd)/bin - -# 1. Check and install Docker if not exists -if ! command -v docker &> /dev/null; then - echo "Docker not found, attempting to install..." - - # Install Docker using official installation script - curl -fsSL https://get.docker.com -o get-docker.sh - sudo sh get-docker.sh - rm get-docker.sh - - # Verify installation - if ! command -v docker &> /dev/null; then - echo "Failed to install Docker" - exit 1 - fi - - # Add current user to docker group (may require logout/login) - sudo usermod -aG docker $USER - echo "Docker installed successfully. You may need to logout and login again for changes to take effect." -else - echo "Docker already installed" -fi - -# 2. Install latest goctl version -go install ../../.. -if [ $? -ne 0 ]; then - echo "Failed to install goctl" - exit 1 -fi -echo "goctl installed successfully" - -# 3. Generate swagger files -echo "Generating swagger files..." -./bin/goctl api swagger --api example.api --dir output -if [ $? -ne 0 ]; then - echo "Failed to generate swagger files" - exit 1 -fi - -if ! is_docker_running; then - echo "Docker is not running, Pls start Docker first" -fi - -# 4. Clean up any existing swagger-ui container -echo "Cleaning up existing swagger-ui containers..." -docker rm -f swagger-ui 2>/dev/null && echo "Removed existing swagger-ui container" - -# 5. Run swagger-ui in Docker -echo "Starting swagger-ui in Docker..." -docker run -d --name swagger-ui -p 8080:8080 -e SWAGGER_JSON=/tmp/example.json -v $(pwd)/output/example.json:/tmp/example.json swaggerapi/swagger-ui -if [ $? -ne 0 ]; then - echo "Failed to start swagger-ui container" - exit 1 -fi - -echo "Waiting for swagger-ui to initialize..." -sleep 1 -SWAGGER_URL="http://localhost:8080" -echo -e "\nSwagger UI is ready at: \033[1;34m${SWAGGER_URL}\033[0m" -echo "Opening in default browser..." - -case "$(uname -s)" in - Linux*) xdg-open "$SWAGGER_URL";; - Darwin*) open "$SWAGGER_URL";; - CYGWIN*|MINGW*|MSYS*) start "$SWAGGER_URL";; - *) echo "System not supported";; -esac \ No newline at end of file diff --git a/internal/swagger/context.go b/internal/swagger/context.go index a1ae909..92c2b12 100644 --- a/internal/swagger/context.go +++ b/internal/swagger/context.go @@ -7,6 +7,7 @@ import ( ) type Context struct { + Api *spec.ApiSpec // API 規範,用於查找類型定義 UseDefinitions bool WrapCodeMsg bool BizCodeEnumDescription string @@ -16,13 +17,16 @@ func testingContext(_ *testing.T) Context { return Context{} } -func contextFromApi(info spec.Info) Context { - if len(info.Properties) == 0 { - return Context{} +func contextFromApi(api *spec.ApiSpec) Context { + if len(api.Info.Properties) == 0 { + return Context{ + Api: api, + } } return Context{ - UseDefinitions: getBoolFromKVOrDefault(info.Properties, propertyKeyUseDefinitions, defaultValueOfPropertyUseDefinition), - WrapCodeMsg: getBoolFromKVOrDefault(info.Properties, propertyKeyWrapCodeMsg, false), - BizCodeEnumDescription: getStringFromKVOrDefault(info.Properties, propertyKeyBizCodeEnumDescription, "business code"), + Api: api, + UseDefinitions: getBoolFromKVOrDefault(api.Info.Properties, propertyKeyUseDefinitions, defaultValueOfPropertyUseDefinition), + WrapCodeMsg: getBoolFromKVOrDefault(api.Info.Properties, propertyKeyWrapCodeMsg, false), + BizCodeEnumDescription: getStringFromKVOrDefault(api.Info.Properties, propertyKeyBizCodeEnumDescription, "business code"), } } diff --git a/internal/swagger/openapi3.go b/internal/swagger/openapi3.go index dcf1500..54b96ac 100644 --- a/internal/swagger/openapi3.go +++ b/internal/swagger/openapi3.go @@ -266,17 +266,91 @@ func convertResponse(response *spec.Response, produces []string) *openapi3.Respo if response.Schema != nil { newResp.Content = make(openapi3.Content) - for _, contentType := range produces { - mediaType := &openapi3.MediaType{ - Schema: convertSchemaToSchemaRef(response.Schema), + + // 檢查是否有業務錯誤碼(x-biz-codes) + bizCodes, hasBizCodes := response.Extensions["x-biz-codes"] + + if hasBizCodes { + // 使用 oneOf 來表示多個可能的回應類型 + for _, contentType := range produces { + mediaType := &openapi3.MediaType{ + Schema: createOneOfSchemaFromBizCodes(bizCodes), + } + newResp.Content[contentType] = mediaType + } + } else { + // 普通回應 + for _, contentType := range produces { + mediaType := &openapi3.MediaType{ + Schema: convertSchemaToSchemaRef(response.Schema), + } + newResp.Content[contentType] = mediaType } - newResp.Content[contentType] = mediaType } } return &openapi3.ResponseRef{Value: newResp} } +// createOneOfSchemaFromBizCodes 從業務錯誤碼創建 oneOf schema +func createOneOfSchemaFromBizCodes(bizCodesInterface interface{}) *openapi3.SchemaRef { + bizCodes, ok := bizCodesInterface.(map[string]BizCode) + if !ok { + // 嘗試轉換為 map[string]interface{} + bizCodesMap, ok := bizCodesInterface.(map[string]interface{}) + if !ok { + return nil + } + + // 轉換為 BizCode + bizCodes = make(map[string]BizCode) + for code, val := range bizCodesMap { + if valMap, ok := val.(map[string]interface{}); ok { + bizCode := BizCode{ + Code: code, + } + if schema, ok := valMap["Schema"].(string); ok { + bizCode.Schema = schema + } + if desc, ok := valMap["Description"].(string); ok { + bizCode.Description = desc + } + bizCodes[code] = bizCode + } + } + } + + if len(bizCodes) == 0 { + return nil + } + + // 創建 oneOf schema + oneOfSchemas := make([]*openapi3.SchemaRef, 0, len(bizCodes)) + for code, bizCode := range bizCodes { + // 創建每個業務錯誤碼的 schema 引用 + ref := "#/components/schemas/" + bizCode.Schema + schemaRef := &openapi3.SchemaRef{ + Ref: ref, + } + + // 添加描述作為擴展 + if bizCode.Description != "" { + schemaRef.Value = &openapi3.Schema{ + Description: "業務錯誤碼 " + code + ": " + bizCode.Description, + } + } + + oneOfSchemas = append(oneOfSchemas, schemaRef) + } + + // 返回包含 oneOf 的 schema + return &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + OneOf: oneOfSchemas, + }, + } +} + // convertSchemaToSchemaRef converts a Swagger 2.0 schema to OpenAPI 3.0 SchemaRef func convertSchemaToSchemaRef(schema *spec.Schema) *openapi3.SchemaRef { if schema == nil { diff --git a/internal/swagger/path.go b/internal/swagger/path.go index 6ba68ae..831fa4b 100644 --- a/internal/swagger/path.go +++ b/internal/swagger/path.go @@ -89,7 +89,7 @@ func spec2Path(ctx Context, group apiSpec.Group, route apiSpec.Route) spec.PathI Deprecated: getBoolFromKVOrDefault(route.AtDoc.Properties, propertyKeyDeprecated, false), Parameters: parametersFromType(ctx, route.Method, route.RequestType), Security: security, - Responses: jsonResponseFromType(ctx, route.AtDoc, route.ResponseType), + Responses: jsonResponseFromTypeWithDocs(ctx, route.AtDoc, route.ResponseType, route.HandlerDoc), }, } externalDocsDescription := getStringFromKVOrDefault(route.AtDoc.Properties, propertyKeyExternalDocsDescription, "") diff --git a/internal/swagger/respdoc.go b/internal/swagger/respdoc.go new file mode 100644 index 0000000..45319e7 --- /dev/null +++ b/internal/swagger/respdoc.go @@ -0,0 +1,249 @@ +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) + } +} diff --git a/internal/swagger/response.go b/internal/swagger/response.go index 3940a8c..3e3fcc2 100644 --- a/internal/swagger/response.go +++ b/internal/swagger/response.go @@ -2,12 +2,31 @@ package swagger import ( "net/http" + "strings" "github.com/go-openapi/spec" apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec" ) func jsonResponseFromType(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type) *spec.Responses { + return jsonResponseFromTypeWithDocs(ctx, atDoc, tp, nil) +} + +func jsonResponseFromTypeWithDocs(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type, handlerDoc apiSpec.Doc) *spec.Responses { + // 首先檢查是否有 @respdoc 註解(從 handlerDoc 或 atDoc) + var respDocs []RespDoc + if len(handlerDoc) > 0 { + respDocs = parseRespDocsFromDoc(handlerDoc) + } + if len(respDocs) == 0 { + respDocs = parseRespDocs(atDoc) + } + + if len(respDocs) > 0 { + return jsonResponseFromRespDocs(ctx, atDoc, respDocs, tp) + } + + // 原有邏輯:使用默認的 200 回應 if tp == nil { return &spec.Responses{ ResponsesProps: spec.ResponsesProps{ @@ -63,3 +82,120 @@ func jsonResponseFromType(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type) *sp }, } } + +// jsonResponseFromRespDocs 從 @respdoc 註解生成多狀態碼回應 +func jsonResponseFromRespDocs(ctx Context, atDoc apiSpec.AtDoc, respDocs []RespDoc, defaultType apiSpec.Type) *spec.Responses { + responses := &spec.Responses{ + ResponsesProps: spec.ResponsesProps{ + StatusCodeResponses: make(map[int]spec.Response), + }, + } + + for _, respDoc := range respDocs { + // 如果有單一 Schema,直接使用 + if respDoc.Schema != "" { + tp := findTypeByName(ctx.Api, respDoc.Schema) + if tp == nil && respDoc.StatusCode == http.StatusOK && defaultType != nil { + // 如果找不到類型且是 200,使用默認類型 + tp = defaultType + } + + response := createResponseFromType(ctx, atDoc, tp, respDoc.Description) + responses.StatusCodeResponses[respDoc.StatusCode] = response + continue + } + + // 如果有多個業務錯誤碼(BizCodes),為 Swagger 2.0 創建一個通用回應 + // OpenAPI 3.0 會在 convertSwagger2ToOpenAPI3 中特殊處理 + if len(respDoc.BizCodes) > 0 { + // 對於 Swagger 2.0,我們使用 oneOf/anyOf 概念的註釋 + // 但 Swagger 2.0 不支持 oneOf,所以使用第一個類型作為示例 + // 並在描述中列出所有可能的類型 + + var firstType apiSpec.Type + descriptions := []string{} + + for code, bizCode := range respDoc.BizCodes { + tp := findTypeByName(ctx.Api, bizCode.Schema) + if firstType == nil && tp != nil { + firstType = tp + } + desc := code + ": " + bizCode.Schema + if bizCode.Description != "" { + desc += " - " + bizCode.Description + } + descriptions = append(descriptions, desc) + } + + description := respDoc.Description + if len(descriptions) > 0 { + if description != "" { + description += "\n\n" + } + description += "Possible errors:\n" + strings.Join(descriptions, "\n") + } + + response := createResponseFromType(ctx, atDoc, firstType, description) + + // 在 VendorExtensible 中存儲業務錯誤碼信息,供 OpenAPI 3.0 使用 + if response.Extensions == nil { + response.Extensions = make(spec.Extensions) + } + response.Extensions["x-biz-codes"] = respDoc.BizCodes + + responses.StatusCodeResponses[respDoc.StatusCode] = response + } + } + + // 如果沒有定義 200 回應,添加默認的 + if _, ok := responses.StatusCodeResponses[http.StatusOK]; !ok && defaultType != nil { + responses.StatusCodeResponses[http.StatusOK] = createResponseFromType(ctx, atDoc, defaultType, "") + } + + return responses +} + +// createResponseFromType 從類型創建回應對象 +func createResponseFromType(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type, description string) spec.Response { + if tp == nil { + return spec.Response{ + ResponseProps: spec.ResponseProps{ + Description: description, + Schema: &spec.Schema{}, + }, + } + } + + props := spec.SchemaProps{ + AdditionalProperties: mapFromGoType(ctx, tp), + Items: itemsFromGoType(ctx, tp), + } + + if ctx.UseDefinitions { + structName, ok := containsStruct(tp) + if ok { + props.Ref = spec.MustCreateRef(getRefName(structName)) + return spec.Response{ + ResponseProps: spec.ResponseProps{ + Description: description, + Schema: &spec.Schema{ + SchemaProps: wrapCodeMsgProps(ctx, props, atDoc), + }, + }, + } + } + } + + p, _ := propertiesFromType(ctx, tp) + props.Type = typeFromGoType(ctx, tp) + props.Properties = p + + return spec.Response{ + ResponseProps: spec.ResponseProps{ + Description: description, + Schema: &spec.Schema{ + SchemaProps: wrapCodeMsgProps(ctx, props, atDoc), + }, + }, + } +} diff --git a/internal/swagger/swagger.go b/internal/swagger/swagger.go index 00e4d6e..9e1405d 100644 --- a/internal/swagger/swagger.go +++ b/internal/swagger/swagger.go @@ -10,7 +10,7 @@ import ( ) func spec2Swagger(api *apiSpec.ApiSpec) (*spec.Swagger, error) { - ctx := contextFromApi(api.Info) + ctx := contextFromApi(api) extensions, info := specExtensions(api.Info) var securityDefinitions spec.SecurityDefinitions securityDefinitionsFromJson := getStringFromKVOrDefault(api.Info.Properties, "securityDefinitionsFromJson", `{}`) diff --git a/test_respdoc.sh b/test_respdoc.sh new file mode 100755 index 0000000..5026490 --- /dev/null +++ b/test_respdoc.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# 測試 respdoc 功能 + +set -e + +echo "🧪 測試 @respdoc 多狀態碼回應功能" +echo "================================" + +OUTPUT_DIR="example/test_output" +mkdir -p "$OUTPUT_DIR" + +echo "" +echo "📝 測試 1: Swagger 2.0 with @respdoc" +./bin/go-doc -a example/example_respdoc.api -d "$OUTPUT_DIR" -f respdoc_swagger2 +CODES=$(jq -r '.paths."/v1/query".get.responses | keys | join(", ")' "$OUTPUT_DIR/respdoc_swagger2.json") +echo "✅ 狀態碼: $CODES" + +echo "" +echo "📝 測試 2: OpenAPI 3.0 with @respdoc" +./bin/go-doc -a example/example_respdoc.api -d "$OUTPUT_DIR" -f respdoc_openapi3 -s openapi3.0 +CODES=$(jq -r '.paths."/v1/query".get.responses | keys | join(", ")' "$OUTPUT_DIR/respdoc_openapi3.json") +echo "✅ 狀態碼: $CODES" + +echo "" +echo "📝 測試 3: 檢查 400 回應的 oneOf(OpenAPI 3.0)" +HAS_ONEOF=$(jq '.paths."/v1/query".get.responses."400".content."application/json".schema | has("oneOf")' "$OUTPUT_DIR/respdoc_openapi3.json") +if [ "$HAS_ONEOF" = "true" ]; then + TYPES=$(jq -r '.paths."/v1/query".get.responses."400".content."application/json".schema.oneOf | map(."$ref" | split("/") | last) | join(", ")' "$OUTPUT_DIR/respdoc_openapi3.json") + echo "✅ 使用 oneOf,包含類型: $TYPES" +else + echo "❌ 沒有使用 oneOf" + exit 1 +fi + +echo "" +echo "📝 測試 4: 檢查所有錯誤類型的 schema" +SCHEMAS=$(jq -r '.components.schemas | keys | map(select(. | contains("Error"))) | join(", ")' "$OUTPUT_DIR/respdoc_openapi3.json") +echo "✅ 錯誤類型 schemas: $SCHEMAS" + +echo "" +echo "📝 測試 5: Swagger 2.0 檢查業務錯誤碼描述" +DESC=$(jq -r '.paths."/v1/query".get.responses."400".description' "$OUTPUT_DIR/respdoc_swagger2.json" | head -3) +echo "✅ 400 描述:" +echo "$DESC" + +echo "" +echo "🎉 所有 @respdoc 測試通過!" +echo "" +echo "生成的檔案:" +ls -lh "$OUTPUT_DIR"/respdoc_*