# go-doc

[![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.23-blue)](https://go.dev/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-1.2.0-blue)](CHANGELOG.md)

**go-doc** 是一個獨立的命令行工具，專門用於將 [go-zero](https://github.com/zeromicro/go-zero) `.api` 文件轉換為 OpenAPI 規範文檔。

原本是 go-zero 專案的一部分，現在已獨立出來，成為一個易於使用的工具，支援生成 **Swagger 2.0** 和 **OpenAPI 3.0** 兩種格式。

---

## 目錄

- [特性](#特性)
- [安裝](#安裝)
- [快速開始](#快速開始)
- [API 文件格式](#api-文件格式)
- [@respdoc 多狀態碼回應](#respdoc-多狀態碼回應)
- [進階功能](#進階功能)
- [OpenAPI 3.0 vs Swagger 2.0](#openapi-30-vs-swagger-20)
- [使用範例](#使用範例)
- [測試](#測試)
- [專案結構](#專案結構)
- [從 go-zero 遷移](#從-go-zero-遷移)
- [版本記錄](#版本記錄)
- [常見問題](#常見問題)
- [貢獻](#貢獻)
- [授權](#授權)

---

## 特性

- **獨立二進制** - 無需依賴 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 <your-repo>
cd go-doc
make install
```

編譯完成後，可以直接 go-doc 做使用
---

## 快速開始

### 基本使用

```bash
# 生成 Swagger 2.0（預設）
go-doc -a example/example.api -d output

# 生成 OpenAPI 3.0（推薦）
go-doc -a example/example.api -d output -s openapi3.0

# 生成 YAML 格式
go-doc -a example/example.api -d output -y

# 生成 OpenAPI 3.0 YAML
go-doc -a example/example.api -d output -s openapi3.0 -y

# 自訂檔名
go-doc -a example/example.api -d output -f my-api
```

### 命令行選項

```
Flags:
  -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）
```

### 使用 Makefile

```bash
# 編譯
make build

# 生成範例
make example

# 清理
make clean

# 運行測試
make test

# 查看所有命令
make help
```

---

## API 文件格式

### 基本結構

```go
syntax = "v1"

info (
    title:       "My API"
    description: "API documentation"
    version:     "v1.0.0"
    host:        "api.example.com"
    basePath:    "/v1"
)

type (
    UserRequest {
        Id   int    `json:"id,range=[1:10000]"`
        Name string `json:"name"`
    }
    UserResponse {
        Id   int    `json:"id"`
        Name string `json:"name"`
    }
)

@server (
    tags: "user"
)
service MyAPI {
    @handler getUser
    get /user/:id (UserRequest) returns (UserResponse)
}
```

### 支援的 Info 屬性

- `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 格式的安全定義

### 標籤選項

#### JSON 標籤

```go
type Example {
    // 範圍驗證
    Age int `json:"age,range=[1:150]"`
    
    // 預設值
    Status string `json:"status,default=active"`
    
    // 範例值
    Email string `json:"email,example=user@example.com"`
    
    // 列舉值
    Role string `json:"role,options=admin|user|guest"`
    
    // 可選欄位
    Phone string `json:"phone,optional"`
}
```

#### Form 標籤

```go
type QueryRequest {
    Keyword string `form:"keyword"`
    Page    int    `form:"page,default=1"`
    Size    int    `form:"size,range=[1:100]"`
}
```

#### Path 標籤

```go
type PathRequest {
    UserId int `path:"userId,range=[1:999999]"`
}
```

#### Header 標籤

```go
type HeaderRequest {
    Token string `header:"Authorization"`
}
```

---

## @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 (
    securityDefinitionsFromJson: `{
        "apiKey": {
            "type": "apiKey",
            "name": "x-api-key",
            "in": "header",
            "description": "API Key Authentication"
        }
    }`
)

@server (
    authType: apiKey
)
service MyAPI {
    // 此處的路由將使用 apiKey 身份驗證
}
```

### Code-Msg 包裝

```go
info (
    wrapCodeMsg: true
    bizCodeEnumDescription: "1001-User not found<br>1002-Permission denied"
)
```

回應將被包裝為：
```json
{
    "code": 0,
    "msg": "ok",
    "data": { /* your actual response */ }
}
```

---

## 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.go
├── internal/
│   ├── swagger/             # 核心邏輯
│   │   ├── respdoc.go       # @respdoc 解析
│   │   ├── response.go      # 回應生成
│   │   ├── openapi3.go      # OpenAPI 3.0 轉換
│   │   ├── swagger.go       # Swagger 2.0 生成
│   │   ├── path.go          # 路徑處理
│   │   ├── parameter.go     # 參數處理
│   │   ├── definition.go    # 定義處理
│   │   └── ...
│   └── util/                # 工具函數
│       ├── util.go
│       ├── stringx.go
│       └── pathx.go
├── example/
│   ├── example.api          # 範例 API
│   ├── example_cn.api       # 中文範例
│   ├── example_respdoc.api  # @respdoc 範例
│   └── test_output/         # 生成的文檔
├── bin/                     # 編譯後的執行檔
├── go.mod
├── go.sum
├── Makefile
├── README.md
├── LICENSE
├── CHANGELOG.md
├── test_all_formats.sh      # 測試腳本
└── test_respdoc.sh          # @respdoc 測試
```

---

## 從 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
```
---

## 總結

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