179 lines
4.5 KiB
Markdown
179 lines
4.5 KiB
Markdown
|
# Bug Fix: Required Fields 判定錯誤
|
||
|
|
||
|
## 🐛 問題描述
|
||
|
|
||
|
生成的 OpenAPI/Swagger 文檔中,所有結構體字段都被錯誤地標記為 `required`,即使這些字段:
|
||
|
- 是**指針類型** (`*string`, `*int`, `*int64`)
|
||
|
- 在 JSON 標籤中包含 **`omitempty`**
|
||
|
- 在 validate 標籤中包含 **`omitempty`**
|
||
|
|
||
|
### 問題示例
|
||
|
|
||
|
```go
|
||
|
type CreateUserInfoReq {
|
||
|
UID string `json:"uid" validate:"required"` // 必填
|
||
|
Avatar *string `json:"avatar,omitempty"` // 應該可選
|
||
|
NickName *string `json:"nick_name,omitempty"` // 應該可選
|
||
|
Language string `json:"language" validate:"required"` // 必填
|
||
|
}
|
||
|
```
|
||
|
|
||
|
**修復前生成的 required 列表:**
|
||
|
```json
|
||
|
"required": ["uid", "avatar", "nick_name", "language"]
|
||
|
```
|
||
|
|
||
|
**修復後正確的 required 列表:**
|
||
|
```json
|
||
|
"required": ["uid", "language"]
|
||
|
```
|
||
|
|
||
|
## 🔍 根本原因
|
||
|
|
||
|
`internal/swagger/swagger.go` 中的 `isRequired()` 函數只檢查 JSON 標籤中的 `optional` 標記,但沒有檢查:
|
||
|
1. 字段類型是否為指針(指針類型在 Go 中表示可選)
|
||
|
2. JSON 標籤是否包含 `omitempty`
|
||
|
|
||
|
## ✅ 修復方案
|
||
|
|
||
|
### 修改的文件
|
||
|
- `internal/swagger/swagger.go`
|
||
|
|
||
|
### 修改內容
|
||
|
|
||
|
#### 1. 修改 `rangeMemberAndDo` 函數簽名
|
||
|
```go
|
||
|
// 修改前
|
||
|
required := isRequired(ctx, tags)
|
||
|
|
||
|
// 修改後
|
||
|
required := isRequired(ctx, tags, field)
|
||
|
```
|
||
|
|
||
|
#### 2. 修改 `isRequired` 函數
|
||
|
```go
|
||
|
// 修改前
|
||
|
func isRequired(ctx Context, tags *apiSpec.Tags) bool {
|
||
|
tag, err := tags.Get(tagJson)
|
||
|
if err == nil {
|
||
|
return !isOptional(ctx, tag.Options)
|
||
|
}
|
||
|
// ...
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// 修改後
|
||
|
func isRequired(ctx Context, tags *apiSpec.Tags, member apiSpec.Member) bool {
|
||
|
// Check if the field type is a pointer - pointer types are optional
|
||
|
if _, isPointer := member.Type.(apiSpec.PointerType); isPointer {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
tag, err := tags.Get(tagJson)
|
||
|
if err == nil {
|
||
|
return !isOptional(ctx, tag.Options)
|
||
|
}
|
||
|
// ...
|
||
|
return false
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### 3. 修改 `isOptional` 函數
|
||
|
```go
|
||
|
// 修改前
|
||
|
func isOptional(_ Context, options []string) bool {
|
||
|
for _, option := range options {
|
||
|
if option == optionalFlag {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// 修改後
|
||
|
func isOptional(_ Context, options []string) bool {
|
||
|
for _, option := range options {
|
||
|
if option == optionalFlag || option == "omitempty" {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## 📊 驗證結果
|
||
|
|
||
|
使用 `example/api/gateway.api` 進行測試:
|
||
|
|
||
|
| 結構體 | 修復前 | 修復後 | 狀態 |
|
||
|
|--------|--------|--------|------|
|
||
|
| CreateUserInfoReq | 13 個必填 | 5 個必填 | ✅ |
|
||
|
| UpdateUserInfoReq | 11 個必填 | 1 個必填 | ✅ |
|
||
|
| ListUserInfoReq | 6 個必填 | 2 個必填 | ✅ |
|
||
|
| GetUserInfoReq | 2 個必填 | 0 個必填 | ✅ |
|
||
|
| BindingUserReq | 3 個必填 | 3 個必填 | ✅ |
|
||
|
|
||
|
### CreateUserInfoReq 詳細對比
|
||
|
|
||
|
**必填字段(非指針類型):**
|
||
|
- `uid` (string)
|
||
|
- `alarm_type` (int)
|
||
|
- `status` (int)
|
||
|
- `language` (string)
|
||
|
- `currency` (string)
|
||
|
|
||
|
**可選字段(指針類型):**
|
||
|
- `avatar` (*string)
|
||
|
- `nick_name` (*string)
|
||
|
- `full_name` (*string)
|
||
|
- `gender` (*int64)
|
||
|
- `birthdate` (*int64)
|
||
|
- `phone_number` (*string)
|
||
|
- `email` (*string)
|
||
|
- `address` (*string)
|
||
|
|
||
|
## 🚀 影響範圍
|
||
|
|
||
|
此修復影響所有使用 go-doc 生成的 OpenAPI/Swagger 文檔:
|
||
|
- ✅ Swagger 2.0 格式
|
||
|
- ✅ OpenAPI 3.0 格式
|
||
|
- ✅ JSON 輸出
|
||
|
- ✅ YAML 輸出
|
||
|
|
||
|
## 📝 開發者注意事項
|
||
|
|
||
|
在 Go 語言和 API 設計中,以下三種方式都表示字段為可選:
|
||
|
1. **指針類型** - `*string`, `*int`, `*int64` 等
|
||
|
2. **JSON 標籤的 `omitempty`** - `json:"field,omitempty"`
|
||
|
3. **Validate 標籤的 `omitempty`** - `validate:"omitempty,..."`
|
||
|
|
||
|
修復後的 go-doc 工具會正確識別這些模式。
|
||
|
|
||
|
## 🔗 相關文件
|
||
|
|
||
|
- 修改文件:`internal/swagger/swagger.go`
|
||
|
- 測試文件:`example/api/gateway.api`
|
||
|
- 生成文件:`example/test_output/gateway.json`
|
||
|
- 驗證腳本:可運行 `make gen-gateway` 重新生成並驗證
|
||
|
|
||
|
## ✅ 測試通過
|
||
|
|
||
|
```bash
|
||
|
# 重新構建
|
||
|
make build
|
||
|
|
||
|
# 生成 gateway API 文檔
|
||
|
./bin/go-doc --api example/api/gateway.api \
|
||
|
--dir example/test_output \
|
||
|
--filename gateway \
|
||
|
--spec-version openapi3.0
|
||
|
|
||
|
# 所有結構體的 required 字段現在都正確了!
|
||
|
```
|
||
|
|
||
|
---
|
||
|
**修復日期:** 2025-10-01
|
||
|
**修復版本:** 1.2.1
|
||
|
**問題類型:** Bug Fix
|
||
|
**嚴重程度:** High (影響 API 文檔準確性)
|