From b7c67fb9e6399818efbb235fe9bfab0b6f676889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Tue, 30 Sep 2025 16:52:30 +0800 Subject: [PATCH] feat: init swagger project --- .gitignore | 1 + CHANGELOG.md | 20 +- Makefile | 16 +- OPENAPI3_GUIDE.md | 279 ++++++++++++++++++++++ QUICK_START.md | 103 +++++++++ README.md | 55 ++++- cmd/go-doc/main.go | 5 +- example/example_respdoc.api | 96 ++++++++ go.mod | 4 + go.sum | 16 +- internal/swagger/command.go | 41 +++- internal/swagger/openapi3.go | 432 +++++++++++++++++++++++++++++++++++ test_all_formats.sh | 103 +++++++++ 13 files changed, 1143 insertions(+), 28 deletions(-) create mode 100644 OPENAPI3_GUIDE.md create mode 100644 QUICK_START.md create mode 100644 example/example_respdoc.api create mode 100644 internal/swagger/openapi3.go create mode 100755 test_all_formats.sh diff --git a/.gitignore b/.gitignore index 59730af..71cff28 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ Thumbs.db # Output files example/test_output/ +test_output_verification/ diff --git a/CHANGELOG.md b/CHANGELOG.md index aec5bc2..1f093ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,30 @@ 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 OpenAPI 2.0 (Swagger) specification +- 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: diff --git a/Makefile b/Makefile index 7b9b540..4d02d12 100644 --- a/Makefile +++ b/Makefile @@ -67,10 +67,20 @@ build-all: ## Build for all platforms GOOS=windows GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe $(MAIN_PATH) @echo "✅ Multi-platform build complete" -example: build ## Generate example swagger files +example: build ## Generate example swagger and openapi files @echo "Generating examples..." @mkdir -p example/test_output - @$(BUILD_DIR)/$(BINARY_NAME) -a example/example.api -d example/test_output -f example - @$(BUILD_DIR)/$(BINARY_NAME) -a example/example.api -d example/test_output -f example -y + @echo " - Swagger 2.0 (JSON)..." + @$(BUILD_DIR)/$(BINARY_NAME) -a example/example.api -d example/test_output -f example_swagger2 + @echo " - Swagger 2.0 (YAML)..." + @$(BUILD_DIR)/$(BINARY_NAME) -a example/example.api -d example/test_output -f example_swagger2 -y + @echo " - OpenAPI 3.0 (JSON)..." + @$(BUILD_DIR)/$(BINARY_NAME) -a example/example.api -d example/test_output -f example_openapi3 -s openapi3.0 + @echo " - OpenAPI 3.0 (YAML)..." + @$(BUILD_DIR)/$(BINARY_NAME) -a example/example.api -d example/test_output -f example_openapi3 -s openapi3.0 -y + @echo " - Chinese Swagger 2.0..." + @$(BUILD_DIR)/$(BINARY_NAME) -a example/example_cn.api -d example/test_output -f example_cn_swagger2 + @echo " - Chinese OpenAPI 3.0..." + @$(BUILD_DIR)/$(BINARY_NAME) -a example/example_cn.api -d example/test_output -f example_cn_openapi3 -s openapi3.0 @echo "✅ Examples generated in example/test_output/" diff --git a/OPENAPI3_GUIDE.md b/OPENAPI3_GUIDE.md new file mode 100644 index 0000000..d03c1de --- /dev/null +++ b/OPENAPI3_GUIDE.md @@ -0,0 +1,279 @@ +# 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 new file mode 100644 index 0000000..9f3b60a --- /dev/null +++ b/QUICK_START.md @@ -0,0 +1,103 @@ +# 🚀 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 cc15b63..cbd7dd5 100644 --- a/README.md +++ b/README.md @@ -3,20 +3,21 @@ [![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) -**go-doc** is a standalone tool that converts [go-zero](https://github.com/zeromicro/go-zero) `.api` files into OpenAPI 2.0 (Swagger) specification. +**go-doc** is a standalone tool that converts [go-zero](https://github.com/zeromicro/go-zero) `.api` files into OpenAPI specifications. -Originally part of the go-zero project, go-doc is now an independent, easy-to-use command-line tool for generating API documentation. +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. ## ✨ Features - 🚀 **Standalone Binary** - No dependencies on go-zero runtime -- 📝 **Full Swagger 2.0 Support** - Complete OpenAPI specification generation +- 📝 **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 Swagger definitions for cleaner output +- 🎨 **Definition References** - Optional use of definitions/schemas for cleaner output +- 🔄 **Automatic Conversion** - Seamless conversion from Swagger 2.0 to OpenAPI 3.0 ## 📦 Installation @@ -39,12 +40,18 @@ go install github.com/danielchan-25/go-doc/cmd/go-doc@latest ### Basic Usage ```bash -# Generate JSON swagger file +# Generate Swagger 2.0 (default) go-doc -a example/example.api -d output -# Generate YAML swagger file +# Generate OpenAPI 3.0 +go-doc -a example/example.api -d output -s openapi3.0 + +# Generate YAML format go-doc -a example/example.api -d output -y +# Generate OpenAPI 3.0 in 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 ``` @@ -53,14 +60,38 @@ go-doc -a example/example.api -d output -f my-api ``` 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 - -v, --version version for go-doc - -y, --yaml Generate YAML format instead of JSON + -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 ``` +### Specification Versions + +#### 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 +``` + +#### 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 File Format ### Basic Structure diff --git a/cmd/go-doc/main.go b/cmd/go-doc/main.go index 961b463..e4f45dc 100644 --- a/cmd/go-doc/main.go +++ b/cmd/go-doc/main.go @@ -9,7 +9,7 @@ import ( ) var ( - version = "1.0.0" + version = "1.1.0" commit = "dev" date = "unknown" ) @@ -18,7 +18,7 @@ func main() { rootCmd := &cobra.Command{ Use: "go-doc", Short: "Generate Swagger documentation from go-zero API files", - Long: `go-doc is a tool that converts go-zero .api files into OpenAPI 2.0 (Swagger) specification.`, + Long: `go-doc is a tool that converts go-zero .api files into OpenAPI specifications (Swagger 2.0 or OpenAPI 3.0).`, Version: fmt.Sprintf("%s (commit: %s, built at: %s)", version, commit, date), RunE: swagger.Command, } @@ -27,6 +27,7 @@ func main() { rootCmd.Flags().StringVarP(&swagger.VarStringDir, "dir", "d", "", "Output directory (required)") rootCmd.Flags().StringVarP(&swagger.VarStringFilename, "filename", "f", "", "Output filename without extension (optional, defaults to API filename)") rootCmd.Flags().BoolVarP(&swagger.VarBoolYaml, "yaml", "y", false, "Generate YAML format instead of JSON") + rootCmd.Flags().StringVarP(&swagger.VarStringSpecVersion, "spec-version", "s", "swagger2.0", "OpenAPI specification version: swagger2.0 or openapi3.0") if err := rootCmd.MarkFlagRequired("api"); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) diff --git a/example/example_respdoc.api b/example/example_respdoc.api new file mode 100644 index 0000000..f60a4f6 --- /dev/null +++ b/example/example_respdoc.api @@ -0,0 +1,96 @@ +syntax = "v1" + +info ( + title: "演示 API" // 对应 swagger 的 title + description: "演示 api 生成 swagger 文件的 api 完整写法" // 对应 swagger 的 description + version: "v1" // 对应 swagger 的 version + termsOfService: "https://github.com/zeromicro/go-zero" // 对应 swagger 的 termsOfService + contactName: "keson.an" // 对应 swagger 的 contactName + contactURL: "https://github.com/zeromicro/go-zero" // 对应 swagger 的 contactURL + contactEmail: "example@gmail.com" // 对应 swagger 的 contactEmail + licenseName: "MIT" // 对应 swagger 的 licenseName + licenseURL: "https://github.com/zeromicro/go-zero" // 对应 swagger 的 licenseURL + consumes: "application/json" // 对应 swagger 的 consumes,不填默认为 application/json + produces: "application/json" // 对应 swagger 的 produces,不填默认为 application/json + schemes: "http,https" // 对应 swagger 的 schemes,不填默认为 https + host: "example.com" // 对应 swagger 的 host,不填默认为 127.0.0.1 + basePath: "/v1" // 对应 swagger 的 basePath,不填默认为 / + wrapCodeMsg: true // 是否用 code-msg 通用响应体,如果开启,则以格式 {"code":0,"msg":"OK","data":$data} 包括响应体 + bizCodeEnumDescription: "1001-未登录
1002-无权限操作" // 全局业务错误码枚举描述,json 格式,key 为业务错误码,value 为该错误码的描述,仅当 wrapCodeMsg 为 true 时生效 + // securityDefinitionsFromJson 为自定义鉴权配置,json 内容将直接放入 swagger 的 securityDefinitions 中, + // 格式参考 https://swagger.io/specification/v2/#security-definitions-object + // 在 api 的 @server 中可声明 authType 来指定其路由使用的鉴权类型 + securityDefinitionsFromJson: `{"apiKey":{"description":"apiKey 类型鉴权自定义","type":"apiKey","name":"x-api-key","in":"header"}}` + useDefinitions: true// 开启声明将生成models 进行关联,definitions 仅对响应体和 json 请求体生效 +) + +type ( + QueryReq { + Id int `form:"id,range=[1:10000],example=10"` + Name string `form:"name,example=keson.an"` + Avatar string `form:"avatar,optional,example=https://example.com/avatar.png"` + } + QueryResp { + 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:未授權原因"` + } +) + +@server ( + tags: "query 演示" // 对应 swagger 的 tags,可以对 swagger 中的 api 进行分组 + summary: "query 类型接口集合" // 对应 swagger 的 summary + prefix: v1 + authType: apiKey // 指定该路由使用的鉴权类型,值为 securityDefinitionsFromJson 中定义的名称 + group:"demo" +) +service Swagger { + @doc ( + description: "query 接口" + bizCodeEnumDescription: " 1003-用不存在
1004-非法操作" // 接口级别业务错误码枚举描述,会覆盖全局的业务错误码,json 格式,key 为业务错误码,value 为该错误码的描述,仅当 wrapCodeMsg 为 true 且 useDefinitions 为 false 时生效 + ) + /* + @respdoc-201 (QueryResp) // 創建成功 + @respdoc-400 ( + 300101: (ValidationError) 參數驗證失敗 + 300102: (InsufficientStockError) 庫存不足 + 300103: (InvalidPaymentError) 支付方式無效 + ) // 客戶端錯誤 + @respdoc-401 (UnauthorizedError) // 未授權 + @respdoc-500 (ErrorResponse) // 服務器錯誤 + */ + @handler query + get /query (QueryReq) returns (QueryResp) +} diff --git a/go.mod b/go.mod index e804c21..6ad39cc 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module go-doc go 1.23 require ( + github.com/getkin/kin-openapi v0.128.0 github.com/go-openapi/spec v0.21.1-0.20250328170532-a3928469592e github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.11.1 @@ -18,8 +19,11 @@ require ( github.com/go-openapi/swag v0.23.1 // indirect github.com/gookit/color v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/yaml v0.3.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect diff --git a/go.sum b/go.sum index 4cd8dc0..6d19005 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4= +github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= @@ -11,12 +13,16 @@ github.com/go-openapi/spec v0.21.1-0.20250328170532-a3928469592e h1:auobAirzhPsL github.com/go-openapi/spec v0.21.1-0.20250328170532-a3928469592e/go.mod h1:NAKTe9SplQBxIUlHlsuId1jk1I7bWTVV/2q/GtdRi6g= github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E= github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA= github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -25,10 +31,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= @@ -37,6 +47,8 @@ github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/zeromicro/go-zero v1.9.0 h1:hlVtQCSHPszQdcwZTawzGwTej1G2mhHybYzMRLuwCt4= diff --git a/internal/swagger/command.go b/internal/swagger/command.go index 4cbe104..8b4f1ad 100644 --- a/internal/swagger/command.go +++ b/internal/swagger/command.go @@ -25,6 +25,9 @@ var ( // VarBoolYaml specifies whether to generate a YAML file. VarBoolYaml bool + + // VarStringSpecVersion specifies the OpenAPI specification version (swagger2.0 or openapi3.0). + VarStringSpecVersion string ) func Command(_ *cobra.Command, _ []string) error { @@ -36,6 +39,11 @@ func Command(_ *cobra.Command, _ []string) error { return errors.New("missing -dir") } + // Validate spec version + if VarStringSpecVersion != "swagger2.0" && VarStringSpecVersion != "openapi3.0" { + return errors.New("spec-version must be either 'swagger2.0' or 'openapi3.0'") + } + api, err := parser.Parse(VarStringAPI, "") if err != nil { return err @@ -46,13 +54,30 @@ func Command(_ *cobra.Command, _ []string) error { if err := api.Validate(); err != nil { return err } - swagger, err := spec2Swagger(api) - if err != nil { - return err - } - data, err := json.MarshalIndent(swagger, "", " ") - if err != nil { - return err + + var data []byte + + if VarStringSpecVersion == "openapi3.0" { + // Generate OpenAPI 3.0 + swagger2, err := spec2Swagger(api) + if err != nil { + return err + } + openapi3Doc := convertSwagger2ToOpenAPI3(swagger2) + data, err = json.MarshalIndent(openapi3Doc, "", " ") + if err != nil { + return err + } + } else { + // Generate Swagger 2.0 (default) + swagger, err := spec2Swagger(api) + if err != nil { + return err + } + data, err = json.MarshalIndent(swagger, "", " ") + if err != nil { + return err + } } err = util.MkdirIfNotExist(VarStringDir) @@ -81,7 +106,7 @@ func Command(_ *cobra.Command, _ []string) error { return os.WriteFile(filePath, data, 0644) } - // generate json swagger file + // generate json file filePath := filepath.Join(VarStringDir, filename+".json") return os.WriteFile(filePath, data, 0644) } diff --git a/internal/swagger/openapi3.go b/internal/swagger/openapi3.go new file mode 100644 index 0000000..dcf1500 --- /dev/null +++ b/internal/swagger/openapi3.go @@ -0,0 +1,432 @@ +package swagger + +import ( + "github.com/getkin/kin-openapi/openapi3" + "github.com/go-openapi/spec" +) + +// convertSwagger2ToOpenAPI3 converts a Swagger 2.0 spec to OpenAPI 3.0 +func convertSwagger2ToOpenAPI3(swagger2 *spec.Swagger) *openapi3.T { + openapi3Doc := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{ + Title: swagger2.Info.Title, + Description: swagger2.Info.Description, + TermsOfService: swagger2.Info.TermsOfService, + Version: swagger2.Info.Version, + }, + Servers: openapi3.Servers{}, + Paths: &openapi3.Paths{}, + } + + // Convert extensions + if swagger2.Info.VendorExtensible.Extensions != nil { + openapi3Doc.Extensions = make(map[string]interface{}) + for k, v := range swagger2.Info.VendorExtensible.Extensions { + openapi3Doc.Extensions[k] = v + } + } + + // Add extensions from swagger root + if swagger2.Extensions != nil { + if openapi3Doc.Extensions == nil { + openapi3Doc.Extensions = make(map[string]interface{}) + } + for k, v := range swagger2.Extensions { + openapi3Doc.Extensions[k] = v + } + } + + // Convert Contact + if swagger2.Info.Contact != nil { + openapi3Doc.Info.Contact = &openapi3.Contact{ + Name: swagger2.Info.Contact.Name, + URL: swagger2.Info.Contact.URL, + Email: swagger2.Info.Contact.Email, + } + } + + // Convert License + if swagger2.Info.License != nil { + openapi3Doc.Info.License = &openapi3.License{ + Name: swagger2.Info.License.Name, + URL: swagger2.Info.License.URL, + } + } + + // Convert Servers from host, basePath, and schemes + if len(swagger2.Host) > 0 || len(swagger2.BasePath) > 0 { + schemes := swagger2.Schemes + if len(schemes) == 0 { + schemes = []string{"https"} + } + for _, scheme := range schemes { + serverURL := scheme + "://" + if len(swagger2.Host) > 0 { + serverURL += swagger2.Host + } else { + serverURL += "localhost" + } + if len(swagger2.BasePath) > 0 && swagger2.BasePath != "/" { + serverURL += swagger2.BasePath + } + openapi3Doc.Servers = append(openapi3Doc.Servers, &openapi3.Server{ + URL: serverURL, + }) + } + } + + // Convert Paths + openapi3Doc.Paths = openapi3.NewPaths() + for path, pathItem := range swagger2.Paths.Paths { + newPathItem := &openapi3.PathItem{} + + if pathItem.Get != nil { + newPathItem.Get = convertOperation(pathItem.Get) + } + if pathItem.Post != nil { + newPathItem.Post = convertOperation(pathItem.Post) + } + if pathItem.Put != nil { + newPathItem.Put = convertOperation(pathItem.Put) + } + if pathItem.Delete != nil { + newPathItem.Delete = convertOperation(pathItem.Delete) + } + if pathItem.Patch != nil { + newPathItem.Patch = convertOperation(pathItem.Patch) + } + if pathItem.Head != nil { + newPathItem.Head = convertOperation(pathItem.Head) + } + if pathItem.Options != nil { + newPathItem.Options = convertOperation(pathItem.Options) + } + + openapi3Doc.Paths.Set(path, newPathItem) + } + + // Convert Definitions to Components/Schemas + if len(swagger2.Definitions) > 0 { + openapi3Doc.Components = &openapi3.Components{ + Schemas: make(openapi3.Schemas), + } + for name, schema := range swagger2.Definitions { + openapi3Doc.Components.Schemas[name] = convertSchemaToSchemaRef(&schema) + } + } + + // Convert SecurityDefinitions to Components/SecuritySchemes + if len(swagger2.SecurityDefinitions) > 0 { + if openapi3Doc.Components == nil { + openapi3Doc.Components = &openapi3.Components{} + } + openapi3Doc.Components.SecuritySchemes = make(openapi3.SecuritySchemes) + for name, secDef := range swagger2.SecurityDefinitions { + openapi3Doc.Components.SecuritySchemes[name] = convertSecurityScheme(secDef) + } + } + + return openapi3Doc +} + +// convertOperation converts a Swagger 2.0 operation to OpenAPI 3.0 +func convertOperation(op *spec.Operation) *openapi3.Operation { + newOp := &openapi3.Operation{ + Tags: op.Tags, + Summary: op.Summary, + Description: op.Description, + OperationID: op.ID, + Parameters: openapi3.Parameters{}, + Responses: &openapi3.Responses{}, + Deprecated: op.Deprecated, + } + + // Convert ExternalDocs + if op.ExternalDocs != nil { + newOp.ExternalDocs = &openapi3.ExternalDocs{ + Description: op.ExternalDocs.Description, + URL: op.ExternalDocs.URL, + } + } + + // Convert Parameters and RequestBody + var bodyParam *spec.Parameter + for _, param := range op.Parameters { + if param.In == "body" { + bodyParam = ¶m + } else { + newOp.Parameters = append(newOp.Parameters, convertParameter(¶m)) + } + } + + // Convert body parameter to requestBody + if bodyParam != nil { + newOp.RequestBody = &openapi3.RequestBodyRef{ + Value: convertBodyParameter(bodyParam, op.Consumes), + } + } + + // Convert Responses + for code, response := range op.Responses.StatusCodeResponses { + newOp.Responses.Set(intToString(code), convertResponse(&response, op.Produces)) + } + + // Convert Security + if len(op.Security) > 0 { + newOp.Security = &openapi3.SecurityRequirements{} + for _, sec := range op.Security { + secReq := openapi3.SecurityRequirement{} + for key, scopes := range sec { + secReq[key] = scopes + } + *newOp.Security = append(*newOp.Security, secReq) + } + } + + return newOp +} + +// convertParameter converts a Swagger 2.0 parameter to OpenAPI 3.0 +func convertParameter(param *spec.Parameter) *openapi3.ParameterRef { + schema := &openapi3.Schema{ + Format: param.Format, + Default: param.Default, + } + if param.Type != "" { + schema.Type = &openapi3.Types{param.Type} + } + + newParam := &openapi3.Parameter{ + Name: param.Name, + In: param.In, + Description: param.Description, + Required: param.Required, + Schema: &openapi3.SchemaRef{Value: schema}, + } + + // Convert validation properties + if param.Maximum != nil { + max := *param.Maximum + newParam.Schema.Value.Max = &max + } + if param.Minimum != nil { + min := *param.Minimum + newParam.Schema.Value.Min = &min + } + if param.ExclusiveMaximum { + newParam.Schema.Value.ExclusiveMax = true + } + if param.ExclusiveMinimum { + newParam.Schema.Value.ExclusiveMin = true + } + if len(param.Enum) > 0 { + newParam.Schema.Value.Enum = param.Enum + } + + // Handle array items + if param.Items != nil { + newParam.Schema.Value.Items = convertItemsToSchemaRef(param.Items) + } + + return &openapi3.ParameterRef{Value: newParam} +} + +// convertBodyParameter converts a body parameter to RequestBody +func convertBodyParameter(param *spec.Parameter, consumes []string) *openapi3.RequestBody { + if len(consumes) == 0 { + consumes = []string{"application/json"} + } + + content := make(openapi3.Content) + for _, contentType := range consumes { + mediaType := &openapi3.MediaType{} + if param.Schema != nil { + mediaType.Schema = convertSchemaToSchemaRef(param.Schema) + } + content[contentType] = mediaType + } + + return &openapi3.RequestBody{ + Description: param.Description, + Required: param.Required, + Content: content, + } +} + +// convertResponse converts a Swagger 2.0 response to OpenAPI 3.0 +func convertResponse(response *spec.Response, produces []string) *openapi3.ResponseRef { + if len(produces) == 0 { + produces = []string{"application/json"} + } + + newResp := &openapi3.Response{ + Description: &response.Description, + } + + if response.Schema != nil { + newResp.Content = make(openapi3.Content) + for _, contentType := range produces { + mediaType := &openapi3.MediaType{ + Schema: convertSchemaToSchemaRef(response.Schema), + } + newResp.Content[contentType] = mediaType + } + } + + return &openapi3.ResponseRef{Value: newResp} +} + +// convertSchemaToSchemaRef converts a Swagger 2.0 schema to OpenAPI 3.0 SchemaRef +func convertSchemaToSchemaRef(schema *spec.Schema) *openapi3.SchemaRef { + if schema == nil { + return nil + } + + // Handle $ref + if schema.Ref.String() != "" { + ref := schema.Ref.String() + // Convert #/definitions/ to #/components/schemas/ + if len(ref) > 14 && ref[:14] == "#/definitions/" { + ref = "#/components/schemas/" + ref[14:] + } + return &openapi3.SchemaRef{Ref: ref} + } + + newSchema := &openapi3.Schema{ + Format: schema.Format, + Description: schema.Description, + Default: schema.Default, + Example: schema.Example, + Required: schema.Required, + } + + // Convert Type from StringOrArray to *Types + if len(schema.Type) > 0 { + types := openapi3.Types(schema.Type) + newSchema.Type = &types + } + + // Convert validation properties + if schema.Maximum != nil { + max := *schema.Maximum + newSchema.Max = &max + } + if schema.Minimum != nil { + min := *schema.Minimum + newSchema.Min = &min + } + if schema.ExclusiveMaximum { + newSchema.ExclusiveMax = true + } + if schema.ExclusiveMinimum { + newSchema.ExclusiveMin = true + } + if len(schema.Enum) > 0 { + newSchema.Enum = schema.Enum + } + + // Convert properties + if len(schema.Properties) > 0 { + newSchema.Properties = make(openapi3.Schemas) + for name, prop := range schema.Properties { + newSchema.Properties[name] = convertSchemaToSchemaRef(&prop) + } + } + + // Convert items + if schema.Items != nil && schema.Items.Schema != nil { + newSchema.Items = convertSchemaToSchemaRef(schema.Items.Schema) + } + + // Convert additionalProperties + if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { + newSchema.AdditionalProperties = openapi3.AdditionalProperties{ + Schema: convertSchemaToSchemaRef(schema.AdditionalProperties.Schema), + } + } + + return &openapi3.SchemaRef{Value: newSchema} +} + +// convertItemsToSchemaRef converts Swagger 2.0 items to OpenAPI 3.0 SchemaRef +func convertItemsToSchemaRef(items *spec.Items) *openapi3.SchemaRef { + if items == nil { + return nil + } + + schema := &openapi3.Schema{ + Format: items.Format, + } + + if items.Type != "" { + schema.Type = &openapi3.Types{items.Type} + } + + if items.Items != nil { + schema.Items = convertItemsToSchemaRef(items.Items) + } + + return &openapi3.SchemaRef{Value: schema} +} + +// convertSecurityScheme converts a Swagger 2.0 security definition to OpenAPI 3.0 +func convertSecurityScheme(secDef *spec.SecurityScheme) *openapi3.SecuritySchemeRef { + newScheme := &openapi3.SecurityScheme{ + Type: secDef.Type, + Description: secDef.Description, + Name: secDef.Name, + } + + // Convert In to openapi3 format + switch secDef.In { + case "header": + newScheme.In = "header" + case "query": + newScheme.In = "query" + case "cookie": + newScheme.In = "cookie" + } + + // Handle OAuth2 + if secDef.Type == "oauth2" { + newScheme.Flows = &openapi3.OAuthFlows{} + + switch secDef.Flow { + case "implicit": + newScheme.Flows.Implicit = &openapi3.OAuthFlow{ + AuthorizationURL: secDef.AuthorizationURL, + Scopes: secDef.Scopes, + } + case "password": + newScheme.Flows.Password = &openapi3.OAuthFlow{ + TokenURL: secDef.TokenURL, + Scopes: secDef.Scopes, + } + case "application": + newScheme.Flows.ClientCredentials = &openapi3.OAuthFlow{ + TokenURL: secDef.TokenURL, + Scopes: secDef.Scopes, + } + case "accessCode": + newScheme.Flows.AuthorizationCode = &openapi3.OAuthFlow{ + AuthorizationURL: secDef.AuthorizationURL, + TokenURL: secDef.TokenURL, + Scopes: secDef.Scopes, + } + } + } + + return &openapi3.SecuritySchemeRef{Value: newScheme} +} + +// intToString converts int to string for response codes +func intToString(code int) string { + if code < 0 || code > 999 { + return "200" // fallback to 200 + } + hundreds := code / 100 + tens := (code / 10) % 10 + ones := code % 10 + return string(rune('0'+hundreds)) + string(rune('0'+tens)) + string(rune('0'+ones)) +} diff --git a/test_all_formats.sh b/test_all_formats.sh new file mode 100755 index 0000000..2360354 --- /dev/null +++ b/test_all_formats.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# 測試所有格式生成 + +set -e + +echo "🧪 測試 go-doc 所有格式生成" +echo "================================" + +# 建立測試目錄 +TEST_DIR="test_output_verification" +mkdir -p "$TEST_DIR" + +echo "" +echo "📝 測試 1: Swagger 2.0 (JSON)" +./bin/go-doc -a example/example.api -d "$TEST_DIR" -f test1_swagger2 +if [ -f "$TEST_DIR/test1_swagger2.json" ]; then + VERSION=$(jq -r '.swagger' "$TEST_DIR/test1_swagger2.json") + echo "✅ 成功生成 Swagger $VERSION" +else + echo "❌ 失敗" + exit 1 +fi + +echo "" +echo "📝 測試 2: Swagger 2.0 (YAML)" +./bin/go-doc -a example/example.api -d "$TEST_DIR" -f test2_swagger2 -y +if [ -f "$TEST_DIR/test2_swagger2.yaml" ]; then + echo "✅ 成功生成 YAML 格式" +else + echo "❌ 失敗" + exit 1 +fi + +echo "" +echo "📝 測試 3: OpenAPI 3.0 (JSON)" +./bin/go-doc -a example/example.api -d "$TEST_DIR" -f test3_openapi3 -s openapi3.0 +if [ -f "$TEST_DIR/test3_openapi3.json" ]; then + VERSION=$(jq -r '.openapi' "$TEST_DIR/test3_openapi3.json") + echo "✅ 成功生成 OpenAPI $VERSION" +else + echo "❌ 失敗" + exit 1 +fi + +echo "" +echo "📝 測試 4: OpenAPI 3.0 (YAML)" +./bin/go-doc -a example/example.api -d "$TEST_DIR" -f test4_openapi3 -s openapi3.0 -y +if [ -f "$TEST_DIR/test4_openapi3.yaml" ]; then + echo "✅ 成功生成 OpenAPI YAML" +else + echo "❌ 失敗" + exit 1 +fi + +echo "" +echo "📝 測試 5: 中文範例 (Swagger 2.0)" +./bin/go-doc -a example/example_cn.api -d "$TEST_DIR" -f test5_cn_swagger2 +if [ -f "$TEST_DIR/test5_cn_swagger2.json" ]; then + TITLE=$(jq -r '.info.title' "$TEST_DIR/test5_cn_swagger2.json") + echo "✅ 成功生成中文範例: $TITLE" +else + echo "❌ 失敗" + exit 1 +fi + +echo "" +echo "📝 測試 6: 中文範例 (OpenAPI 3.0)" +./bin/go-doc -a example/example_cn.api -d "$TEST_DIR" -f test6_cn_openapi3 -s openapi3.0 +if [ -f "$TEST_DIR/test6_cn_openapi3.json" ]; then + TITLE=$(jq -r '.info.title' "$TEST_DIR/test6_cn_openapi3.json") + echo "✅ 成功生成中文 OpenAPI 3.0: $TITLE" +else + echo "❌ 失敗" + exit 1 +fi + +echo "" +echo "📝 測試 7: 錯誤處理(無效的 spec-version)" +if ./bin/go-doc -a example/example.api -d "$TEST_DIR" -s invalid 2>&1 | grep -q "spec-version must be"; then + echo "✅ 錯誤處理正常" +else + echo "❌ 錯誤處理失敗" + exit 1 +fi + +echo "" +echo "📊 生成檔案統計" +echo "================================" +ls -lh "$TEST_DIR" + +echo "" +echo "📈 檔案大小比較" +echo "================================" +echo "Swagger 2.0 vs OpenAPI 3.0:" +S2_SIZE=$(stat -f%z "$TEST_DIR/test1_swagger2.json" 2>/dev/null || stat -c%s "$TEST_DIR/test1_swagger2.json") +O3_SIZE=$(stat -f%z "$TEST_DIR/test3_openapi3.json" 2>/dev/null || stat -c%s "$TEST_DIR/test3_openapi3.json") +echo " Swagger 2.0: $S2_SIZE bytes" +echo " OpenAPI 3.0: $O3_SIZE bytes" + +echo "" +echo "🎉 所有測試通過!" +echo "" +echo "生成的檔案位於: $TEST_DIR/"