feat: add doc generate
This commit is contained in:
parent
b7c67fb9e6
commit
8dd4d406f4
69
CHANGELOG.md
69
CHANGELOG.md
|
@ -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.
|
||||
|
255
MIGRATION.md
255
MIGRATION.md
|
@ -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)
|
||||
|
|
@ -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
|
||||
```
|
103
QUICK_START.md
103
QUICK_START.md
|
@ -1,103 +0,0 @@
|
|||
# 🚀 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`
|
824
README.md
824
README.md
|
@ -2,99 +2,128 @@
|
|||
|
||||
[](https://go.dev/)
|
||||
[](LICENSE)
|
||||
[](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 <your-repo>
|
||||
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 <your-repo>
|
||||
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!**
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
commit = "dev"
|
||||
date = "unknown"
|
||||
)
|
||||
|
|
|
@ -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-用不存在<br>1004-非法操作" // 接口级别业务错误码枚举描述,会覆盖全局的业务错误码,json 格式,key 为业务错误码,value 为该错误码的描述,仅当 wrapCodeMsg 为 true 且 useDefinitions 为 false 时生效
|
||||
)
|
||||
/*
|
||||
@respdoc-201 (QueryResp) // 創建成功
|
||||
|
|
|
@ -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
|
Binary file not shown.
Before Width: | Height: | Size: 3.3 MiB |
Binary file not shown.
Before Width: | Height: | Size: 3.6 MiB |
|
@ -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
|
Binary file not shown.
Before Width: | Height: | Size: 1.7 MiB |
Binary file not shown.
Before Width: | Height: | Size: 2.4 MiB |
|
@ -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
|
Binary file not shown.
Before Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
Before Width: | Height: | Size: 2.4 MiB |
|
@ -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
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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, "")
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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", `{}`)
|
||||
|
|
|
@ -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_*
|
Loading…
Reference in New Issue