feat: init swagger project

This commit is contained in:
王性驊 2025-09-30 16:52:30 +08:00
parent 696d789686
commit b7c67fb9e6
13 changed files with 1143 additions and 28 deletions

1
.gitignore vendored
View File

@ -30,4 +30,5 @@ Thumbs.db
# Output files
example/test_output/
test_output_verification/

View File

@ -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:

View File

@ -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/"

279
OPENAPI3_GUIDE.md Normal file
View File

@ -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
```

103
QUICK_START.md Normal file
View File

@ -0,0 +1,103 @@
# 🚀 Quick Start Guide
## 安裝
### 從原始碼編譯
```bash
git clone <your-repo>
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`

View File

@ -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

View File

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

View File

@ -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-未登录<br>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-用不存在<br>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)
}

4
go.mod
View File

@ -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

16
go.sum
View File

@ -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=

View File

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

View File

@ -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 = &param
} else {
newOp.Parameters = append(newOp.Parameters, convertParameter(&param))
}
}
// 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))
}

103
test_all_formats.sh Executable file
View File

@ -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/"