add lint
This commit is contained in:
parent
79c12702ec
commit
fb5ac4b09f
|
|
@ -0,0 +1,145 @@
|
||||||
|
# golangci-lint v2 — Gateway 專案
|
||||||
|
# 參考:https://golangci-lint.run/docs/configuration/
|
||||||
|
# 預設集「standard」= errcheck + govet + ineffassign + staticcheck + unused(取代已棄用的 golint)
|
||||||
|
# 再啟用社群常見的品質 / 安全 / 風格 linter,並關閉過度吵雜的規則。
|
||||||
|
|
||||||
|
version: "2"
|
||||||
|
|
||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
tests: true
|
||||||
|
modules-download-mode: readonly
|
||||||
|
go: "1.26"
|
||||||
|
|
||||||
|
output:
|
||||||
|
formats:
|
||||||
|
text:
|
||||||
|
print-linter-name: true
|
||||||
|
colors: true
|
||||||
|
|
||||||
|
linters:
|
||||||
|
default: standard
|
||||||
|
|
||||||
|
enable:
|
||||||
|
# 錯誤與 context
|
||||||
|
- bodyclose
|
||||||
|
- contextcheck
|
||||||
|
- copyloopvar
|
||||||
|
- errname
|
||||||
|
- errorlint
|
||||||
|
- noctx
|
||||||
|
# 安全
|
||||||
|
- gosec
|
||||||
|
# 風格(revive 為 golint 的現代替代)
|
||||||
|
- revive
|
||||||
|
- misspell
|
||||||
|
- unconvert
|
||||||
|
- whitespace
|
||||||
|
- gocritic
|
||||||
|
- goconst
|
||||||
|
- predeclared
|
||||||
|
# 測試
|
||||||
|
- testifylint
|
||||||
|
- thelper
|
||||||
|
# HTTP / API 常見
|
||||||
|
- rowserrcheck
|
||||||
|
- sloglint
|
||||||
|
|
||||||
|
disable:
|
||||||
|
- exhaustruct
|
||||||
|
- varnamelen
|
||||||
|
- wrapcheck
|
||||||
|
- nlreturn
|
||||||
|
- wsl
|
||||||
|
- wsl_v5
|
||||||
|
- dupl
|
||||||
|
- depguard
|
||||||
|
- tagliatelle
|
||||||
|
- ireturn
|
||||||
|
- funlen
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
|
||||||
|
settings:
|
||||||
|
gosec:
|
||||||
|
excludes:
|
||||||
|
- G104
|
||||||
|
gocritic:
|
||||||
|
enabled-tags:
|
||||||
|
- diagnostic
|
||||||
|
- style
|
||||||
|
- performance
|
||||||
|
disabled-checks:
|
||||||
|
- hugeParam
|
||||||
|
- rangeValCopy
|
||||||
|
- whyNoLint
|
||||||
|
revive:
|
||||||
|
severity: warning
|
||||||
|
rules:
|
||||||
|
- name: blank-imports
|
||||||
|
- name: context-as-argument
|
||||||
|
- name: context-keys-type
|
||||||
|
- name: dot-imports
|
||||||
|
- name: error-return
|
||||||
|
- name: error-naming
|
||||||
|
- name: exported
|
||||||
|
arguments:
|
||||||
|
- disableStutteringCheck
|
||||||
|
- name: increment-decrement
|
||||||
|
- name: indent-error-flow
|
||||||
|
- name: range
|
||||||
|
- name: receiver-naming
|
||||||
|
- name: time-naming
|
||||||
|
- name: unexported-return
|
||||||
|
- name: var-declaration
|
||||||
|
- name: var-naming
|
||||||
|
disabled: true
|
||||||
|
- name: package-comments
|
||||||
|
disabled: true
|
||||||
|
- name: comment-spacings
|
||||||
|
disabled: true
|
||||||
|
errcheck:
|
||||||
|
check-type-assertions: true
|
||||||
|
check-blank: true
|
||||||
|
staticcheck:
|
||||||
|
checks:
|
||||||
|
- all
|
||||||
|
- -ST1000
|
||||||
|
- -ST1003
|
||||||
|
- -ST1016
|
||||||
|
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
warn-unused: true
|
||||||
|
presets:
|
||||||
|
- comments
|
||||||
|
- common-false-positives
|
||||||
|
- std-error-handling
|
||||||
|
- legacy
|
||||||
|
rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
- errcheck
|
||||||
|
- funlen
|
||||||
|
- gocyclo
|
||||||
|
- cyclop
|
||||||
|
- path: internal/types/types\.go
|
||||||
|
linters:
|
||||||
|
- revive
|
||||||
|
- gocritic
|
||||||
|
paths:
|
||||||
|
- generate/doc-generate
|
||||||
|
- docs/openapi
|
||||||
|
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- goimports
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- generate/doc-generate
|
||||||
|
|
||||||
|
issues:
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 0
|
||||||
70
Makefile
70
Makefile
|
|
@ -1,27 +1,71 @@
|
||||||
# go-zero 生成風格
|
# go-zero 生成風格
|
||||||
GO_ZERO_STYLE=go_zero
|
GO_ZERO_STYLE := go_zero
|
||||||
GO ?= go
|
GO ?= go
|
||||||
GOFMT ?= gofmt "-s"
|
GOFMT ?= gofmt
|
||||||
GOFILES := $(shell find . -name "*.go")
|
GOFILES := $(shell find . -name '*.go' -not -path './generate/doc-generate/*')
|
||||||
LDFLAGS := -s -w
|
|
||||||
VERSION="v0.0.1"
|
|
||||||
DOCKER_REP="reg.wang/app-cloudep-member-service"
|
|
||||||
GIT_COMMIT ?= $(shell git rev-parse --short HEAD)
|
|
||||||
|
|
||||||
|
|
||||||
GO_DOC_DIR := generate/doc-generate
|
GO_DOC_DIR := generate/doc-generate
|
||||||
GO_DOC_BIN := $(GO_DOC_DIR)/bin/go-doc
|
GO_DOC_BIN := $(GO_DOC_DIR)/bin/go-doc
|
||||||
API_ENTRY := ./generate/api/gateway.api
|
API_ENTRY := ./generate/api/gateway.api
|
||||||
DOC_OUT := ./docs/openapi
|
DOC_OUT := ./docs/openapi
|
||||||
|
|
||||||
.PHONY: gen-api build-go-doc gen-doc
|
GOCTL ?= goctl
|
||||||
gen-api: # 使用專案 handler 模板(response.Write)
|
GOCTL_PKG := github.com/zeromicro/go-zero/tools/goctl@latest
|
||||||
goctl api go -api $(API_ENTRY) -dir . -style go_zero -home generate/goctl
|
GOLANGCI_PKG := github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
.PHONY: help tools gen-api build-go-doc gen-doc test fmt lint lint-fix fix check run
|
||||||
|
|
||||||
|
help: ## 顯示可用指令
|
||||||
|
@echo "Gateway Makefile"
|
||||||
|
@echo ""
|
||||||
|
@echo "首次開發:"
|
||||||
|
@echo " make tools 安裝 goctl、goimports、golangci-lint(寫入 \$$GOPATH/bin)"
|
||||||
|
@echo " go mod download"
|
||||||
|
@echo ""
|
||||||
|
@echo "常用:"
|
||||||
|
@grep -E '^[a-zA-Z0-9_-]+:.*## ' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## "}; {printf " make %-14s %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
tools: ## 安裝 goctl、goimports、golangci-lint(需 Go,且 GOPATH/bin 在 PATH)
|
||||||
|
@command -v $(GOCTL) >/dev/null 2>&1 || (echo ">> installing goctl" && $(GO) install $(GOCTL_PKG))
|
||||||
|
@command -v goimports >/dev/null 2>&1 || (echo ">> installing goimports" && $(GO) install golang.org/x/tools/cmd/goimports@latest)
|
||||||
|
@command -v golangci-lint >/dev/null 2>&1 || (echo ">> installing golangci-lint" && $(GO) install $(GOLANGCI_PKG))
|
||||||
|
@echo "tools OK"
|
||||||
|
@echo " goctl: $$(goctl --version 2>/dev/null || echo missing)"
|
||||||
|
@echo " golangci-lint: $$(golangci-lint version 2>/dev/null | head -1 || echo missing)"
|
||||||
|
|
||||||
|
gen-api: tools ## 由 .api 生成 handler / logic / types(自訂 handler 模板)
|
||||||
|
$(GOCTL) api go -api $(API_ENTRY) -dir . -style $(GO_ZERO_STYLE) -home generate/goctl
|
||||||
|
|
||||||
build-go-doc: ## 編譯 go-doc(OpenAPI 文件生成器)
|
build-go-doc: ## 編譯 go-doc(OpenAPI 文件生成器)
|
||||||
cd $(GO_DOC_DIR) && GOTOOLCHAIN=go1.26.1 go build -o bin/go-doc ./cmd/go-doc
|
@echo ">> building $(GO_DOC_BIN)"
|
||||||
|
@mkdir -p $(GO_DOC_DIR)/bin
|
||||||
|
@cd $(GO_DOC_DIR) && $(GO) build -o bin/go-doc ./cmd/go-doc
|
||||||
|
|
||||||
gen-doc: build-go-doc ## 從 .api 生成 OpenAPI 3.0 YAML
|
gen-doc: build-go-doc ## 從 .api 生成 OpenAPI 3.0 YAML
|
||||||
@mkdir -p $(DOC_OUT)
|
@mkdir -p $(DOC_OUT)
|
||||||
$(GO_DOC_BIN) -a $(API_ENTRY) -d $(DOC_OUT) -f gateway -s openapi3.0 -y
|
$(GO_DOC_BIN) -a $(API_ENTRY) -d $(DOC_OUT) -f gateway -s openapi3.0 -y
|
||||||
@echo "Generated: $(DOC_OUT)/gateway.yaml"
|
@echo "Generated: $(DOC_OUT)/gateway.yaml"
|
||||||
|
|
||||||
|
test: ## 執行測試
|
||||||
|
$(GO) test ./...
|
||||||
|
|
||||||
|
fmt: ## gofmt + goimports(不含 lint)
|
||||||
|
$(GOFMT) -s -w $(GOFILES)
|
||||||
|
@command -v goimports >/dev/null 2>&1 && goimports -w . || (echo "goimports not found; run: make tools" && exit 1)
|
||||||
|
|
||||||
|
lint: ## golangci-lint 靜態檢查(先 make tools)
|
||||||
|
@command -v golangci-lint >/dev/null 2>&1 || (echo "golangci-lint not found; run: make tools" && exit 1)
|
||||||
|
golangci-lint run ./...
|
||||||
|
|
||||||
|
lint-fix: ## 自動修正可修的 lint / formatter 問題(見 .golangci.yml)
|
||||||
|
@command -v golangci-lint >/dev/null 2>&1 || (echo "golangci-lint not found; run: make tools" && exit 1)
|
||||||
|
golangci-lint run --fix ./...
|
||||||
|
|
||||||
|
fix: fmt lint-fix lint ## 格式化 + 自動修 lint + 再檢查(提交前建議)
|
||||||
|
|
||||||
|
check: fix test ## 提交 / PR 前完整檢查(fmt、lint、test)
|
||||||
|
|
||||||
|
run: ## 啟動 Gateway(etc/gateway.yaml)
|
||||||
|
$(GO) run gateway.go -f etc/gateway.yaml
|
||||||
|
|
|
||||||
83
README.md
83
README.md
|
|
@ -1,4 +1,4 @@
|
||||||
y94# Portal API Gateway (PGW)
|
# Portal API Gateway (PGW)
|
||||||
|
|
||||||
基於 [go-zero](https://github.com/zeromicro/go-zero) 的 API Gateway,提供統一 HTTP JSON 回應、8 碼業務錯誤碼,以及由 `.api` 定義驅動的程式碼與 OpenAPI 3.0 文件生成。
|
基於 [go-zero](https://github.com/zeromicro/go-zero) 的 API Gateway,提供統一 HTTP JSON 回應、8 碼業務錯誤碼,以及由 `.api` 定義驅動的程式碼與 OpenAPI 3.0 文件生成。
|
||||||
|
|
||||||
|
|
@ -12,32 +12,40 @@ y94# Portal API Gateway (PGW)
|
||||||
|
|
||||||
## 環境需求
|
## 環境需求
|
||||||
|
|
||||||
| 工具 | 說明 |
|
| 工具 | 必要 | 說明 |
|
||||||
|------|------|
|
|------|:----:|------|
|
||||||
| Go | ≥ 1.26.1(見 `go.mod`) |
|
| Go | ✓ | ≥ 1.26.1(見 `go.mod`) |
|
||||||
| [goctl](https://go-zero.dev/docs/tasks/installation/goctl) | go-zero 程式碼生成 |
|
| Make | ✓ | 執行 `gen-api` / `gen-doc` 等 |
|
||||||
| Make | 執行 `gen-api` / `gen-doc`(可選) |
|
| [goctl](https://go-zero.dev/docs/tasks/installation/goctl) | ✓ | `make gen-api` 用;可 `make tools` 安裝 |
|
||||||
|
| goimports | 建議 | `make fmt` 用;`make tools` 安裝 |
|
||||||
|
| golangci-lint | 建議 | `make lint` 用;`make tools` 安裝(v2,見 `.golangci.yml`) |
|
||||||
|
| jq | 選用 | README 範例 `curl \| jq` |
|
||||||
|
|
||||||
安裝 goctl(若尚未安裝):
|
請確認 **`$(go env GOPATH)/bin` 在 PATH**,否則 `go install` 的 `goctl` 找不到。
|
||||||
|
|
||||||
|
## 快速開始(首次)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go install github.com/zeromicro/go-zero/tools/goctl@latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## 快速開始
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 克隆後進入專案目錄
|
|
||||||
cd gateway
|
cd gateway
|
||||||
|
|
||||||
# 下載依賴
|
# 1. 工具(goctl、goimports)
|
||||||
|
make tools
|
||||||
|
|
||||||
|
# 2. 依賴
|
||||||
go mod download
|
go mod download
|
||||||
|
|
||||||
# 依 .api 生成 handler / logic / types(若已生成可跳過)
|
# 3. 依 .api 生成程式碼(可選,repo 內通常已有)
|
||||||
make gen-api
|
make gen-api
|
||||||
|
|
||||||
# 啟動服務
|
# 4. 啟動
|
||||||
go run gateway.go -f etc/gateway.yaml
|
make run
|
||||||
|
# 或:go run gateway.go -f etc/gateway.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
產生 OpenAPI(會先編譯 `generate/doc-generate` 內的 go-doc):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make gen-doc # 輸出 docs/openapi/gateway.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
健康檢查:
|
健康檢查:
|
||||||
|
|
@ -58,12 +66,20 @@ curl -s http://127.0.0.1:8888/api/v1/health | jq
|
||||||
|
|
||||||
## 常用指令
|
## 常用指令
|
||||||
|
|
||||||
|
執行 `make` 或 `make help` 可看完整列表。
|
||||||
|
|
||||||
| 指令 | 說明 |
|
| 指令 | 說明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `make gen-api` | 由 `generate/api/gateway.api` 生成 Go 程式碼(使用 `generate/goctl` 模板) |
|
| `make tools` | 首次安裝 goctl、goimports |
|
||||||
| `make gen-doc` | 生成 `docs/openapi/gateway.yaml`(OpenAPI 3.0) |
|
| `make gen-api` | 由 `gateway.api` 生成 Go(自訂 `generate/goctl` 模板) |
|
||||||
| `go run gateway.go -f etc/gateway.yaml` | 啟動 Gateway |
|
| `make gen-doc` | 編譯 go-doc 並生成 `docs/openapi/gateway.yaml` |
|
||||||
| `go test ./...` | 執行測試 |
|
| `make test` | 執行測試 |
|
||||||
|
| `make fmt` | gofmt + goimports |
|
||||||
|
| `make lint` | golangci-lint 靜態檢查(必須 0 issues) |
|
||||||
|
| `make lint-fix` | 自動修正可修的 lint / import 問題 |
|
||||||
|
| `make fix` | `fmt` + `lint-fix` + `lint`(提交前建議) |
|
||||||
|
| `make check` | `fix` + `test`(PR / AI 完成前必跑) |
|
||||||
|
| `make run` | 啟動 Gateway |
|
||||||
|
|
||||||
## 專案結構
|
## 專案結構
|
||||||
|
|
||||||
|
|
@ -257,7 +273,20 @@ if err != nil {
|
||||||
|
|
||||||
**禁止:** repository 回傳裸 `fmt.Errorf` 給上層;logic 再包一層無語意錯誤;logic 直接操作 Mongo / repository 實作。
|
**禁止:** repository 回傳裸 `fmt.Errorf` 給上層;logic 再包一層無語意錯誤;logic 直接操作 Mongo / repository 實作。
|
||||||
|
|
||||||
### 5. 請求驗證(`library/validate`)
|
### 5. Lint 與自動修正
|
||||||
|
|
||||||
|
設定檔:`.golangci.yml`(golangci-lint v2,`standard` + revive / gosec / errorlint 等)。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make lint-fix # 自動修(goimports、部分 linter auto-fix)
|
||||||
|
make lint # 僅檢查
|
||||||
|
make fix # fmt + lint-fix + lint
|
||||||
|
make check # fix + test(建議每次改 Go 後執行)
|
||||||
|
```
|
||||||
|
|
||||||
|
無法自動修的須手改;AI / 協作者應以 `make check` 通過為完成條件(見 `.cursor/rules/golangci-lint.mdc`)。
|
||||||
|
|
||||||
|
### 6. 請求驗證(`library/validate`)
|
||||||
|
|
||||||
- `svc.ServiceContext.Validator`:啟動時以 `validate.NewWithDefaultEN()` 建立,可傳入 `validate.Option` 註冊自訂 tag。
|
- `svc.ServiceContext.Validator`:啟動時以 `validate.NewWithDefaultEN()` 建立,可傳入 `validate.Option` 註冊自訂 tag。
|
||||||
- **Handler 模板**(`generate/goctl/api/handler.tpl`)在 `httpx.Parse` 之後自動 `ValidateAll(&req)`,失敗經 `WrapRequestError` → **400** `InputInvalidFormat`;Logic 通常不必再驗證。
|
- **Handler 模板**(`generate/goctl/api/handler.tpl`)在 `httpx.Parse` 之後自動 `ValidateAll(&req)`,失敗經 `WrapRequestError` → **400** `InputInvalidFormat`;Logic 通常不必再驗證。
|
||||||
|
|
@ -270,7 +299,7 @@ if err := l.svcCtx.Validator.ValidateAll(req); err != nil {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. 新增業務模組檢查清單
|
### 7. 新增業務模組檢查清單
|
||||||
|
|
||||||
1. 建立 `internal/model/{module}/`,依上方目錄放置 `entity`、`enum`、`repository`、`usecase`。
|
1. 建立 `internal/model/{module}/`,依上方目錄放置 `entity`、`enum`、`repository`、`usecase`。
|
||||||
2. 在 `internal/svc/service_context.go` 組裝 repository → usecase,掛上介面欄位。
|
2. 在 `internal/svc/service_context.go` 組裝 repository → usecase,掛上介面欄位。
|
||||||
|
|
@ -280,7 +309,7 @@ if err := l.svcCtx.Validator.ValidateAll(req); err != nil {
|
||||||
|
|
||||||
完整步驟見 [docs/model.md](docs/model.md) 第 11 節。
|
完整步驟見 [docs/model.md](docs/model.md) 第 11 節。
|
||||||
|
|
||||||
### 7. 錯誤碼 API 速查
|
### 8. 錯誤碼 API 速查
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
|
|
@ -296,7 +325,7 @@ return nil, errb.InputMissingRequired("email").WithCause(err)
|
||||||
|
|
||||||
Category 與 HTTP 對照見 [internal/library/errors/README.md](internal/library/errors/README.md)。
|
Category 與 HTTP 對照見 [internal/library/errors/README.md](internal/library/errors/README.md)。
|
||||||
|
|
||||||
### 8. API 文件(`@` 註解)
|
### 9. API 文件(`@` 註解)
|
||||||
|
|
||||||
在 `.api` 中:
|
在 `.api` 中:
|
||||||
|
|
||||||
|
|
@ -306,7 +335,7 @@ Category 與 HTTP 對照見 [internal/library/errors/README.md](internal/library
|
||||||
|
|
||||||
範例見 `generate/api/normal.api`。
|
範例見 `generate/api/normal.api`。
|
||||||
|
|
||||||
### 9. OpenAPI 產物與 Git
|
### 10. OpenAPI 產物與 Git
|
||||||
|
|
||||||
預設 `.gitignore` 會忽略 `docs/openapi/*.yaml`。若需提交給前端,請在 `.gitignore` 註解相關規則後執行 `make gen-doc` 並加入版本控制。
|
預設 `.gitignore` 會忽略 `docs/openapi/*.yaml`。若需提交給前端,請在 `.gitignore` 註解相關規則後執行 `make gen-doc` 並加入版本控制。
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"go-doc/internal/util"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/parser"
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/parser"
|
||||||
"go-doc/internal/util"
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
|
||||||
"go-doc/internal/util"
|
"go-doc/internal/util"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
func rangeValueFromOptions(options []string) (minimum *float64, maximum *float64, exclusiveMinimum bool, exclusiveMaximum bool) {
|
func rangeValueFromOptions(options []string) (minimum *float64, maximum *float64, exclusiveMinimum bool, exclusiveMaximum bool) {
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"go-doc/internal/util"
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
"github.com/go-openapi/spec"
|
||||||
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||||
"go-doc/internal/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func spec2Paths(ctx Context, srv apiSpec.Service) *spec.Paths {
|
func spec2Paths(ctx Context, srv apiSpec.Service) *spec.Paths {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"gateway/internal/library/errors/code"
|
"gateway/internal/library/errors/code"
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
errs "gateway/internal/library/errors"
|
errs "gateway/internal/library/errors"
|
||||||
"gateway/internal/library/errors/code"
|
"gateway/internal/library/errors/code"
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gateway/internal/library/errors/code"
|
"gateway/internal/library/errors/code"
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gateway/internal/library/errors/code"
|
"gateway/internal/library/errors/code"
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ func (ve ValidationErrors) Error() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString("validation failed:")
|
sb.WriteString("validation failed:")
|
||||||
for i, e := range ve {
|
for i, e := range ve {
|
||||||
sb.WriteString(fmt.Sprintf(" field='%s', message='%s'", e.Field, e.Message))
|
fmt.Fprintf(&sb, " field='%s', message='%s'", e.Field, e.Message)
|
||||||
if i < len(ve)-1 {
|
if i < len(ve)-1 {
|
||||||
sb.WriteString(";")
|
sb.WriteString(";")
|
||||||
}
|
}
|
||||||
|
|
@ -57,9 +57,9 @@ func (ve ValidationErrors) ToBusinessError(scope code.Scope) *errs.Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// translateValidationErrors converts validator.ValidationErrors to our custom ValidationErrors.
|
// translateValidationErrors converts validator.ValidationErrors to our custom ValidationErrors.
|
||||||
func translateValidationErrors(errs validator.ValidationErrors, trans ut.Translator) ValidationErrors {
|
func translateValidationErrors(valErrs validator.ValidationErrors, trans ut.Translator) ValidationErrors {
|
||||||
customErrors := make(ValidationErrors, 0, len(errs))
|
customErrors := make(ValidationErrors, 0, len(valErrs))
|
||||||
for _, err := range errs {
|
for _, err := range valErrs {
|
||||||
customErrors = append(customErrors, ValidationError{
|
customErrors = append(customErrors, ValidationError{
|
||||||
Field: err.Field(), // Already transformed by RegisterTagNameFunc
|
Field: err.Field(), // Already transformed by RegisterTagNameFunc
|
||||||
Message: err.Translate(trans),
|
Message: err.Translate(trans),
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
entranslations "github.com/go-playground/validator/v10/translations/en"
|
entranslations "github.com/go-playground/validator/v10/translations/en"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate ...
|
// Validate validates structs using go-playground/validator tags and custom options.
|
||||||
type Validate interface {
|
type Validate interface {
|
||||||
ValidateAll(obj any) error // 驗證整個結構體
|
ValidateAll(obj any) error // 驗證整個結構體
|
||||||
BindToValidator(opts ...Option) error // 綁定客製化驗證規則
|
BindToValidator(opts ...Option) error // 綁定客製化驗證規則
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
package validate
|
package validate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
ut "github.com/go-playground/universal-translator"
|
ut "github.com/go-playground/universal-translator"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test struct for validation scenarios
|
// Test struct for validation scenarios
|
||||||
|
|
@ -58,7 +58,7 @@ func TestValidateAll_DefaultTranslationFailure(t *testing.T) {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
var validationErrs ValidationErrors
|
var validationErrs ValidationErrors
|
||||||
require.True(t, errors.As(err, &validationErrs), "Error should be of type ValidationErrors")
|
require.ErrorAs(t, err, &validationErrs)
|
||||||
require.Len(t, validationErrs, 2)
|
require.Len(t, validationErrs, 2)
|
||||||
|
|
||||||
// Check errors (order might vary)
|
// Check errors (order might vary)
|
||||||
|
|
@ -103,7 +103,7 @@ func TestValidateAll_CustomValidationWithTranslation(t *testing.T) {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
var validationErrs ValidationErrors
|
var validationErrs ValidationErrors
|
||||||
require.True(t, errors.As(err, &validationErrs))
|
require.ErrorAs(t, err, &validationErrs)
|
||||||
require.Len(t, validationErrs, 1)
|
require.Len(t, validationErrs, 1)
|
||||||
|
|
||||||
assert.Equal(t, "user_status", validationErrs[0].Field)
|
assert.Equal(t, "user_status", validationErrs[0].Field)
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ func TestWrapRequestErrorPreservesBusinessError(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
orig := errs.For(code.Facade).ResNotFound("x")
|
orig := errs.For(code.Facade).ResNotFound("x")
|
||||||
if response.WrapRequestError(orig) != orig {
|
if wrapped := response.WrapRequestError(orig); !errors.Is(wrapped, orig) {
|
||||||
t.Fatal("business error should not be wrapped")
|
t.Fatal("business error should not be wrapped")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue