Compare commits
6 Commits
Author | SHA1 | Date |
---|---|---|
Tony Chen | 2f9d7f892c | 3 years ago |
冯国庆 | a9b8af4c55 | 3 years ago |
onlyzilla | 4d2522f0b6 | 3 years ago |
Vincent | 9116815183 | 3 years ago |
Vincent | 659a56dc9d | 4 years ago |
Charles Ge | cd831f0565 | 4 years ago |
@ -1,2 +0,0 @@ |
|||||||
open_collective: go-kratos |
|
||||||
github: [go-kratos] |
|
@ -1,26 +0,0 @@ |
|||||||
--- |
|
||||||
name: "\U0001F41B Bug Report" |
|
||||||
about: Report something that's broken. Please ensure your kratos version is still supported. |
|
||||||
title: '' |
|
||||||
labels: bug |
|
||||||
assignees: '' |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
<!-- |
|
||||||
Please answer these questions before submitting your issue. Thanks! |
|
||||||
For questions please use one of our forums: https://go-kratos.dev/docs/getting-started/faq |
|
||||||
--> |
|
||||||
#### What happened: |
|
||||||
|
|
||||||
#### What you expected to happen: |
|
||||||
|
|
||||||
#### How to reproduce it (as minimally and precisely as possible): |
|
||||||
|
|
||||||
#### Anything else we need to know?: |
|
||||||
|
|
||||||
#### Environment: |
|
||||||
- Kratos version (use `kratos -v`): |
|
||||||
- Go version (use `go version`): |
|
||||||
- OS (e.g: `cat /etc/os-release`): |
|
||||||
- Others: |
|
@ -1,5 +0,0 @@ |
|||||||
blank_issues_enabled: false |
|
||||||
contact_links: |
|
||||||
- name: Documentation issue |
|
||||||
url: https://go-kratos.dev/en/docs/ |
|
||||||
about: For documentation issues, open a pull request at the go-kratos/go-kratos.dev repository |
|
@ -1,54 +0,0 @@ |
|||||||
--- |
|
||||||
name: "\U0001F4A1 Feature Request" |
|
||||||
about: For ideas or feature requests, start a new discussion. |
|
||||||
title: "[Feature]" |
|
||||||
labels: feature |
|
||||||
assignees: '' |
|
||||||
--- |
|
||||||
|
|
||||||
Please see the FAQ in our main README.md before submitting your issue. |
|
||||||
|
|
||||||
<!-- |
|
||||||
In order to accurately distinguish that the needs put forward by users are the needs of most users and reasonable needs, solicit community opinions through the process, and the features adopted by the community will be realized as new functions. |
|
||||||
|
|
||||||
In order to make the proposal process as simple as possible, the process includes three stages: feature request - > proposal - > pull-request, where feature, proposal is issue and pull-request is the specific function implementation. |
|
||||||
|
|
||||||
### Feature-request |
|
||||||
|
|
||||||
In order to help the community correctly understand the requirements of the feature, the feature request issue needs to describe the functional requirements and relevant references or documents in detail. And the feature request issue can contain the basic description of the function, which can be used as a reference for the function implementation in the proposal. |
|
||||||
|
|
||||||
### Proposal |
|
||||||
|
|
||||||
Proposal contains the basic implementation methods of functions, such as interface definition, general usage of functions, etc. |
|
||||||
|
|
||||||
### Pull-request |
|
||||||
|
|
||||||
After the function is realized, a merge request will be initiated to associate the proposal issue with the function issue. After the merger is completed, all questions will be closed and the process will end. |
|
||||||
|
|
||||||
### Decision process |
|
||||||
|
|
||||||
When more than five maintainer members agree to implement the feature, a proposal issue will be created for detailed design. The status of the proposal is divided into: under discussion, finalized and abandoned. After reaching the final status, start specific implementation (PR can also be implemented synchronously during the discussion) |
|
||||||
|
|
||||||
### Final decision maker mechanism |
|
||||||
|
|
||||||
If the maintainer team members have major differences on a requirement, the final decision is made by @Terry Mao. |
|
||||||
--> |
|
||||||
|
|
||||||
### What problem is the feature used to solve? |
|
||||||
<!-- |
|
||||||
example: |
|
||||||
We hope to add event interface to Kratos framework to access middleware such as Kafka and rabbitmq |
|
||||||
--> |
|
||||||
|
|
||||||
### Requirements description of the feature |
|
||||||
<!-- |
|
||||||
example: |
|
||||||
The event interface should be added to Kratos. The interface should contain subscribers and publishers, and the message body should contain key value head |
|
||||||
--> |
|
||||||
### References |
|
||||||
<!-- |
|
||||||
example: |
|
||||||
- [nats](http://xxxxx) |
|
||||||
- [kafka](http://xxxxx) |
|
||||||
- [rabbitmq](http://xxxxx) |
|
||||||
--> |
|
@ -1,73 +0,0 @@ |
|||||||
--- |
|
||||||
name: "\U0001F9F1 Proposal Request" |
|
||||||
about: Implementation draft of feature. |
|
||||||
title: "[Proposal]" |
|
||||||
labels: proposal |
|
||||||
assignees: '' |
|
||||||
--- |
|
||||||
|
|
||||||
Please see the FAQ in our main README.md before submitting your issue. |
|
||||||
|
|
||||||
<!-- |
|
||||||
In order to accurately distinguish that the needs put forward by users are the needs of most users and reasonable needs, solicit community opinions through the process, and the features adopted by the community will be realized as new functions. |
|
||||||
|
|
||||||
In order to make the proposal process as simple as possible, the process includes three stages: feature request - > proposal - > pull-request, where feature, proposal is issue and pull-request is the specific function implementation. |
|
||||||
|
|
||||||
### Feature-request |
|
||||||
|
|
||||||
In order to help the community correctly understand the requirements of the feature, the feature request issue needs to describe the functional requirements and relevant references or documents in detail. And the feature request issue can contain the basic description of the function, which can be used as a reference for the function implementation in the proposal. |
|
||||||
|
|
||||||
### Proposal |
|
||||||
|
|
||||||
Proposal contains the basic implementation methods of functions, such as interface definition, general usage of functions, etc. |
|
||||||
|
|
||||||
### Pull-request |
|
||||||
|
|
||||||
After the function is realized, a merge request will be initiated to associate the proposal issue with the function issue. After the merger is completed, all questions will be closed and the process will end. |
|
||||||
|
|
||||||
### Decision process |
|
||||||
|
|
||||||
When more than five maintainer members agree to implement the feature, a proposal issue will be created for detailed design. The status of the proposal is divided into: under discussion, finalized and abandoned. After reaching the final status, start specific implementation (PR can also be implemented synchronously during the discussion) |
|
||||||
|
|
||||||
### Final decision maker mechanism |
|
||||||
|
|
||||||
If the maintainer team members have major differences on a requirement, the final decision is made by @Terry Mao. |
|
||||||
--> |
|
||||||
|
|
||||||
### Proposal description |
|
||||||
<!-- |
|
||||||
example: |
|
||||||
Add event interface for accessing message oriented middleware |
|
||||||
--> |
|
||||||
### Implementation mode |
|
||||||
<!-- |
|
||||||
```go |
|
||||||
example: |
|
||||||
type Message interface { |
|
||||||
Key() string |
|
||||||
Value() []byte |
|
||||||
Header() map[string]string |
|
||||||
Ack() error |
|
||||||
Nack() error |
|
||||||
} |
|
||||||
|
|
||||||
type Handler func(context.Context, Message) error |
|
||||||
|
|
||||||
type Event interface { |
|
||||||
Send(ctx context.Context, key string, value []byte]) error |
|
||||||
Receive(ctx context.Context, handler Handler) error |
|
||||||
Close() error |
|
||||||
} |
|
||||||
```` |
|
||||||
--> |
|
||||||
### Usage demonstration |
|
||||||
<!-- |
|
||||||
example: |
|
||||||
```go |
|
||||||
msg := kafka.NewMessage("kratos", []byte("hello world"), map[string]string{ |
|
||||||
"user": "kratos", |
|
||||||
"phone": "123456", |
|
||||||
}) |
|
||||||
err := sender.Send(context.Background(), msg) |
|
||||||
``` |
|
||||||
--> |
|
@ -1,10 +0,0 @@ |
|||||||
--- |
|
||||||
name: "\U0001F680 Question" |
|
||||||
about: Ask a question about Kratos. |
|
||||||
title: "[Question]" |
|
||||||
labels: question |
|
||||||
assignees: '' |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
Please see the FAQ in our main README.md before submitting your issue. |
|
@ -1,87 +0,0 @@ |
|||||||
--- |
|
||||||
version: 2 |
|
||||||
updates: |
|
||||||
- package-ecosystem: "github-actions" |
|
||||||
directory: "/" |
|
||||||
schedule: |
|
||||||
interval: "daily" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/config/apollo" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/config/consul" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/config/etcd" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/config/kubernetes" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/config/nacos" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/encoding/msgpack" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/log/aliyun" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/log/zap" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/log/fluent" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/metrics/datadog" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/metrics/prometheus" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/opensergo" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/registry/apollo" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/registry/consul" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/registry/etcd" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/registry/kubernetes" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/registry/nacos" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/registry/zookeeper" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/registry/eureka" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
||||||
- package-ecosystem: "gomod" |
|
||||||
directory: "/contrib/registry/polaris" |
|
||||||
schedule: |
|
||||||
interval: "weekly" |
|
@ -1,40 +0,0 @@ |
|||||||
<!-- |
|
||||||
🎉 Thanks for sending a pull request to Kratos! Here are some tips for you: |
|
||||||
|
|
||||||
1. If this is your first time contributing to Kratos, please read our contribution guide: https://go-kratos.dev/en/docs/community/contribution/ |
|
||||||
2. Ensure you have added or ran the appropriate tests and lint for your PR, please use `make lint` and `make test` before filing your PR, use `make clean` to tidy your go mod. |
|
||||||
3. If the PR is unfinished, you may need to mark it as a WIP(Work In Progress) PR or Draft PR |
|
||||||
4. Please use a semantic commits format title, such as `<type>[optional scope]: <description>`, see: https://go-kratos.dev/docs/community/contribution#type |
|
||||||
5. at the same time, please note that similar work should be submitted in one PR as far as possible to reduce the workload of reviewers. Do not split a work into multiple PR unless it should. |
|
||||||
--> |
|
||||||
|
|
||||||
<!-- |
|
||||||
🎉 感谢您向 Kratos 发送 PR!以下是一些提示: |
|
||||||
如果这是你第一次为 Kratos 贡献,请阅读我们的贡献指南:https://go-kratos.dev/en/docs/community/contribution/ |
|
||||||
2、确保您已经为您的 PR 添加或运行了适当的测试和lint,请在提交PR之前使用“make lint”和“make test”,使用“make clean”整理您的 go.mod。 |
|
||||||
3、如果 PR 未完成,您可能需要将其标记为 WIP(Work In Progress)PR 或 Draft PR |
|
||||||
4、请使用语义提交格式标题,如“<类型>[可选范围]:<说明>`,请参阅:https://go-kratos.dev/docs/community/contribution#type |
|
||||||
5. 同时请注意,同类的工作请尽量在一个PR中提交,以减轻 review 者的工作负担,不要把一项工作拆分成很多个PR,除非它应该这样做。 |
|
||||||
--> |
|
||||||
|
|
||||||
|
|
||||||
#### Description (what this PR does / why we need it): |
|
||||||
<!-- |
|
||||||
* The description should include the motivation for this PR or contrast this with previous behavior |
|
||||||
--> |
|
||||||
|
|
||||||
|
|
||||||
#### Which issue(s) this PR fixes (resolves / be part of): |
|
||||||
<!-- |
|
||||||
* Automatically closes linked issue when PR is merged. |
|
||||||
* If your PR is not fully resolved the issue, please use `part of #<issue number>` instead. |
|
||||||
|
|
||||||
Usage: `fixes/resolves #<issue number>`, or `fixes/resolves (paste link of issue)`. |
|
||||||
--> |
|
||||||
|
|
||||||
|
|
||||||
#### Other special notes for the reviewers: |
|
||||||
<!-- |
|
||||||
* Somethings that need extra attention for the reviewers |
|
||||||
* Some additional notes, TODO list, etc. |
|
||||||
--> |
|
@ -1,33 +0,0 @@ |
|||||||
titleOnly: true |
|
||||||
commitOnly: false |
|
||||||
titleAndCommits: false |
|
||||||
scopes: |
|
||||||
- api |
|
||||||
- cmd |
|
||||||
- config |
|
||||||
- contrib |
|
||||||
- docs |
|
||||||
- encoding |
|
||||||
- hack |
|
||||||
- internal |
|
||||||
- log |
|
||||||
- metadata |
|
||||||
- metrics |
|
||||||
- middleware |
|
||||||
- registry |
|
||||||
- selector |
|
||||||
- third_party |
|
||||||
- transport |
|
||||||
types: |
|
||||||
- deps |
|
||||||
- feat |
|
||||||
- fix |
|
||||||
- docs |
|
||||||
- style |
|
||||||
- refactor |
|
||||||
- perf |
|
||||||
- test |
|
||||||
- build |
|
||||||
- ci |
|
||||||
- chore |
|
||||||
- revert |
|
@ -1,12 +0,0 @@ |
|||||||
daysUntilStale: 30 |
|
||||||
daysUntilClose: 3 |
|
||||||
exemptLabels: |
|
||||||
- pinned |
|
||||||
- security |
|
||||||
- bug |
|
||||||
staleLabel: wontfix |
|
||||||
markComment: > |
|
||||||
This issue has been automatically marked as stale because it has not had |
|
||||||
recent activity. It will be closed if no further activity occurs. Thank you |
|
||||||
for your contributions. |
|
||||||
closeComment: true |
|
@ -1,22 +0,0 @@ |
|||||||
name: "CodeQL" |
|
||||||
|
|
||||||
on: |
|
||||||
push: |
|
||||||
branches: [ main, v1.0.x ] |
|
||||||
|
|
||||||
jobs: |
|
||||||
analyze: |
|
||||||
name: Analyze |
|
||||||
runs-on: ubuntu-latest |
|
||||||
|
|
||||||
steps: |
|
||||||
- name: Checkout repo |
|
||||||
uses: actions/checkout@v3 |
|
||||||
|
|
||||||
- name: Initialize CodeQL |
|
||||||
uses: github/codeql-action/init@v2 |
|
||||||
with: |
|
||||||
languages: go |
|
||||||
|
|
||||||
- name: CodeQL Analysis |
|
||||||
uses: github/codeql-action/analyze@v2 |
|
@ -1,27 +0,0 @@ |
|||||||
on: |
|
||||||
push: |
|
||||||
branches: |
|
||||||
- main |
|
||||||
tags: |
|
||||||
- "*" |
|
||||||
|
|
||||||
name: Sync to Gitee |
|
||||||
jobs: |
|
||||||
run: |
|
||||||
name: Run |
|
||||||
runs-on: ubuntu-latest |
|
||||||
steps: |
|
||||||
- name: Checkout source code |
|
||||||
uses: actions/checkout@v3 |
|
||||||
- name: Mirror Github to Gitee |
|
||||||
uses: Yikun/hub-mirror-action@v1.2 |
|
||||||
with: |
|
||||||
src: github/go-kratos |
|
||||||
dst: gitee/go-kratos |
|
||||||
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} |
|
||||||
dst_token: ${{ secrets.GITEE_TOKEN }} |
|
||||||
account_type: org |
|
||||||
timeout: 600 |
|
||||||
debug: true |
|
||||||
force_update: true |
|
||||||
static_list: "kratos" |
|
@ -1,36 +0,0 @@ |
|||||||
name: Lint |
|
||||||
|
|
||||||
on: |
|
||||||
push: |
|
||||||
pull_request: |
|
||||||
branches: |
|
||||||
- main |
|
||||||
workflow_dispatch: |
|
||||||
|
|
||||||
jobs: |
|
||||||
resolve-modules: |
|
||||||
name: resolve module |
|
||||||
runs-on: ubuntu-latest |
|
||||||
outputs: |
|
||||||
matrix: ${{ steps.set-matrix.outputs.matrix }} |
|
||||||
steps: |
|
||||||
- name: Checkout Repo |
|
||||||
uses: actions/checkout@v3 |
|
||||||
|
|
||||||
- id: set-matrix |
|
||||||
run: ./hack/resolve-modules.sh |
|
||||||
|
|
||||||
lint: |
|
||||||
name: lint module |
|
||||||
runs-on: ubuntu-latest |
|
||||||
needs: resolve-modules |
|
||||||
strategy: |
|
||||||
matrix: ${{ fromJson(needs.resolve-modules.outputs.matrix) }} |
|
||||||
steps: |
|
||||||
- uses: actions/checkout@v3 |
|
||||||
- name: Lint |
|
||||||
uses: golangci/golangci-lint-action@v3 |
|
||||||
with: |
|
||||||
version: latest |
|
||||||
working-directory: ${{ matrix.workdir }} |
|
||||||
skip-pkg-cache: true |
|
@ -1,39 +1,32 @@ |
|||||||
# Reference https://github.com/github/gitignore/blob/master/Go.gitignore |
# idea ignore |
||||||
# Binaries for programs and plugins |
.idea/ |
||||||
|
*.ipr |
||||||
|
*.iml |
||||||
|
*.iws |
||||||
|
.vscode/ |
||||||
|
|
||||||
|
# temp ignore |
||||||
|
*.log |
||||||
|
*.cache |
||||||
|
*.diff |
||||||
*.exe |
*.exe |
||||||
*.exe~ |
*.exe~ |
||||||
*.dll |
*.patch |
||||||
*.dylib |
*.swp |
||||||
|
*.tmp |
||||||
# Test binary, built with `go test -c` |
|
||||||
*.test |
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE |
|
||||||
*.out |
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it) |
|
||||||
vendor/ |
|
||||||
|
|
||||||
# Go workspace file |
|
||||||
go.work |
|
||||||
go.work.sum |
|
||||||
|
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects) |
|
||||||
*.o |
|
||||||
*.a |
|
||||||
*.so |
|
||||||
|
|
||||||
# OS General |
# system ignore |
||||||
Thumbs.db |
|
||||||
.DS_Store |
.DS_Store |
||||||
|
Thumbs.db |
||||||
|
|
||||||
# project |
# project |
||||||
*.cert |
*.cert |
||||||
*.key |
*.key |
||||||
*.log |
tool/kratos/kratos |
||||||
bin/ |
tool/kratos-protoc/kratos-protoc |
||||||
|
tool/kratos-gen-bts/kratos-gen-bts |
||||||
# Develop tools |
tool/kratos-gen-mc/kratos-gen-mc |
||||||
.vscode/ |
tool/kratos/kratos-protoc/kratos-protoc |
||||||
.idea/ |
tool/kratos/protobuf/protoc-gen-bm/protoc-gen-bm |
||||||
*.swp |
tool/kratos/protobuf/protoc-gen-ecode/protoc-gen-ecode |
||||||
|
tool/kratos/protobuf/protoc-gen-bswagger/protoc-gen-bswagger |
||||||
|
@ -1,71 +1,138 @@ |
|||||||
|
# [index] https://github.com/golangci/golangci-lint |
||||||
|
# [example] https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml |
||||||
|
|
||||||
run: |
run: |
||||||
timeout: 5m |
tests: true #是否包含测试文件 |
||||||
modules-download-mode: readonly |
issues-exit-code: 0 |
||||||
|
|
||||||
|
linters-settings: |
||||||
|
# govet: |
||||||
|
# check-shadowing: true #启用了对同名变量名在函数中被隐藏的警告 |
||||||
|
gofmt: |
||||||
|
simplify: true |
||||||
|
goimports: |
||||||
|
local-prefixes: "github.com/go-kratos/kratos" # 格式化代码时,本地代码单独块 |
||||||
|
gocritic: |
||||||
|
enabled-tags: |
||||||
|
- diagnostic |
||||||
|
# - style |
||||||
|
# - performance |
||||||
|
disabled-checks: |
||||||
|
#- wrapperFunc |
||||||
|
#- dupImport # https://github.com/go-critic/go-critic/issues/845 |
||||||
|
- commentedOutCode |
||||||
|
- ifElseChain |
||||||
|
- elseif |
||||||
|
settings: # settings passed to gocritic |
||||||
|
captLocal: # must be valid enabled check name |
||||||
|
paramsOnly: true |
||||||
|
# rangeValCopy: |
||||||
|
# sizeThreshold: 32 |
||||||
|
lll: |
||||||
|
line-length: 500 |
||||||
|
funlen: |
||||||
|
lines: 500 |
||||||
|
statements: 500 |
||||||
|
gocyclo: |
||||||
|
min-complexity: 100 |
||||||
|
|
||||||
linters: |
linters: |
||||||
disable-all: true |
disable-all: true |
||||||
fast: false |
|
||||||
enable: |
enable: |
||||||
- bodyclose |
# https://golangci-lint.run/usage/configuration/ |
||||||
- deadcode |
- bodyclose # http.resp.body 内存泄露检查 |
||||||
- dogsled |
- deadcode # 无用的变量声明检查 |
||||||
- durationcheck |
- depguard # 自定义依赖包白、黑名单 控制导包 |
||||||
- errcheck |
- dogsled # 空白标识符的赋值检查 默认为2 |
||||||
- exportloopref |
#- dupl # 重复代码检查 |
||||||
- govet |
- errcheck # 未判断的error返回值检查 |
||||||
- gosimple |
- funlen # 接口最大行数检查 |
||||||
- gofmt |
#- gochecknoinits # 包中定义init()函数检查 |
||||||
- gofumpt |
#- goconst # 常量字符串检查 |
||||||
- goconst |
- gocritic # |
||||||
- goimports |
- gocyclo # 代码复杂度检查 |
||||||
- gomnd |
- gofmt # 优化代码 |
||||||
- gocyclo |
- goimports # 自动增加和删除包 |
||||||
- ineffassign |
- golint # 代码风格检查 |
||||||
- lll |
#- gomnd # 参数、赋值、用例、条件、操作和返回语句检查 |
||||||
- prealloc |
- goprintffuncname # |
||||||
- revive |
- gosec # 源代码安全检查 |
||||||
- staticcheck |
- gosimple # 可以优化的代码检查 注:该工具已整合到staticcheck中 |
||||||
- structcheck |
- govet # 代码正确性检查 |
||||||
- typecheck |
- ineffassign # 无效赋值检查 |
||||||
- unused |
- interfacer # 建议接口的使用方式 |
||||||
- varcheck |
- lll # 行最大字符 |
||||||
- whitespace |
- misspell # 拼写错误检查 |
||||||
- wastedassign |
- nakedret # 大于指定函数长度的函数的无约束返回值检查 |
||||||
- unconvert |
- nolintlint # |
||||||
|
- rowserrcheck # sql.Rows.Err检查 |
||||||
|
- scopelint # 循环变量引用检查,排除test文件 |
||||||
|
- staticcheck # 静态检查 |
||||||
|
- structcheck # 结构体字段的约束条件检查 |
||||||
|
- stylecheck # 代码风格检查 |
||||||
|
- typecheck # 类型检查 |
||||||
|
- unconvert # 类型转换检查 |
||||||
|
- unparam # 未使用参数检查 |
||||||
|
#- unused # 未使用变量、函数检查 |
||||||
|
- varcheck # 报告exported变量和常量 |
||||||
|
- whitespace # 空行检查 |
||||||
|
|
||||||
# don't enable: |
severity: |
||||||
# - asciicheck |
# Default value is empty string. |
||||||
# - scopelint |
# Set the default severity for issues. If severity rules are defined and the issues |
||||||
# - gochecknoglobals |
# do not match or no severity is provided to the rule this will be the default |
||||||
# - gocognit |
# severity applied. Severities should match the supported severity names of the |
||||||
# - godot |
# selected out format. |
||||||
# - godox |
# - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity |
||||||
# - goerr113 |
# - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#severity |
||||||
# - interfacer |
# - Github: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message |
||||||
# - maligned |
default-severity: error |
||||||
# - nestif |
# The default value is false. |
||||||
# - prealloc |
# If set to true severity-rules regular expressions become case sensitive. |
||||||
# - testpackage |
case-sensitive: false |
||||||
# - stylrcheck |
# Default value is empty list. |
||||||
# - wsl |
# When a list of severity rules are provided, severity information will be added to lint |
||||||
|
# issues. Severity rules have the same filtering capability as exclude rules except you |
||||||
|
# are allowed to specify one matcher per severity rule. |
||||||
|
# Only affects out formats that support setting severity information. |
||||||
|
rules: |
||||||
|
- linters: |
||||||
|
- dupl |
||||||
|
- nakedret |
||||||
|
- lll |
||||||
|
- misspell |
||||||
|
- goprintffuncname |
||||||
|
- stylecheck |
||||||
|
- deadcode |
||||||
|
- whitespace |
||||||
|
- unparam |
||||||
|
- golint |
||||||
|
- gosec |
||||||
|
- staticcheck |
||||||
|
- structcheck |
||||||
|
- gocritic |
||||||
|
- errcheck |
||||||
|
- rowserrcheck |
||||||
|
- unconvert |
||||||
|
- gosimple |
||||||
|
- rowserrcheck |
||||||
|
- ineffassign |
||||||
|
severity: warning |
||||||
|
|
||||||
linters-settings: |
issues: |
||||||
govet: |
# Excluding configuration per-path, per-linter, per-text and per-source |
||||||
check-shadowing: true |
exclude-rules: |
||||||
whitespace: |
- path: _test\.go |
||||||
multi-func: true |
linters: |
||||||
lll: |
- gomnd |
||||||
line-length: 160 |
- gocyclo |
||||||
gomnd: |
- errcheck |
||||||
# don't include the "operation", "argument" and "assign" |
- dupl |
||||||
checks: |
- gosec |
||||||
- case |
- scopelint |
||||||
- condition |
- interfacer |
||||||
- return |
- govet |
||||||
goconst: |
# https://github.com/go-critic/go-critic/issues/926 |
||||||
ignore-tests: true |
- linters: |
||||||
gocyclo: |
- gocritic |
||||||
# recommend 10-20 |
text: "unnecessaryDefer:" |
||||||
min-complexity: 50 |
|
||||||
goimports: |
|
||||||
local-prefixes: github.com/go-kratos # Put imports beginning with prefix after 3rd-party packages |
|
@ -0,0 +1,59 @@ |
|||||||
|
language: go |
||||||
|
|
||||||
|
go: |
||||||
|
- 1.12.x |
||||||
|
- 1.13.x |
||||||
|
|
||||||
|
services: |
||||||
|
- docker |
||||||
|
|
||||||
|
# Only clone the most recent commit. |
||||||
|
git: |
||||||
|
depth: 1 |
||||||
|
|
||||||
|
# Force-enable Go modules. This will be unnecessary when Go 1.12 lands. |
||||||
|
env: |
||||||
|
global: |
||||||
|
- GO111MODULE=on |
||||||
|
- REGION=sh |
||||||
|
- ZONE=sh001 |
||||||
|
- DEPLOY_ENV=dev |
||||||
|
- DISCOVERY_NODES=127.0.0.1:7171 |
||||||
|
- HTTP_PERF=tcp://0.0.0.0:0 |
||||||
|
- DOCKER_COMPOSE_VERSION=1.24.1 |
||||||
|
- ZK_VERSION=3.5.6 |
||||||
|
|
||||||
|
before_install: |
||||||
|
# docker-compose |
||||||
|
- sudo rm /usr/local/bin/docker-compose |
||||||
|
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose |
||||||
|
- chmod +x docker-compose |
||||||
|
- sudo mv docker-compose /usr/local/bin |
||||||
|
# zookeeper |
||||||
|
- wget "http://apache.cs.utah.edu/zookeeper/zookeeper-${ZK_VERSION}/apache-zookeeper-${ZK_VERSION}-bin.tar.gz" |
||||||
|
- tar -xvf "apache-zookeeper-${ZK_VERSION}-bin.tar.gz" |
||||||
|
- mv apache-zookeeper-${ZK_VERSION}-bin zk |
||||||
|
- chmod +x ./zk/bin/zkServer.sh |
||||||
|
|
||||||
|
# Skip the install step. Don't `go get` dependencies. Only build with the code |
||||||
|
# in vendor/ |
||||||
|
install: true |
||||||
|
|
||||||
|
# Anything in before_script that returns a nonzero exit code will flunk the |
||||||
|
# build and immediately stop. It's sorta like having set -e enabled in bash. |
||||||
|
# Make sure golangci-lint is vendored. |
||||||
|
before_script: |
||||||
|
- curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $GOPATH/bin |
||||||
|
# discovery |
||||||
|
- curl -sfL https://raw.githubusercontent.com/bilibili/discovery/master/install.sh | sh -s -- -b $GOPATH/bin |
||||||
|
- curl -sfL https://raw.githubusercontent.com/bilibili/discovery/master/cmd/discovery/discovery-example.toml -o $GOPATH/bin/discovery.toml |
||||||
|
- nohup bash -c "$GOPATH/bin/discovery -conf $GOPATH/bin/discovery.toml &" |
||||||
|
# zookeeper |
||||||
|
- sudo ./zk/bin/zkServer.sh start ./zk/conf/zoo_sample.cfg 1> /dev/null |
||||||
|
|
||||||
|
script: |
||||||
|
- go build ./... |
||||||
|
- go test ./... |
||||||
|
|
||||||
|
after_success: |
||||||
|
- golangci-lint run # run a bunch of code checkers/linters in parallel |
@ -1,128 +0,0 @@ |
|||||||
# Contributor Covenant Code of Conduct |
|
||||||
|
|
||||||
## Our Pledge |
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our |
|
||||||
community a harassment-free experience for everyone, regardless of age, body |
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender |
|
||||||
identity and expression, level of experience, education, socio-economic status, |
|
||||||
nationality, personal appearance, race, religion, or sexual identity |
|
||||||
and orientation. |
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming, |
|
||||||
diverse, inclusive, and healthy community. |
|
||||||
|
|
||||||
## Our Standards |
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our |
|
||||||
community include: |
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people |
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences |
|
||||||
* Giving and gracefully accepting constructive feedback |
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes, |
|
||||||
and learning from the experience |
|
||||||
* Focusing on what is best not just for us as individuals, but for the |
|
||||||
overall community |
|
||||||
|
|
||||||
Examples of unacceptable behavior include: |
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or |
|
||||||
advances of any kind |
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks |
|
||||||
* Public or private harassment |
|
||||||
* Publishing others' private information, such as a physical or email |
|
||||||
address, without their explicit permission |
|
||||||
* Other conduct which could reasonably be considered inappropriate in a |
|
||||||
professional setting |
|
||||||
|
|
||||||
## Enforcement Responsibilities |
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of |
|
||||||
acceptable behavior and will take appropriate and fair corrective action in |
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive, |
|
||||||
or harmful. |
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject |
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are |
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation |
|
||||||
decisions when appropriate. |
|
||||||
|
|
||||||
## Scope |
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when |
|
||||||
an individual is officially representing the community in public spaces. |
|
||||||
Examples of representing our community include using an official e-mail address, |
|
||||||
posting via an official social media account, or acting as an appointed |
|
||||||
representative at an online or offline event. |
|
||||||
|
|
||||||
## Enforcement |
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be |
|
||||||
reported to the community leaders responsible for enforcement at |
|
||||||
iammao@vip.qq.com. |
|
||||||
All complaints will be reviewed and investigated promptly and fairly. |
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the |
|
||||||
reporter of any incident. |
|
||||||
|
|
||||||
## Enforcement Guidelines |
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining |
|
||||||
the consequences for any action they deem in violation of this Code of Conduct: |
|
||||||
|
|
||||||
### 1. Correction |
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed |
|
||||||
unprofessional or unwelcome in the community. |
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing |
|
||||||
clarity around the nature of the violation and an explanation of why the |
|
||||||
behavior was inappropriate. A public apology may be requested. |
|
||||||
|
|
||||||
### 2. Warning |
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series |
|
||||||
of actions. |
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No |
|
||||||
interaction with the people involved, including unsolicited interaction with |
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This |
|
||||||
includes avoiding interactions in community spaces as well as external channels |
|
||||||
like social media. Violating these terms may lead to a temporary or |
|
||||||
permanent ban. |
|
||||||
|
|
||||||
### 3. Temporary Ban |
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including |
|
||||||
sustained inappropriate behavior. |
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public |
|
||||||
communication with the community for a specified period of time. No public or |
|
||||||
private interaction with the people involved, including unsolicited interaction |
|
||||||
with those enforcing the Code of Conduct, is allowed during this period. |
|
||||||
Violating these terms may lead to a permanent ban. |
|
||||||
|
|
||||||
### 4. Permanent Ban |
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community |
|
||||||
standards, including sustained inappropriate behavior, harassment of an |
|
||||||
individual, or aggression toward or disparagement of classes of individuals. |
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within |
|
||||||
the community. |
|
||||||
|
|
||||||
## Attribution |
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], |
|
||||||
version 2.0, available at |
|
||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. |
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct |
|
||||||
enforcement ladder](https://github.com/mozilla/diversity). |
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org |
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at |
|
||||||
https://www.contributor-covenant.org/faq. Translations are available at |
|
||||||
https://www.contributor-covenant.org/translations. |
|
@ -1,122 +0,0 @@ |
|||||||
The kratos community wants to be helped by a wide range of developers, so you'd like to take a few minutes to read this guide before you mention the problem or pull request. |
|
||||||
|
|
||||||
## Reporting Bug or Fixing Bugs |
|
||||||
We use GitHub issues to manage issues. If you want to submit , first make sure you've searched for existing issues, pull requests and read our [FAQ](https://go-kratos.dev/docs/intro/faq). |
|
||||||
|
|
||||||
When submitting a bug report, use the issue template we provide to clearly describe the problems encountered and how to reproduce, and if convenient it is best to provide a minimal reproduce repository. |
|
||||||
|
|
||||||
## Adding new features |
|
||||||
|
|
||||||
In order to accurately distinguish whether the needs put forward by users are the needs or reasonable needs of most users, solicit opinions from the community through the proposal process, and the proposals adopted by the community will be realized as new feature. |
|
||||||
In order to make the proposal process as simple as possible, the process includes three stages: proposal, feature and PR, in which proposal, feature is issue and PR is the specific function implementation. |
|
||||||
In order to facilitate the community to correctly understand the requirements of the proposal, the proposal issue needs to describe the functional requirements and relevant references or literature in detail. |
|
||||||
When most community users agree with this proposal, they will create a feature issue associated with the proposal issue. |
|
||||||
The feature issue needs to describe the implementation method and function demonstration in detail as a reference for the final function implementation. |
|
||||||
After the function is implemented, a merge request will be initiated to associate the proposal issue and feature issue. |
|
||||||
After the merge is completed, Close all issues. |
|
||||||
|
|
||||||
## How to submit code |
|
||||||
If you've never submitted code on GitHub, follow these steps: |
|
||||||
|
|
||||||
- First, please fork items to your GitHub account |
|
||||||
- Then create a new feature branch based on the main branch and name it features such as feature-log |
|
||||||
- Write code |
|
||||||
- Submit code to the far end branch |
|
||||||
- Submit a PR request in github |
|
||||||
- Wait for review and merge to the main branch |
|
||||||
|
|
||||||
**Note That when you submit a PR request, you first ensure that the code uses the correct coding specifications and that there are complete test cases, and that the information in the submission of the PR is best associated with the relevant issue to ease the workload of the auditor.** |
|
||||||
|
|
||||||
## Conventional Commits |
|
||||||
|
|
||||||
``` |
|
||||||
<type>[optional scope]: <description> |
|
||||||
|
|
||||||
[optional body] |
|
||||||
|
|
||||||
[optional footer(s)] |
|
||||||
``` |
|
||||||
|
|
||||||
> More: [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) |
|
||||||
|
|
||||||
### type |
|
||||||
|
|
||||||
There are the following types of commit: |
|
||||||
|
|
||||||
#### Main |
|
||||||
|
|
||||||
- **fix**: A bug fix |
|
||||||
- **feat**: A new feature |
|
||||||
- **deps**: Changes external dependencies |
|
||||||
- **break**: Changes has break change |
|
||||||
|
|
||||||
#### Other |
|
||||||
|
|
||||||
- **docs**: Documentation only changes |
|
||||||
- **refactor**: A code change that neither fixes a bug nor adds a feature |
|
||||||
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc) |
|
||||||
- **test**: Adding missing tests or correcting existing tests |
|
||||||
- **chore** Daily work, examples, etc. |
|
||||||
- **ci**: Changes to our CI configuration files and scripts |
|
||||||
|
|
||||||
### scope |
|
||||||
|
|
||||||
The following is the list of supported scopes: |
|
||||||
|
|
||||||
- transport |
|
||||||
- examples |
|
||||||
- middleware |
|
||||||
- config |
|
||||||
- cmd |
|
||||||
- etc. |
|
||||||
|
|
||||||
### description |
|
||||||
|
|
||||||
The description contains a succinct description of the change |
|
||||||
|
|
||||||
- use the imperative, present tense: "change" not "changed" nor "changes" |
|
||||||
- don't capitalize the first letter |
|
||||||
- no dot (.) at the end |
|
||||||
|
|
||||||
### body |
|
||||||
|
|
||||||
The body should include the motivation for the change and contrast this with previous behavior. |
|
||||||
|
|
||||||
### footer |
|
||||||
|
|
||||||
The footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit Closes. |
|
||||||
|
|
||||||
### examples |
|
||||||
|
|
||||||
|
|
||||||
#### Only commit message |
|
||||||
``` |
|
||||||
fix: The log debug level should be -1 |
|
||||||
``` |
|
||||||
|
|
||||||
#### Attention |
|
||||||
``` |
|
||||||
refactor!(transport/http): replacement underlying implementation |
|
||||||
``` |
|
||||||
|
|
||||||
#### Full commit message |
|
||||||
``` |
|
||||||
fix(log): [BREAKING-CHANGE] unable to meet the requirement of log Library |
|
||||||
|
|
||||||
Explain the reason, purpose, realization method, etc. |
|
||||||
|
|
||||||
Close #777 |
|
||||||
Doc change on doc/#111 |
|
||||||
BREAKING CHANGE: |
|
||||||
Breaks log.info api, log.log should be used instead |
|
||||||
``` |
|
||||||
## Release |
|
||||||
|
|
||||||
You can use `kratos changelog dev` to generate a change log during. |
|
||||||
|
|
||||||
The following is the list of supported types: |
|
||||||
|
|
||||||
- Breaking Change |
|
||||||
- Dependencies |
|
||||||
- Bug Fixes |
|
||||||
- Others |
|
@ -1,102 +0,0 @@ |
|||||||
user := $(shell whoami)
|
|
||||||
rev := $(shell git rev-parse --short HEAD)
|
|
||||||
os := $(shell expr substr $(shell uname -s) 1 5)
|
|
||||||
|
|
||||||
# GOBIN > GOPATH > INSTALLDIR
|
|
||||||
# Mac OS X
|
|
||||||
ifeq ($(shell uname),Darwin) |
|
||||||
GOBIN := $(shell echo ${GOBIN} | cut -d':' -f1)
|
|
||||||
GOPATH := $(shell echo $(GOPATH) | cut -d':' -f1)
|
|
||||||
endif |
|
||||||
|
|
||||||
# Linux
|
|
||||||
ifeq ($(os),Linux) |
|
||||||
GOBIN := $(shell echo ${GOBIN} | cut -d':' -f1)
|
|
||||||
GOPATH := $(shell echo $(GOPATH) | cut -d':' -f1)
|
|
||||||
endif |
|
||||||
|
|
||||||
# Windows
|
|
||||||
ifeq ($(os),MINGW) |
|
||||||
GOBIN := $(subst \,/,$(GOBIN))
|
|
||||||
GOPATH := $(subst \,/,$(GOPATH))
|
|
||||||
GOBIN :=/$(shell echo "$(GOBIN)" | cut -d';' -f1 | sed 's/://g')
|
|
||||||
GOPATH :=/$(shell echo "$(GOPATH)" | cut -d';' -f1 | sed 's/://g')
|
|
||||||
endif |
|
||||||
BIN := ""
|
|
||||||
|
|
||||||
TOOLS_SHELL="./hack/tools.sh"
|
|
||||||
# golangci-lint
|
|
||||||
LINTER := bin/golangci-lint
|
|
||||||
|
|
||||||
# check GOBIN
|
|
||||||
ifneq ($(GOBIN),) |
|
||||||
BIN=$(GOBIN)
|
|
||||||
else |
|
||||||
# check GOPATH
|
|
||||||
ifneq ($(GOPATH),)
|
|
||||||
BIN=$(GOPATH)/bin
|
|
||||||
endif
|
|
||||||
endif |
|
||||||
|
|
||||||
$(LINTER): |
|
||||||
curl -SL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s latest
|
|
||||||
|
|
||||||
all: |
|
||||||
@cd cmd/kratos && go build && cd - &> /dev/null
|
|
||||||
@cd cmd/protoc-gen-go-errors && go build && cd - &> /dev/null
|
|
||||||
@cd cmd/protoc-gen-go-http && go build && cd - &> /dev/null
|
|
||||||
|
|
||||||
.PHONY: install |
|
||||||
install: all |
|
||||||
ifeq ($(user),root) |
|
||||||
#root, install for all user
|
|
||||||
@cp ./cmd/kratos/kratos /usr/bin
|
|
||||||
@cp ./cmd/protoc-gen-go-errors/protoc-gen-go-errors /usr/bin
|
|
||||||
@cp ./cmd/protoc-gen-go-http/protoc-gen-go-http /usr/bin
|
|
||||||
else |
|
||||||
#!root, install for current user
|
|
||||||
$(shell if [ -z '$(BIN)' ]; then read -p "Please select installdir: " REPLY; mkdir -p $${REPLY};\
|
|
||||||
cp ./cmd/kratos/kratos $${REPLY}/;cp ./cmd/protoc-gen-go-errors/protoc-gen-go-errors $${REPLY}/;cp ./cmd/protoc-gen-go-http/protoc-gen-go-http $${REPLY}/;else mkdir -p '$(BIN)';\
|
|
||||||
cp ./cmd/kratos/kratos '$(BIN)';cp ./cmd/protoc-gen-go-errors/protoc-gen-go-errors '$(BIN)';cp ./cmd/protoc-gen-go-http/protoc-gen-go-http '$(BIN)'; fi)
|
|
||||||
endif |
|
||||||
@which protoc-gen-go &> /dev/null || go get google.golang.org/protobuf/cmd/protoc-gen-go
|
|
||||||
@which protoc-gen-go-grpc &> /dev/null || go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
|
|
||||||
@which protoc-gen-validate &> /dev/null || go get github.com/envoyproxy/protoc-gen-validate
|
|
||||||
@echo "install finished"
|
|
||||||
|
|
||||||
.PHONY: uninstall |
|
||||||
uninstall: |
|
||||||
$(shell for i in `which -a kratos | grep -v '/usr/bin/kratos' 2>/dev/null | sort | uniq`; do read -p "Press to remove $${i} (y/n): " REPLY; if [ $${REPLY} = "y" ]; then rm -f $${i}; fi; done)
|
|
||||||
$(shell for i in `which -a protoc-gen-go-grpc | grep -v '/usr/bin/protoc-gen-go-errors' 2>/dev/null | sort | uniq`; do read -p "Press to remove $${i} (y/n): " REPLY; if [ $${REPLY} = "y" ]; then rm -f $${i}; fi; done)
|
|
||||||
$(shell for i in `which -a protoc-gen-validate | grep -v '/usr/bin/protoc-gen-go-errors' 2>/dev/null | sort | uniq`; do read -p "Press to remove $${i} (y/n): " REPLY; if [ $${REPLY} = "y" ]; then rm -f $${i}; fi; done)
|
|
||||||
@echo "uninstall finished"
|
|
||||||
|
|
||||||
.PHONY: clean |
|
||||||
clean: |
|
||||||
@${TOOLS_SHELL} tidy
|
|
||||||
@echo "clean finished"
|
|
||||||
|
|
||||||
.PHONY: fix |
|
||||||
fix: $(LINTER) |
|
||||||
@${TOOLS_SHELL} fix
|
|
||||||
@echo "lint fix finished"
|
|
||||||
|
|
||||||
.PHONY: test |
|
||||||
test: |
|
||||||
@${TOOLS_SHELL} test
|
|
||||||
@echo "go test finished"
|
|
||||||
|
|
||||||
.PHONY: test-coverage |
|
||||||
test-coverage: |
|
||||||
@${TOOLS_SHELL} test_coverage
|
|
||||||
@echo "go test with coverage finished"
|
|
||||||
|
|
||||||
.PHONY: lint |
|
||||||
lint: $(LINTER) |
|
||||||
@${TOOLS_SHELL} lint
|
|
||||||
@echo "lint check finished"
|
|
||||||
|
|
||||||
.PHONY: proto |
|
||||||
proto: |
|
||||||
protoc --proto_path=./api --proto_path=./third_party --go_out=paths=source_relative:./api --go-grpc_out=paths=source_relative:./api --go-http_out=paths=source_relative:./api metadata/metadata.proto
|
|
||||||
protoc --proto_path=./third_party --go_out=paths=source_relative:./errors/errors.proto
|
|
@ -1,149 +0,0 @@ |
|||||||
<p align="center"><a href="https://go-kratos.dev/" target="_blank"><img src="https://github.com/go-kratos/kratos/blob/main/docs/images/kratos-large.png?raw=true"></a></p> |
|
||||||
|
|
||||||
<p align="center"> |
|
||||||
<a href="https://github.com/go-kratos/kratos/actions"><img src="https://github.com/go-kratos/kratos/workflows/Go/badge.svg" alt="Build Status"></a> |
|
||||||
<a href="https://pkg.go.dev/github.com/go-kratos/kratos/v2"><img src="https://pkg.go.dev/badge/github.com/go-kratos/kratos/v2" alt="GoDoc"></a> |
|
||||||
<a href="https://codecov.io/gh/go-kratos/kratos"><img src="https://codecov.io/gh/go-kratos/kratos/master/graph/badge.svg" alt="codeCov"></a> |
|
||||||
<a href="https://goreportcard.com/report/github.com/go-kratos/kratos"><img src="https://goreportcard.com/badge/github.com/go-kratos/kratos" alt="Go Report Card"></a> |
|
||||||
<a href="https://github.com/go-kratos/kratos/blob/main/LICENSE"><img src="https://img.shields.io/github/license/go-kratos/kratos" alt="License"></a> |
|
||||||
<a href="https://github.com/avelino/awesome-go"><img src="https://awesome.re/mentioned-badge.svg" alt="Awesome Go"></a> |
|
||||||
<a href="https://discord.gg/BWzJsUJ"><img src="https://img.shields.io/discord/766619759214854164?label=chat&logo=discord" alt="Discord"></a> |
|
||||||
</p> |
|
||||||
<p align="center"> |
|
||||||
<a href="https://www.producthunt.com/posts/go-kratos?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go-kratos" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=306565&theme=light" alt="Go Kratos - A Go framework for microservices. | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> |
|
||||||
</p> |
|
||||||
|
|
||||||
Translations: [English](README.md) | [简体中文](README_zh.md) |
|
||||||
|
|
||||||
# Kratos |
|
||||||
|
|
||||||
Kratos 一套轻量级 Go 微服务框架,包含大量微服务相关功能及工具。 |
|
||||||
|
|
||||||
> 名字来源于:《战神》游戏以希腊神话为背景,讲述奎托斯(Kratos)由凡人成为战神并展开弑神屠杀的冒险经历。 |
|
||||||
|
|
||||||
## Goals |
|
||||||
|
|
||||||
我们致力于提供完整的微服务研发体验,整合相关框架及工具后,微服务治理相关部分可对整体业务开发周期无感,从而更加聚焦于业务交付。对每位开发者而言,整套 Kratos 框架也是不错的学习仓库,可以了解和参考到微服务方面的技术积累和经验。 |
|
||||||
|
|
||||||
### Principles |
|
||||||
|
|
||||||
* 简单:不过度设计,代码平实简单; |
|
||||||
* 通用:通用业务开发所需要的基础库的功能; |
|
||||||
* 高效:提高业务迭代的效率; |
|
||||||
* 稳定:基础库可测试性高,覆盖率高,有线上实践安全可靠; |
|
||||||
* 健壮:通过良好的基础库设计,减少错用; |
|
||||||
* 高性能:性能高,但不特定为了性能做 hack 优化,引入 unsafe ; |
|
||||||
* 扩展性:良好的接口设计,来扩展实现,或者通过新增基础库目录来扩展功能; |
|
||||||
* 容错性:为失败设计,大量引入对 SRE 的理解,鲁棒性高; |
|
||||||
* 工具链:包含大量工具链,比如 cache 代码生成,lint 工具等等; |
|
||||||
|
|
||||||
## Features |
|
||||||
* [APIs](https://go-kratos.dev/docs/component/api) :协议通信以 HTTP/gRPC 为基础,通过 Protobuf 进行定义; |
|
||||||
* [Errors](https://go-kratos.dev/docs/component/errors/) :通过 Protobuf 的 Enum 作为错误码定义,以及工具生成判定接口; |
|
||||||
* [Metadata](https://go-kratos.dev/docs/component/metadata) :在协议通信 HTTP/gRPC 中,通过 Middleware 规范化服务元信息传递; |
|
||||||
* [Config](https://go-kratos.dev/docs/component/config) :支持多数据源方式,进行配置合并铺平,通过 Atomic 方式支持动态配置; |
|
||||||
* [Logger](https://go-kratos.dev/docs/component/log) :标准日志接口,可方便集成三方 log 库,并可通过 fluentd 收集日志; |
|
||||||
* [Metrics](https://go-kratos.dev/docs/component/middleware/metrics) :统一指标接口,可以实现各种指标系统,默认集成 Prometheus; |
|
||||||
* [Tracing](https://go-kratos.dev/docs/component/middleware/tracing) :遵循 OpenTelemetry 规范定义,以实现微服务链路追踪; |
|
||||||
* [Encoding](https://go-kratos.dev/docs/component/encoding) :支持 Accept 和 Content-Type 进行自动选择内容编码; |
|
||||||
* [Transport](https://go-kratos.dev/docs/component/transport/overview) :通用的 [HTTP](https://go-kratos.dev/docs/component/transport/http) /[gRPC](https://go-kratos.dev/docs/component/transport/grpc) 传输层,实现统一的 [Middleware](https://go-kratos.dev/docs/component/middleware/overview) 插件支持; |
|
||||||
* [Registry](https://go-kratos.dev/docs/component/registry) :实现统一注册中心接口,可插件化对接各种注册中心; |
|
||||||
* [Validation](https://go-kratos.dev/docs/component/middleware/validate): 通过Protobuf统一定义校验规则,并同时适用于HTTP/gRPC服务. |
|
||||||
* [SwaggerAPI](https://go-kratos.dev/docs/guide/openapi): 通过集成第三方[Swagger插件](https://github.com/go-kratos/swagger-api) 能够自动生成Swagger API json并启动一个内置的Swagger UI服务. |
|
||||||
|
|
||||||
## Getting Started |
|
||||||
### Required |
|
||||||
- [go](https://golang.org/dl/) |
|
||||||
- [protoc](https://github.com/protocolbuffers/protobuf) |
|
||||||
- [protoc-gen-go](https://github.com/protocolbuffers/protobuf-go) |
|
||||||
|
|
||||||
### Installing |
|
||||||
##### go install 安装: |
|
||||||
``` |
|
||||||
go install github.com/go-kratos/kratos/cmd/kratos/v2@latest |
|
||||||
kratos upgrade |
|
||||||
``` |
|
||||||
##### 源码编译安装: |
|
||||||
``` |
|
||||||
git clone https://github.com/go-kratos/kratos |
|
||||||
cd kratos |
|
||||||
make install |
|
||||||
``` |
|
||||||
|
|
||||||
### Create a service |
|
||||||
``` |
|
||||||
# 创建项目模板 |
|
||||||
kratos new helloworld |
|
||||||
|
|
||||||
cd helloworld |
|
||||||
# 拉取项目依赖 |
|
||||||
go mod download |
|
||||||
|
|
||||||
# 生成proto模板 |
|
||||||
kratos proto add api/helloworld/helloworld.proto |
|
||||||
# 生成proto源码 |
|
||||||
kratos proto client api/helloworld/helloworld.proto |
|
||||||
# 生成server模板 |
|
||||||
kratos proto server api/helloworld/helloworld.proto -t internal/service |
|
||||||
|
|
||||||
# 生成所有proto源码、wire等等 |
|
||||||
go generate ./... |
|
||||||
|
|
||||||
# 运行程序 |
|
||||||
kratos run |
|
||||||
``` |
|
||||||
|
|
||||||
### Kratos Boot |
|
||||||
``` |
|
||||||
import "github.com/go-kratos/kratos/v2" |
|
||||||
import "github.com/go-kratos/kratos/v2/transport/grpc" |
|
||||||
import "github.com/go-kratos/kratos/v2/transport/http" |
|
||||||
|
|
||||||
httpSrv := http.NewServer(http.Address(":8000")) |
|
||||||
grpcSrv := grpc.NewServer(grpc.Address(":9000")) |
|
||||||
|
|
||||||
app := kratos.New( |
|
||||||
kratos.Name("kratos"), |
|
||||||
kratos.Version("latest"), |
|
||||||
kratos.Server(httpSrv, grpcSrv), |
|
||||||
) |
|
||||||
app.Run() |
|
||||||
``` |
|
||||||
|
|
||||||
## Related |
|
||||||
|
|
||||||
* [Docs](https://go-kratos.dev/) |
|
||||||
* [Examples](https://github.com/go-kratos/examples) |
|
||||||
* [Service Layout](https://github.com/go-kratos/kratos-layout) |
|
||||||
|
|
||||||
## Community |
|
||||||
* [Wechat Group](https://github.com/go-kratos/kratos/issues/682) |
|
||||||
* [Discord Group](https://discord.gg/BWzJsUJ) |
|
||||||
* Website: [go-kratos.dev](https://go-kratos.dev) |
|
||||||
* QQ Group: 716486124 |
|
||||||
|
|
||||||
## WeChat Official Account |
|
||||||
![kratos](docs/images/wechat.png) |
|
||||||
|
|
||||||
## Conventional commits |
|
||||||
提交信息的结构应该如下所示: |
|
||||||
```text |
|
||||||
<type>[optional scope]: <description> |
|
||||||
|
|
||||||
[optional body] |
|
||||||
|
|
||||||
[optional footer(s)] |
|
||||||
``` |
|
||||||
|
|
||||||
提交信息应按照下面的格式: |
|
||||||
- fix: simply describe the problem that has been fixed |
|
||||||
- feat(log): simple describe of new features |
|
||||||
- deps(examples): simple describe the change of the dependency |
|
||||||
- break(http): simple describe the reasons for breaking change |
|
||||||
|
|
||||||
## Sponsors and Backers |
|
||||||
|
|
||||||
![kratos](docs/images/alipay.png) |
|
||||||
|
|
||||||
## License |
|
||||||
Kratos is MIT licensed. See the [LICENSE](./LICENSE) file for details. |
|
@ -1,82 +0,0 @@ |
|||||||
# Kratos |
|
||||||
|
|
||||||
This document defines the roadmap for Kratos development. |
|
||||||
|
|
||||||
## Features |
|
||||||
- [x] Config |
|
||||||
- [x] Local Files |
|
||||||
- [x] K8s ConfigMap |
|
||||||
- [x] Consul |
|
||||||
- [x] Etcd |
|
||||||
- [x] Nacos |
|
||||||
- [x] Registry |
|
||||||
- [x] Consul |
|
||||||
- [x] Etcd |
|
||||||
- [x] K8s |
|
||||||
- [x] Nacos |
|
||||||
- [x] Encoding |
|
||||||
- [x] JSON |
|
||||||
- [x] Protobuf |
|
||||||
- [x] Transport |
|
||||||
- [x] HTTP |
|
||||||
- [x] gRPC |
|
||||||
- [x] Middleware |
|
||||||
- [x] Logging |
|
||||||
- [x] metrics |
|
||||||
- [x] recovery |
|
||||||
- [x] gRPC status |
|
||||||
- [x] transport tracing |
|
||||||
- [x] Validator |
|
||||||
- [x] Authentication |
|
||||||
- [x] Ratelimit |
|
||||||
- [x] CircuitBreaker |
|
||||||
- [x] Metrics |
|
||||||
- [x] Prometheus |
|
||||||
- [x] DataDog |
|
||||||
- [x] Tracing |
|
||||||
- [x] HTTP |
|
||||||
- [x] TLS |
|
||||||
- [x] Client |
|
||||||
- [x] Service Registrar |
|
||||||
- [ ] javascript/typescript clients |
|
||||||
- [x] gRPC |
|
||||||
- [x] TLS |
|
||||||
- [x] Uarry Handler |
|
||||||
- [x] Streaming Handler |
|
||||||
- [ ] Cache |
|
||||||
- [ ] go-redis |
|
||||||
- [x] Event |
|
||||||
- [x] Pub/Sub |
|
||||||
- [x] Kafka |
|
||||||
- [ ] Nats |
|
||||||
- [x] Database |
|
||||||
- [x] Ent |
|
||||||
- [ ] Gorm |
|
||||||
|
|
||||||
## Platform |
|
||||||
- [ ] Kratos API |
|
||||||
- [ ] Auth |
|
||||||
- [ ] Config |
|
||||||
- [ ] Registry |
|
||||||
- [ ] Events |
|
||||||
- [ ] Kratos Runtime |
|
||||||
- [ ] Secrets |
|
||||||
- [ ] Service-to-Service |
|
||||||
- [ ] Publish and Subscribe |
|
||||||
- [ ] Observability |
|
||||||
- [ ] Controllable |
|
||||||
- [ ] Kratos UI |
|
||||||
- [ ] Auth |
|
||||||
- [ ] Config |
|
||||||
- [ ] Services |
|
||||||
- [ ] Endpoints |
|
||||||
- [ ] Ratelimit |
|
||||||
- [ ] CircuitBreaker |
|
||||||
- [ ] FaultInjection |
|
||||||
- [ ] TrafficPolicy |
|
||||||
|
|
||||||
## Tools |
|
||||||
- [x] Kratos |
|
||||||
- [x] HTTP Generator |
|
||||||
- [ ] API YAML |
|
||||||
- [x] Errors Generator |
|
@ -1,19 +0,0 @@ |
|||||||
# Security Policy |
|
||||||
|
|
||||||
## Supported Versions |
|
||||||
|
|
||||||
Use this section to tell people about which versions of your project are |
|
||||||
currently being supported with security updates. |
|
||||||
|
|
||||||
| Version | Supported | |
|
||||||
|-------------|--------------------| |
|
||||||
| 2.0.rc1 | :white_check_mark: | |
|
||||||
| < 2.0.beta3 | :x: | |
|
||||||
|
|
||||||
## Reporting a Vulnerability |
|
||||||
|
|
||||||
Use this section to tell people how to report a vulnerability. |
|
||||||
|
|
||||||
Tell them where to go, how often they can expect to get an update on a |
|
||||||
reported vulnerability, what to expect if the vulnerability is accepted or |
|
||||||
declined, etc. |
|
@ -1 +0,0 @@ |
|||||||
# API proto |
|
@ -1,365 +0,0 @@ |
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.28.0
|
|
||||||
// protoc v3.19.4
|
|
||||||
// source: metadata/metadata.proto
|
|
||||||
|
|
||||||
package metadata |
|
||||||
|
|
||||||
import ( |
|
||||||
_ "google.golang.org/genproto/googleapis/api/annotations" |
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect" |
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl" |
|
||||||
descriptorpb "google.golang.org/protobuf/types/descriptorpb" |
|
||||||
reflect "reflect" |
|
||||||
sync "sync" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
// Verify that this generated code is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) |
|
||||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) |
|
||||||
) |
|
||||||
|
|
||||||
type ListServicesRequest struct { |
|
||||||
state protoimpl.MessageState |
|
||||||
sizeCache protoimpl.SizeCache |
|
||||||
unknownFields protoimpl.UnknownFields |
|
||||||
} |
|
||||||
|
|
||||||
func (x *ListServicesRequest) Reset() { |
|
||||||
*x = ListServicesRequest{} |
|
||||||
if protoimpl.UnsafeEnabled { |
|
||||||
mi := &file_metadata_metadata_proto_msgTypes[0] |
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |
|
||||||
ms.StoreMessageInfo(mi) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (x *ListServicesRequest) String() string { |
|
||||||
return protoimpl.X.MessageStringOf(x) |
|
||||||
} |
|
||||||
|
|
||||||
func (*ListServicesRequest) ProtoMessage() {} |
|
||||||
|
|
||||||
func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { |
|
||||||
mi := &file_metadata_metadata_proto_msgTypes[0] |
|
||||||
if protoimpl.UnsafeEnabled && x != nil { |
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |
|
||||||
if ms.LoadMessageInfo() == nil { |
|
||||||
ms.StoreMessageInfo(mi) |
|
||||||
} |
|
||||||
return ms |
|
||||||
} |
|
||||||
return mi.MessageOf(x) |
|
||||||
} |
|
||||||
|
|
||||||
// Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*ListServicesRequest) Descriptor() ([]byte, []int) { |
|
||||||
return file_metadata_metadata_proto_rawDescGZIP(), []int{0} |
|
||||||
} |
|
||||||
|
|
||||||
type ListServicesReply struct { |
|
||||||
state protoimpl.MessageState |
|
||||||
sizeCache protoimpl.SizeCache |
|
||||||
unknownFields protoimpl.UnknownFields |
|
||||||
|
|
||||||
Services []string `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` |
|
||||||
Methods []string `protobuf:"bytes,2,rep,name=methods,proto3" json:"methods,omitempty"` |
|
||||||
} |
|
||||||
|
|
||||||
func (x *ListServicesReply) Reset() { |
|
||||||
*x = ListServicesReply{} |
|
||||||
if protoimpl.UnsafeEnabled { |
|
||||||
mi := &file_metadata_metadata_proto_msgTypes[1] |
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |
|
||||||
ms.StoreMessageInfo(mi) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (x *ListServicesReply) String() string { |
|
||||||
return protoimpl.X.MessageStringOf(x) |
|
||||||
} |
|
||||||
|
|
||||||
func (*ListServicesReply) ProtoMessage() {} |
|
||||||
|
|
||||||
func (x *ListServicesReply) ProtoReflect() protoreflect.Message { |
|
||||||
mi := &file_metadata_metadata_proto_msgTypes[1] |
|
||||||
if protoimpl.UnsafeEnabled && x != nil { |
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |
|
||||||
if ms.LoadMessageInfo() == nil { |
|
||||||
ms.StoreMessageInfo(mi) |
|
||||||
} |
|
||||||
return ms |
|
||||||
} |
|
||||||
return mi.MessageOf(x) |
|
||||||
} |
|
||||||
|
|
||||||
// Deprecated: Use ListServicesReply.ProtoReflect.Descriptor instead.
|
|
||||||
func (*ListServicesReply) Descriptor() ([]byte, []int) { |
|
||||||
return file_metadata_metadata_proto_rawDescGZIP(), []int{1} |
|
||||||
} |
|
||||||
|
|
||||||
func (x *ListServicesReply) GetServices() []string { |
|
||||||
if x != nil { |
|
||||||
return x.Services |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (x *ListServicesReply) GetMethods() []string { |
|
||||||
if x != nil { |
|
||||||
return x.Methods |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
type GetServiceDescRequest struct { |
|
||||||
state protoimpl.MessageState |
|
||||||
sizeCache protoimpl.SizeCache |
|
||||||
unknownFields protoimpl.UnknownFields |
|
||||||
|
|
||||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` |
|
||||||
} |
|
||||||
|
|
||||||
func (x *GetServiceDescRequest) Reset() { |
|
||||||
*x = GetServiceDescRequest{} |
|
||||||
if protoimpl.UnsafeEnabled { |
|
||||||
mi := &file_metadata_metadata_proto_msgTypes[2] |
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |
|
||||||
ms.StoreMessageInfo(mi) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (x *GetServiceDescRequest) String() string { |
|
||||||
return protoimpl.X.MessageStringOf(x) |
|
||||||
} |
|
||||||
|
|
||||||
func (*GetServiceDescRequest) ProtoMessage() {} |
|
||||||
|
|
||||||
func (x *GetServiceDescRequest) ProtoReflect() protoreflect.Message { |
|
||||||
mi := &file_metadata_metadata_proto_msgTypes[2] |
|
||||||
if protoimpl.UnsafeEnabled && x != nil { |
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |
|
||||||
if ms.LoadMessageInfo() == nil { |
|
||||||
ms.StoreMessageInfo(mi) |
|
||||||
} |
|
||||||
return ms |
|
||||||
} |
|
||||||
return mi.MessageOf(x) |
|
||||||
} |
|
||||||
|
|
||||||
// Deprecated: Use GetServiceDescRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*GetServiceDescRequest) Descriptor() ([]byte, []int) { |
|
||||||
return file_metadata_metadata_proto_rawDescGZIP(), []int{2} |
|
||||||
} |
|
||||||
|
|
||||||
func (x *GetServiceDescRequest) GetName() string { |
|
||||||
if x != nil { |
|
||||||
return x.Name |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
type GetServiceDescReply struct { |
|
||||||
state protoimpl.MessageState |
|
||||||
sizeCache protoimpl.SizeCache |
|
||||||
unknownFields protoimpl.UnknownFields |
|
||||||
|
|
||||||
FileDescSet *descriptorpb.FileDescriptorSet `protobuf:"bytes,1,opt,name=file_desc_set,json=fileDescSet,proto3" json:"file_desc_set,omitempty"` |
|
||||||
} |
|
||||||
|
|
||||||
func (x *GetServiceDescReply) Reset() { |
|
||||||
*x = GetServiceDescReply{} |
|
||||||
if protoimpl.UnsafeEnabled { |
|
||||||
mi := &file_metadata_metadata_proto_msgTypes[3] |
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |
|
||||||
ms.StoreMessageInfo(mi) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (x *GetServiceDescReply) String() string { |
|
||||||
return protoimpl.X.MessageStringOf(x) |
|
||||||
} |
|
||||||
|
|
||||||
func (*GetServiceDescReply) ProtoMessage() {} |
|
||||||
|
|
||||||
func (x *GetServiceDescReply) ProtoReflect() protoreflect.Message { |
|
||||||
mi := &file_metadata_metadata_proto_msgTypes[3] |
|
||||||
if protoimpl.UnsafeEnabled && x != nil { |
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |
|
||||||
if ms.LoadMessageInfo() == nil { |
|
||||||
ms.StoreMessageInfo(mi) |
|
||||||
} |
|
||||||
return ms |
|
||||||
} |
|
||||||
return mi.MessageOf(x) |
|
||||||
} |
|
||||||
|
|
||||||
// Deprecated: Use GetServiceDescReply.ProtoReflect.Descriptor instead.
|
|
||||||
func (*GetServiceDescReply) Descriptor() ([]byte, []int) { |
|
||||||
return file_metadata_metadata_proto_rawDescGZIP(), []int{3} |
|
||||||
} |
|
||||||
|
|
||||||
func (x *GetServiceDescReply) GetFileDescSet() *descriptorpb.FileDescriptorSet { |
|
||||||
if x != nil { |
|
||||||
return x.FileDescSet |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
var File_metadata_metadata_proto protoreflect.FileDescriptor |
|
||||||
|
|
||||||
var file_metadata_metadata_proto_rawDesc = []byte{ |
|
||||||
0x0a, 0x17, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x64, |
|
||||||
0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x6b, 0x72, 0x61, 0x74, 0x6f, |
|
||||||
0x73, 0x2e, 0x61, 0x70, 0x69, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, |
|
||||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, |
|
||||||
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, |
|
||||||
0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, |
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, |
|
||||||
0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x49, 0x0a, 0x11, |
|
||||||
0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, |
|
||||||
0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, |
|
||||||
0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, |
|
||||||
0x07, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, |
|
||||||
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0x2b, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x53, 0x65, |
|
||||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, |
|
||||||
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, |
|
||||||
0x6e, 0x61, 0x6d, 0x65, 0x22, 0x5d, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, |
|
||||||
0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x46, 0x0a, 0x0d, 0x66, |
|
||||||
0x69, 0x6c, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, |
|
||||||
0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, |
|
||||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, |
|
||||||
0x74, 0x6f, 0x72, 0x53, 0x65, 0x74, 0x52, 0x0b, 0x66, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, |
|
||||||
0x53, 0x65, 0x74, 0x32, 0xdd, 0x01, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, |
|
||||||
0x12, 0x61, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, |
|
||||||
0x12, 0x1f, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, |
|
||||||
0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, |
|
||||||
0x74, 0x1a, 0x1d, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, |
|
||||||
0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, |
|
||||||
0x22, 0x11, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x12, 0x09, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, |
|
||||||
0x63, 0x65, 0x73, 0x12, 0x6e, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, |
|
||||||
0x65, 0x44, 0x65, 0x73, 0x63, 0x12, 0x21, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, |
|
||||||
0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, |
|
||||||
0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, |
|
||||||
0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, |
|
||||||
0x44, 0x65, 0x73, 0x63, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, |
|
||||||
0x12, 0x12, 0x10, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x6e, 0x61, |
|
||||||
0x6d, 0x65, 0x7d, 0x42, 0x63, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, |
|
||||||
0x62, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x50, 0x01, 0x5a, 0x3c, |
|
||||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, |
|
||||||
0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x61, |
|
||||||
0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, |
|
||||||
0x61, 0x70, 0x69, 0x3b, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xa2, 0x02, 0x09, 0x4b, |
|
||||||
0x72, 0x61, 0x74, 0x6f, 0x73, 0x41, 0x50, 0x49, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, |
|
||||||
} |
|
||||||
|
|
||||||
var ( |
|
||||||
file_metadata_metadata_proto_rawDescOnce sync.Once |
|
||||||
file_metadata_metadata_proto_rawDescData = file_metadata_metadata_proto_rawDesc |
|
||||||
) |
|
||||||
|
|
||||||
func file_metadata_metadata_proto_rawDescGZIP() []byte { |
|
||||||
file_metadata_metadata_proto_rawDescOnce.Do(func() { |
|
||||||
file_metadata_metadata_proto_rawDescData = protoimpl.X.CompressGZIP(file_metadata_metadata_proto_rawDescData) |
|
||||||
}) |
|
||||||
return file_metadata_metadata_proto_rawDescData |
|
||||||
} |
|
||||||
|
|
||||||
var file_metadata_metadata_proto_msgTypes = make([]protoimpl.MessageInfo, 4) |
|
||||||
var file_metadata_metadata_proto_goTypes = []interface{}{ |
|
||||||
(*ListServicesRequest)(nil), // 0: kratos.api.ListServicesRequest
|
|
||||||
(*ListServicesReply)(nil), // 1: kratos.api.ListServicesReply
|
|
||||||
(*GetServiceDescRequest)(nil), // 2: kratos.api.GetServiceDescRequest
|
|
||||||
(*GetServiceDescReply)(nil), // 3: kratos.api.GetServiceDescReply
|
|
||||||
(*descriptorpb.FileDescriptorSet)(nil), // 4: google.protobuf.FileDescriptorSet
|
|
||||||
} |
|
||||||
var file_metadata_metadata_proto_depIdxs = []int32{ |
|
||||||
4, // 0: kratos.api.GetServiceDescReply.file_desc_set:type_name -> google.protobuf.FileDescriptorSet
|
|
||||||
0, // 1: kratos.api.Metadata.ListServices:input_type -> kratos.api.ListServicesRequest
|
|
||||||
2, // 2: kratos.api.Metadata.GetServiceDesc:input_type -> kratos.api.GetServiceDescRequest
|
|
||||||
1, // 3: kratos.api.Metadata.ListServices:output_type -> kratos.api.ListServicesReply
|
|
||||||
3, // 4: kratos.api.Metadata.GetServiceDesc:output_type -> kratos.api.GetServiceDescReply
|
|
||||||
3, // [3:5] is the sub-list for method output_type
|
|
||||||
1, // [1:3] is the sub-list for method input_type
|
|
||||||
1, // [1:1] is the sub-list for extension type_name
|
|
||||||
1, // [1:1] is the sub-list for extension extendee
|
|
||||||
0, // [0:1] is the sub-list for field type_name
|
|
||||||
} |
|
||||||
|
|
||||||
func init() { file_metadata_metadata_proto_init() } |
|
||||||
func file_metadata_metadata_proto_init() { |
|
||||||
if File_metadata_metadata_proto != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
if !protoimpl.UnsafeEnabled { |
|
||||||
file_metadata_metadata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { |
|
||||||
switch v := v.(*ListServicesRequest); i { |
|
||||||
case 0: |
|
||||||
return &v.state |
|
||||||
case 1: |
|
||||||
return &v.sizeCache |
|
||||||
case 2: |
|
||||||
return &v.unknownFields |
|
||||||
default: |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
file_metadata_metadata_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { |
|
||||||
switch v := v.(*ListServicesReply); i { |
|
||||||
case 0: |
|
||||||
return &v.state |
|
||||||
case 1: |
|
||||||
return &v.sizeCache |
|
||||||
case 2: |
|
||||||
return &v.unknownFields |
|
||||||
default: |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
file_metadata_metadata_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { |
|
||||||
switch v := v.(*GetServiceDescRequest); i { |
|
||||||
case 0: |
|
||||||
return &v.state |
|
||||||
case 1: |
|
||||||
return &v.sizeCache |
|
||||||
case 2: |
|
||||||
return &v.unknownFields |
|
||||||
default: |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
file_metadata_metadata_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { |
|
||||||
switch v := v.(*GetServiceDescReply); i { |
|
||||||
case 0: |
|
||||||
return &v.state |
|
||||||
case 1: |
|
||||||
return &v.sizeCache |
|
||||||
case 2: |
|
||||||
return &v.unknownFields |
|
||||||
default: |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
type x struct{} |
|
||||||
out := protoimpl.TypeBuilder{ |
|
||||||
File: protoimpl.DescBuilder{ |
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), |
|
||||||
RawDescriptor: file_metadata_metadata_proto_rawDesc, |
|
||||||
NumEnums: 0, |
|
||||||
NumMessages: 4, |
|
||||||
NumExtensions: 0, |
|
||||||
NumServices: 1, |
|
||||||
}, |
|
||||||
GoTypes: file_metadata_metadata_proto_goTypes, |
|
||||||
DependencyIndexes: file_metadata_metadata_proto_depIdxs, |
|
||||||
MessageInfos: file_metadata_metadata_proto_msgTypes, |
|
||||||
}.Build() |
|
||||||
File_metadata_metadata_proto = out.File |
|
||||||
file_metadata_metadata_proto_rawDesc = nil |
|
||||||
file_metadata_metadata_proto_goTypes = nil |
|
||||||
file_metadata_metadata_proto_depIdxs = nil |
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
syntax = "proto3"; |
|
||||||
|
|
||||||
package kratos.api; |
|
||||||
|
|
||||||
import "google/protobuf/descriptor.proto"; |
|
||||||
import "google/api/annotations.proto"; |
|
||||||
|
|
||||||
option go_package = "github.com/go-kratos/kratos/v2/api/proto/kratos/api;metadata"; |
|
||||||
option java_multiple_files = true; |
|
||||||
option java_package = "com.github.kratos.api"; |
|
||||||
option objc_class_prefix = "KratosAPI"; |
|
||||||
|
|
||||||
|
|
||||||
// Metadata is api definition metadata service. |
|
||||||
service Metadata { |
|
||||||
// ListServices list the full name of all services. |
|
||||||
rpc ListServices (ListServicesRequest) returns (ListServicesReply) { |
|
||||||
option (google.api.http) = { |
|
||||||
get: "/services", |
|
||||||
}; |
|
||||||
} |
|
||||||
// GetServiceDesc get the full fileDescriptorSet of service. |
|
||||||
rpc GetServiceDesc (GetServiceDescRequest) returns (GetServiceDescReply) { |
|
||||||
option (google.api.http) = { |
|
||||||
get: "/services/{name}", |
|
||||||
}; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
message ListServicesRequest {} |
|
||||||
message ListServicesReply { |
|
||||||
repeated string services = 1; |
|
||||||
repeated string methods = 2; |
|
||||||
} |
|
||||||
|
|
||||||
message GetServiceDescRequest { |
|
||||||
string name = 1; |
|
||||||
} |
|
||||||
|
|
||||||
message GetServiceDescReply { |
|
||||||
google.protobuf.FileDescriptorSet file_desc_set = 1; |
|
||||||
} |
|
||||||
|
|
@ -1,145 +0,0 @@ |
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// - protoc-gen-go-grpc v1.2.0
|
|
||||||
// - protoc v3.19.4
|
|
||||||
// source: metadata/metadata.proto
|
|
||||||
|
|
||||||
package metadata |
|
||||||
|
|
||||||
import ( |
|
||||||
context "context" |
|
||||||
grpc "google.golang.org/grpc" |
|
||||||
codes "google.golang.org/grpc/codes" |
|
||||||
status "google.golang.org/grpc/status" |
|
||||||
) |
|
||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
|
||||||
// is compatible with the grpc package it is being compiled against.
|
|
||||||
// Requires gRPC-Go v1.32.0 or later.
|
|
||||||
const _ = grpc.SupportPackageIsVersion7 |
|
||||||
|
|
||||||
// MetadataClient is the client API for Metadata service.
|
|
||||||
//
|
|
||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
|
||||||
type MetadataClient interface { |
|
||||||
// ListServices list the full name of all services.
|
|
||||||
ListServices(ctx context.Context, in *ListServicesRequest, opts ...grpc.CallOption) (*ListServicesReply, error) |
|
||||||
// GetServiceDesc get the full fileDescriptorSet of service.
|
|
||||||
GetServiceDesc(ctx context.Context, in *GetServiceDescRequest, opts ...grpc.CallOption) (*GetServiceDescReply, error) |
|
||||||
} |
|
||||||
|
|
||||||
type metadataClient struct { |
|
||||||
cc grpc.ClientConnInterface |
|
||||||
} |
|
||||||
|
|
||||||
func NewMetadataClient(cc grpc.ClientConnInterface) MetadataClient { |
|
||||||
return &metadataClient{cc} |
|
||||||
} |
|
||||||
|
|
||||||
func (c *metadataClient) ListServices(ctx context.Context, in *ListServicesRequest, opts ...grpc.CallOption) (*ListServicesReply, error) { |
|
||||||
out := new(ListServicesReply) |
|
||||||
err := c.cc.Invoke(ctx, "/kratos.api.Metadata/ListServices", in, out, opts...) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return out, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (c *metadataClient) GetServiceDesc(ctx context.Context, in *GetServiceDescRequest, opts ...grpc.CallOption) (*GetServiceDescReply, error) { |
|
||||||
out := new(GetServiceDescReply) |
|
||||||
err := c.cc.Invoke(ctx, "/kratos.api.Metadata/GetServiceDesc", in, out, opts...) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return out, nil |
|
||||||
} |
|
||||||
|
|
||||||
// MetadataServer is the server API for Metadata service.
|
|
||||||
// All implementations must embed UnimplementedMetadataServer
|
|
||||||
// for forward compatibility
|
|
||||||
type MetadataServer interface { |
|
||||||
// ListServices list the full name of all services.
|
|
||||||
ListServices(context.Context, *ListServicesRequest) (*ListServicesReply, error) |
|
||||||
// GetServiceDesc get the full fileDescriptorSet of service.
|
|
||||||
GetServiceDesc(context.Context, *GetServiceDescRequest) (*GetServiceDescReply, error) |
|
||||||
mustEmbedUnimplementedMetadataServer() |
|
||||||
} |
|
||||||
|
|
||||||
// UnimplementedMetadataServer must be embedded to have forward compatible implementations.
|
|
||||||
type UnimplementedMetadataServer struct { |
|
||||||
} |
|
||||||
|
|
||||||
func (UnimplementedMetadataServer) ListServices(context.Context, *ListServicesRequest) (*ListServicesReply, error) { |
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListServices not implemented") |
|
||||||
} |
|
||||||
func (UnimplementedMetadataServer) GetServiceDesc(context.Context, *GetServiceDescRequest) (*GetServiceDescReply, error) { |
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetServiceDesc not implemented") |
|
||||||
} |
|
||||||
func (UnimplementedMetadataServer) mustEmbedUnimplementedMetadataServer() {} |
|
||||||
|
|
||||||
// UnsafeMetadataServer may be embedded to opt out of forward compatibility for this service.
|
|
||||||
// Use of this interface is not recommended, as added methods to MetadataServer will
|
|
||||||
// result in compilation errors.
|
|
||||||
type UnsafeMetadataServer interface { |
|
||||||
mustEmbedUnimplementedMetadataServer() |
|
||||||
} |
|
||||||
|
|
||||||
func RegisterMetadataServer(s grpc.ServiceRegistrar, srv MetadataServer) { |
|
||||||
s.RegisterService(&Metadata_ServiceDesc, srv) |
|
||||||
} |
|
||||||
|
|
||||||
func _Metadata_ListServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { |
|
||||||
in := new(ListServicesRequest) |
|
||||||
if err := dec(in); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if interceptor == nil { |
|
||||||
return srv.(MetadataServer).ListServices(ctx, in) |
|
||||||
} |
|
||||||
info := &grpc.UnaryServerInfo{ |
|
||||||
Server: srv, |
|
||||||
FullMethod: "/kratos.api.Metadata/ListServices", |
|
||||||
} |
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) { |
|
||||||
return srv.(MetadataServer).ListServices(ctx, req.(*ListServicesRequest)) |
|
||||||
} |
|
||||||
return interceptor(ctx, in, info, handler) |
|
||||||
} |
|
||||||
|
|
||||||
func _Metadata_GetServiceDesc_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { |
|
||||||
in := new(GetServiceDescRequest) |
|
||||||
if err := dec(in); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if interceptor == nil { |
|
||||||
return srv.(MetadataServer).GetServiceDesc(ctx, in) |
|
||||||
} |
|
||||||
info := &grpc.UnaryServerInfo{ |
|
||||||
Server: srv, |
|
||||||
FullMethod: "/kratos.api.Metadata/GetServiceDesc", |
|
||||||
} |
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) { |
|
||||||
return srv.(MetadataServer).GetServiceDesc(ctx, req.(*GetServiceDescRequest)) |
|
||||||
} |
|
||||||
return interceptor(ctx, in, info, handler) |
|
||||||
} |
|
||||||
|
|
||||||
// Metadata_ServiceDesc is the grpc.ServiceDesc for Metadata service.
|
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
|
||||||
// and not to be introspected or modified (even as a copy)
|
|
||||||
var Metadata_ServiceDesc = grpc.ServiceDesc{ |
|
||||||
ServiceName: "kratos.api.Metadata", |
|
||||||
HandlerType: (*MetadataServer)(nil), |
|
||||||
Methods: []grpc.MethodDesc{ |
|
||||||
{ |
|
||||||
MethodName: "ListServices", |
|
||||||
Handler: _Metadata_ListServices_Handler, |
|
||||||
}, |
|
||||||
{ |
|
||||||
MethodName: "GetServiceDesc", |
|
||||||
Handler: _Metadata_GetServiceDesc_Handler, |
|
||||||
}, |
|
||||||
}, |
|
||||||
Streams: []grpc.StreamDesc{}, |
|
||||||
Metadata: "metadata/metadata.proto", |
|
||||||
} |
|
@ -1,109 +0,0 @@ |
|||||||
// Code generated by protoc-gen-go-http. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go-http v2.3.0
|
|
||||||
|
|
||||||
package metadata |
|
||||||
|
|
||||||
import ( |
|
||||||
context "context" |
|
||||||
http "github.com/go-kratos/kratos/v2/transport/http" |
|
||||||
binding "github.com/go-kratos/kratos/v2/transport/http/binding" |
|
||||||
) |
|
||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
|
||||||
// is compatible with the kratos package it is being compiled against.
|
|
||||||
var _ = new(context.Context) |
|
||||||
var _ = binding.EncodeURL |
|
||||||
|
|
||||||
const _ = http.SupportPackageIsVersion1 |
|
||||||
|
|
||||||
type MetadataHTTPServer interface { |
|
||||||
GetServiceDesc(context.Context, *GetServiceDescRequest) (*GetServiceDescReply, error) |
|
||||||
ListServices(context.Context, *ListServicesRequest) (*ListServicesReply, error) |
|
||||||
} |
|
||||||
|
|
||||||
func RegisterMetadataHTTPServer(s *http.Server, srv MetadataHTTPServer) { |
|
||||||
r := s.Route("/") |
|
||||||
r.GET("/services", _Metadata_ListServices0_HTTP_Handler(srv)) |
|
||||||
r.GET("/services/{name}", _Metadata_GetServiceDesc0_HTTP_Handler(srv)) |
|
||||||
} |
|
||||||
|
|
||||||
func _Metadata_ListServices0_HTTP_Handler(srv MetadataHTTPServer) func(ctx http.Context) error { |
|
||||||
return func(ctx http.Context) error { |
|
||||||
var in ListServicesRequest |
|
||||||
if err := ctx.BindQuery(&in); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
http.SetOperation(ctx, "/kratos.api.Metadata/ListServices") |
|
||||||
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { |
|
||||||
return srv.ListServices(ctx, req.(*ListServicesRequest)) |
|
||||||
}) |
|
||||||
out, err := h(ctx, &in) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
reply := out.(*ListServicesReply) |
|
||||||
return ctx.Result(200, reply) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func _Metadata_GetServiceDesc0_HTTP_Handler(srv MetadataHTTPServer) func(ctx http.Context) error { |
|
||||||
return func(ctx http.Context) error { |
|
||||||
var in GetServiceDescRequest |
|
||||||
if err := ctx.BindQuery(&in); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
if err := ctx.BindVars(&in); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
http.SetOperation(ctx, "/kratos.api.Metadata/GetServiceDesc") |
|
||||||
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { |
|
||||||
return srv.GetServiceDesc(ctx, req.(*GetServiceDescRequest)) |
|
||||||
}) |
|
||||||
out, err := h(ctx, &in) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
reply := out.(*GetServiceDescReply) |
|
||||||
return ctx.Result(200, reply) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
type MetadataHTTPClient interface { |
|
||||||
GetServiceDesc(ctx context.Context, req *GetServiceDescRequest, opts ...http.CallOption) (rsp *GetServiceDescReply, err error) |
|
||||||
ListServices(ctx context.Context, req *ListServicesRequest, opts ...http.CallOption) (rsp *ListServicesReply, err error) |
|
||||||
} |
|
||||||
|
|
||||||
type MetadataHTTPClientImpl struct { |
|
||||||
cc *http.Client |
|
||||||
} |
|
||||||
|
|
||||||
func NewMetadataHTTPClient(client *http.Client) MetadataHTTPClient { |
|
||||||
return &MetadataHTTPClientImpl{client} |
|
||||||
} |
|
||||||
|
|
||||||
func (c *MetadataHTTPClientImpl) GetServiceDesc(ctx context.Context, in *GetServiceDescRequest, opts ...http.CallOption) (*GetServiceDescReply, error) { |
|
||||||
var out GetServiceDescReply |
|
||||||
pattern := "/services/{name}" |
|
||||||
path := binding.EncodeURL(pattern, in, true) |
|
||||||
opts = append(opts, http.Operation("/kratos.api.Metadata/GetServiceDesc")) |
|
||||||
opts = append(opts, http.PathTemplate(pattern)) |
|
||||||
err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return &out, err |
|
||||||
} |
|
||||||
|
|
||||||
func (c *MetadataHTTPClientImpl) ListServices(ctx context.Context, in *ListServicesRequest, opts ...http.CallOption) (*ListServicesReply, error) { |
|
||||||
var out ListServicesReply |
|
||||||
pattern := "/services" |
|
||||||
path := binding.EncodeURL(pattern, in, true) |
|
||||||
opts = append(opts, http.Operation("/kratos.api.Metadata/ListServices")) |
|
||||||
opts = append(opts, http.PathTemplate(pattern)) |
|
||||||
err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return &out, err |
|
||||||
} |
|
@ -1,206 +0,0 @@ |
|||||||
package metadata |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"compress/gzip" |
|
||||||
"context" |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"sort" |
|
||||||
"sync" |
|
||||||
|
|
||||||
"google.golang.org/grpc" |
|
||||||
"google.golang.org/grpc/codes" |
|
||||||
"google.golang.org/grpc/status" |
|
||||||
"google.golang.org/protobuf/proto" |
|
||||||
"google.golang.org/protobuf/reflect/protodesc" |
|
||||||
"google.golang.org/protobuf/reflect/protoreflect" |
|
||||||
"google.golang.org/protobuf/reflect/protoregistry" |
|
||||||
dpb "google.golang.org/protobuf/types/descriptorpb" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/log" |
|
||||||
) |
|
||||||
|
|
||||||
// Server is api meta server
|
|
||||||
type Server struct { |
|
||||||
UnimplementedMetadataServer |
|
||||||
|
|
||||||
srv *grpc.Server |
|
||||||
lock sync.Mutex |
|
||||||
services map[string]*dpb.FileDescriptorSet |
|
||||||
methods map[string][]string |
|
||||||
} |
|
||||||
|
|
||||||
// NewServer create server instance
|
|
||||||
func NewServer(srv *grpc.Server) *Server { |
|
||||||
return &Server{ |
|
||||||
srv: srv, |
|
||||||
services: make(map[string]*dpb.FileDescriptorSet), |
|
||||||
methods: make(map[string][]string), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Server) load() error { |
|
||||||
if len(s.services) > 0 { |
|
||||||
return nil |
|
||||||
} |
|
||||||
if s.srv != nil { |
|
||||||
for name, info := range s.srv.GetServiceInfo() { |
|
||||||
fd, err := parseMetadata(info.Metadata) |
|
||||||
if err != nil { |
|
||||||
return fmt.Errorf("invalid service %s metadata err:%v", name, err) |
|
||||||
} |
|
||||||
protoSet, err := allDependency(fd) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
s.services[name] = &dpb.FileDescriptorSet{File: protoSet} |
|
||||||
for _, method := range info.Methods { |
|
||||||
s.methods[name] = append(s.methods[name], method.Name) |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
var err error |
|
||||||
protoregistry.GlobalFiles.RangeFiles(func(fd protoreflect.FileDescriptor) bool { |
|
||||||
if fd.Services() == nil { |
|
||||||
return true |
|
||||||
} |
|
||||||
for i := 0; i < fd.Services().Len(); i++ { |
|
||||||
svc := fd.Services().Get(i) |
|
||||||
fdp, e := fileDescriptorProto(fd.Path()) |
|
||||||
if e != nil { |
|
||||||
err = e |
|
||||||
return false |
|
||||||
} |
|
||||||
fdps, e := allDependency(fdp) |
|
||||||
if e != nil { |
|
||||||
if errors.Is(e, protoregistry.NotFound) { |
|
||||||
// Skip this service if one of its dependencies is not found.
|
|
||||||
continue |
|
||||||
} |
|
||||||
err = e |
|
||||||
return false |
|
||||||
} |
|
||||||
s.services[string(svc.FullName())] = &dpb.FileDescriptorSet{File: fdps} |
|
||||||
if svc.Methods() == nil { |
|
||||||
continue |
|
||||||
} |
|
||||||
for j := 0; j < svc.Methods().Len(); j++ { |
|
||||||
method := svc.Methods().Get(j) |
|
||||||
s.methods[string(svc.FullName())] = append(s.methods[string(svc.FullName())], string(method.Name())) |
|
||||||
} |
|
||||||
} |
|
||||||
return true |
|
||||||
}) |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// ListServices return all services
|
|
||||||
func (s *Server) ListServices(_ context.Context, _ *ListServicesRequest) (*ListServicesReply, error) { |
|
||||||
s.lock.Lock() |
|
||||||
defer s.lock.Unlock() |
|
||||||
if err := s.load(); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
reply := &ListServicesReply{ |
|
||||||
Services: make([]string, 0, len(s.services)), |
|
||||||
Methods: make([]string, 0, len(s.methods)), |
|
||||||
} |
|
||||||
for name := range s.services { |
|
||||||
reply.Services = append(reply.Services, name) |
|
||||||
} |
|
||||||
for name, methods := range s.methods { |
|
||||||
for _, method := range methods { |
|
||||||
reply.Methods = append(reply.Methods, fmt.Sprintf("/%s/%s", name, method)) |
|
||||||
} |
|
||||||
} |
|
||||||
sort.Strings(reply.Services) |
|
||||||
sort.Strings(reply.Methods) |
|
||||||
return reply, nil |
|
||||||
} |
|
||||||
|
|
||||||
// GetServiceDesc return service meta by name
|
|
||||||
func (s *Server) GetServiceDesc(_ context.Context, in *GetServiceDescRequest) (*GetServiceDescReply, error) { |
|
||||||
s.lock.Lock() |
|
||||||
defer s.lock.Unlock() |
|
||||||
if err := s.load(); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
fds, ok := s.services[in.Name] |
|
||||||
if !ok { |
|
||||||
return nil, status.Errorf(codes.NotFound, "service %s not found", in.Name) |
|
||||||
} |
|
||||||
return &GetServiceDescReply{FileDescSet: fds}, nil |
|
||||||
} |
|
||||||
|
|
||||||
// parseMetadata finds the file descriptor bytes specified meta.
|
|
||||||
// For SupportPackageIsVersion4, m is the name of the proto file, we
|
|
||||||
// call proto.FileDescriptor to get the byte slice.
|
|
||||||
// For SupportPackageIsVersion3, m is a byte slice itself.
|
|
||||||
func parseMetadata(meta interface{}) (*dpb.FileDescriptorProto, error) { |
|
||||||
// Check if meta is the file name.
|
|
||||||
if fileNameForMeta, ok := meta.(string); ok { |
|
||||||
return fileDescriptorProto(fileNameForMeta) |
|
||||||
} |
|
||||||
// Check if meta is the byte slice.
|
|
||||||
if enc, ok := meta.([]byte); ok { |
|
||||||
return decodeFileDesc(enc) |
|
||||||
} |
|
||||||
return nil, fmt.Errorf("proto not sumpport metadata: %v", meta) |
|
||||||
} |
|
||||||
|
|
||||||
// decodeFileDesc does decompression and unmarshalling on the given
|
|
||||||
// file descriptor byte slice.
|
|
||||||
func decodeFileDesc(enc []byte) (*dpb.FileDescriptorProto, error) { |
|
||||||
raw, err := decompress(enc) |
|
||||||
if err != nil { |
|
||||||
return nil, fmt.Errorf("failed to decompress enc: %v", err) |
|
||||||
} |
|
||||||
fd := new(dpb.FileDescriptorProto) |
|
||||||
if err := proto.Unmarshal(raw, fd); err != nil { |
|
||||||
return nil, fmt.Errorf("bad descriptor: %v", err) |
|
||||||
} |
|
||||||
return fd, nil |
|
||||||
} |
|
||||||
|
|
||||||
func allDependency(fd *dpb.FileDescriptorProto) ([]*dpb.FileDescriptorProto, error) { |
|
||||||
var files []*dpb.FileDescriptorProto |
|
||||||
for _, dep := range fd.Dependency { |
|
||||||
fdDep, err := fileDescriptorProto(dep) |
|
||||||
if err != nil { |
|
||||||
log.Warnf("%s", err) |
|
||||||
continue |
|
||||||
} |
|
||||||
temp, err := allDependency(fdDep) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
files = append(files, temp...) |
|
||||||
} |
|
||||||
files = append(files, fd) |
|
||||||
return files, nil |
|
||||||
} |
|
||||||
|
|
||||||
// decompress does gzip decompression.
|
|
||||||
func decompress(b []byte) ([]byte, error) { |
|
||||||
r, err := gzip.NewReader(bytes.NewReader(b)) |
|
||||||
if err != nil { |
|
||||||
return nil, fmt.Errorf("bad gzipped descriptor: %v", err) |
|
||||||
} |
|
||||||
out, err := io.ReadAll(r) |
|
||||||
if err != nil { |
|
||||||
return nil, fmt.Errorf("bad gzipped descriptor: %v", err) |
|
||||||
} |
|
||||||
return out, nil |
|
||||||
} |
|
||||||
|
|
||||||
func fileDescriptorProto(path string) (*dpb.FileDescriptorProto, error) { |
|
||||||
fd, err := protoregistry.GlobalFiles.FindFileByPath(path) |
|
||||||
if err != nil { |
|
||||||
return nil, fmt.Errorf("find proto by path failed, path: %s, err: %s", path, err) |
|
||||||
} |
|
||||||
fdpb := protodesc.ToFileDescriptorProto(fd) |
|
||||||
return fdpb, nil |
|
||||||
} |
|
@ -1,207 +0,0 @@ |
|||||||
package kratos |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"errors" |
|
||||||
"os" |
|
||||||
"os/signal" |
|
||||||
"sync" |
|
||||||
"syscall" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/log" |
|
||||||
"github.com/go-kratos/kratos/v2/registry" |
|
||||||
"github.com/go-kratos/kratos/v2/transport" |
|
||||||
|
|
||||||
"github.com/google/uuid" |
|
||||||
"golang.org/x/sync/errgroup" |
|
||||||
) |
|
||||||
|
|
||||||
// AppInfo is application context value.
|
|
||||||
type AppInfo interface { |
|
||||||
ID() string |
|
||||||
Name() string |
|
||||||
Version() string |
|
||||||
Metadata() map[string]string |
|
||||||
Endpoint() []string |
|
||||||
} |
|
||||||
|
|
||||||
// App is an application components lifecycle manager.
|
|
||||||
type App struct { |
|
||||||
opts options |
|
||||||
ctx context.Context |
|
||||||
cancel func() |
|
||||||
mu sync.Mutex |
|
||||||
instance *registry.ServiceInstance |
|
||||||
} |
|
||||||
|
|
||||||
// New create an application lifecycle manager.
|
|
||||||
func New(opts ...Option) *App { |
|
||||||
o := options{ |
|
||||||
ctx: context.Background(), |
|
||||||
sigs: []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT}, |
|
||||||
registrarTimeout: 10 * time.Second, |
|
||||||
stopTimeout: 10 * time.Second, |
|
||||||
} |
|
||||||
if id, err := uuid.NewUUID(); err == nil { |
|
||||||
o.id = id.String() |
|
||||||
} |
|
||||||
for _, opt := range opts { |
|
||||||
opt(&o) |
|
||||||
} |
|
||||||
if o.logger != nil { |
|
||||||
log.SetLogger(o.logger) |
|
||||||
} |
|
||||||
ctx, cancel := context.WithCancel(o.ctx) |
|
||||||
return &App{ |
|
||||||
ctx: ctx, |
|
||||||
cancel: cancel, |
|
||||||
opts: o, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// ID returns app instance id.
|
|
||||||
func (a *App) ID() string { return a.opts.id } |
|
||||||
|
|
||||||
// Name returns service name.
|
|
||||||
func (a *App) Name() string { return a.opts.name } |
|
||||||
|
|
||||||
// Version returns app version.
|
|
||||||
func (a *App) Version() string { return a.opts.version } |
|
||||||
|
|
||||||
// Metadata returns service metadata.
|
|
||||||
func (a *App) Metadata() map[string]string { return a.opts.metadata } |
|
||||||
|
|
||||||
// Endpoint returns endpoints.
|
|
||||||
func (a *App) Endpoint() []string { |
|
||||||
if a.instance != nil { |
|
||||||
return a.instance.Endpoints |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Run executes all OnStart hooks registered with the application's Lifecycle.
|
|
||||||
func (a *App) Run() error { |
|
||||||
instance, err := a.buildInstance() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
a.mu.Lock() |
|
||||||
a.instance = instance |
|
||||||
a.mu.Unlock() |
|
||||||
sctx := NewContext(a.ctx, a) |
|
||||||
eg, ctx := errgroup.WithContext(sctx) |
|
||||||
wg := sync.WaitGroup{} |
|
||||||
|
|
||||||
for _, fn := range a.opts.beforeStart { |
|
||||||
if err = fn(sctx); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
for _, srv := range a.opts.servers { |
|
||||||
srv := srv |
|
||||||
eg.Go(func() error { |
|
||||||
<-ctx.Done() // wait for stop signal
|
|
||||||
stopCtx, cancel := context.WithTimeout(NewContext(a.opts.ctx, a), a.opts.stopTimeout) |
|
||||||
defer cancel() |
|
||||||
return srv.Stop(stopCtx) |
|
||||||
}) |
|
||||||
wg.Add(1) |
|
||||||
eg.Go(func() error { |
|
||||||
wg.Done() // here is to ensure server start has begun running before register, so defer is not needed
|
|
||||||
return srv.Start(sctx) |
|
||||||
}) |
|
||||||
} |
|
||||||
wg.Wait() |
|
||||||
if a.opts.registrar != nil { |
|
||||||
rctx, rcancel := context.WithTimeout(ctx, a.opts.registrarTimeout) |
|
||||||
defer rcancel() |
|
||||||
if err = a.opts.registrar.Register(rctx, instance); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
for _, fn := range a.opts.afterStart { |
|
||||||
if err = fn(sctx); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
c := make(chan os.Signal, 1) |
|
||||||
signal.Notify(c, a.opts.sigs...) |
|
||||||
eg.Go(func() error { |
|
||||||
select { |
|
||||||
case <-ctx.Done(): |
|
||||||
return nil |
|
||||||
case <-c: |
|
||||||
return a.Stop() |
|
||||||
} |
|
||||||
}) |
|
||||||
if err = eg.Wait(); err != nil && !errors.Is(err, context.Canceled) { |
|
||||||
return err |
|
||||||
} |
|
||||||
for _, fn := range a.opts.afterStop { |
|
||||||
err = fn(sctx) |
|
||||||
} |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// Stop gracefully stops the application.
|
|
||||||
func (a *App) Stop() (err error) { |
|
||||||
sctx := NewContext(a.ctx, a) |
|
||||||
for _, fn := range a.opts.beforeStop { |
|
||||||
err = fn(sctx) |
|
||||||
} |
|
||||||
|
|
||||||
a.mu.Lock() |
|
||||||
instance := a.instance |
|
||||||
a.mu.Unlock() |
|
||||||
if a.opts.registrar != nil && instance != nil { |
|
||||||
ctx, cancel := context.WithTimeout(NewContext(a.ctx, a), a.opts.registrarTimeout) |
|
||||||
defer cancel() |
|
||||||
if err = a.opts.registrar.Deregister(ctx, instance); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
if a.cancel != nil { |
|
||||||
a.cancel() |
|
||||||
} |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
func (a *App) buildInstance() (*registry.ServiceInstance, error) { |
|
||||||
endpoints := make([]string, 0, len(a.opts.endpoints)) |
|
||||||
for _, e := range a.opts.endpoints { |
|
||||||
endpoints = append(endpoints, e.String()) |
|
||||||
} |
|
||||||
if len(endpoints) == 0 { |
|
||||||
for _, srv := range a.opts.servers { |
|
||||||
if r, ok := srv.(transport.Endpointer); ok { |
|
||||||
e, err := r.Endpoint() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
endpoints = append(endpoints, e.String()) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return ®istry.ServiceInstance{ |
|
||||||
ID: a.opts.id, |
|
||||||
Name: a.opts.name, |
|
||||||
Version: a.opts.version, |
|
||||||
Metadata: a.opts.metadata, |
|
||||||
Endpoints: endpoints, |
|
||||||
}, nil |
|
||||||
} |
|
||||||
|
|
||||||
type appKey struct{} |
|
||||||
|
|
||||||
// NewContext returns a new Context that carries value.
|
|
||||||
func NewContext(ctx context.Context, s AppInfo) context.Context { |
|
||||||
return context.WithValue(ctx, appKey{}, s) |
|
||||||
} |
|
||||||
|
|
||||||
// FromContext returns the Transport value stored in ctx, if any.
|
|
||||||
func FromContext(ctx context.Context) (s AppInfo, ok bool) { |
|
||||||
s, ok = ctx.Value(appKey{}).(AppInfo) |
|
||||||
return |
|
||||||
} |
|
@ -1,285 +0,0 @@ |
|||||||
package kratos |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"errors" |
|
||||||
"net/url" |
|
||||||
"reflect" |
|
||||||
"sync" |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/registry" |
|
||||||
"github.com/go-kratos/kratos/v2/transport/grpc" |
|
||||||
"github.com/go-kratos/kratos/v2/transport/http" |
|
||||||
) |
|
||||||
|
|
||||||
type mockRegistry struct { |
|
||||||
lk sync.Mutex |
|
||||||
service map[string]*registry.ServiceInstance |
|
||||||
} |
|
||||||
|
|
||||||
func (r *mockRegistry) Register(_ context.Context, service *registry.ServiceInstance) error { |
|
||||||
if service == nil || service.ID == "" { |
|
||||||
return errors.New("no service id") |
|
||||||
} |
|
||||||
r.lk.Lock() |
|
||||||
defer r.lk.Unlock() |
|
||||||
r.service[service.ID] = service |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Deregister the registration.
|
|
||||||
func (r *mockRegistry) Deregister(_ context.Context, service *registry.ServiceInstance) error { |
|
||||||
r.lk.Lock() |
|
||||||
defer r.lk.Unlock() |
|
||||||
if r.service[service.ID] == nil { |
|
||||||
return errors.New("deregister service not found") |
|
||||||
} |
|
||||||
delete(r.service, service.ID) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func TestApp(t *testing.T) { |
|
||||||
hs := http.NewServer() |
|
||||||
gs := grpc.NewServer() |
|
||||||
app := New( |
|
||||||
Name("kratos"), |
|
||||||
Version("v1.0.0"), |
|
||||||
Server(hs, gs), |
|
||||||
BeforeStart(func(_ context.Context) error { |
|
||||||
t.Log("BeforeStart...") |
|
||||||
return nil |
|
||||||
}), |
|
||||||
BeforeStop(func(_ context.Context) error { |
|
||||||
t.Log("BeforeStop...") |
|
||||||
return nil |
|
||||||
}), |
|
||||||
AfterStart(func(_ context.Context) error { |
|
||||||
t.Log("AfterStart...") |
|
||||||
return nil |
|
||||||
}), |
|
||||||
AfterStop(func(_ context.Context) error { |
|
||||||
t.Log("AfterStop...") |
|
||||||
return nil |
|
||||||
}), |
|
||||||
Registrar(&mockRegistry{service: make(map[string]*registry.ServiceInstance)}), |
|
||||||
) |
|
||||||
time.AfterFunc(time.Second, func() { |
|
||||||
_ = app.Stop() |
|
||||||
}) |
|
||||||
if err := app.Run(); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestApp_ID(t *testing.T) { |
|
||||||
v := "123" |
|
||||||
o := New(ID(v)) |
|
||||||
if !reflect.DeepEqual(v, o.ID()) { |
|
||||||
t.Fatalf("o.ID():%s is not equal to v:%s", o.ID(), v) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestApp_Name(t *testing.T) { |
|
||||||
v := "123" |
|
||||||
o := New(Name(v)) |
|
||||||
if !reflect.DeepEqual(v, o.Name()) { |
|
||||||
t.Fatalf("o.Name():%s is not equal to v:%s", o.Name(), v) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestApp_Version(t *testing.T) { |
|
||||||
v := "123" |
|
||||||
o := New(Version(v)) |
|
||||||
if !reflect.DeepEqual(v, o.Version()) { |
|
||||||
t.Fatalf("o.Version():%s is not equal to v:%s", o.Version(), v) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestApp_Metadata(t *testing.T) { |
|
||||||
v := map[string]string{ |
|
||||||
"a": "1", |
|
||||||
"b": "2", |
|
||||||
} |
|
||||||
o := New(Metadata(v)) |
|
||||||
if !reflect.DeepEqual(v, o.Metadata()) { |
|
||||||
t.Fatalf("o.Metadata():%s is not equal to v:%s", o.Metadata(), v) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestApp_Endpoint(t *testing.T) { |
|
||||||
v := []string{"https://go-kratos.dev", "localhost"} |
|
||||||
var endpoints []*url.URL |
|
||||||
for _, urlStr := range v { |
|
||||||
if endpoint, err := url.Parse(urlStr); err != nil { |
|
||||||
t.Errorf("invalid endpoint:%v", urlStr) |
|
||||||
} else { |
|
||||||
endpoints = append(endpoints, endpoint) |
|
||||||
} |
|
||||||
} |
|
||||||
o := New(Endpoint(endpoints...)) |
|
||||||
if instance, err := o.buildInstance(); err != nil { |
|
||||||
t.Error("build instance failed") |
|
||||||
} else { |
|
||||||
o.instance = instance |
|
||||||
} |
|
||||||
if !reflect.DeepEqual(o.Endpoint(), v) { |
|
||||||
t.Errorf("Endpoint() = %v, want %v", o.Endpoint(), v) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestApp_buildInstance(t *testing.T) { |
|
||||||
want := struct { |
|
||||||
id string |
|
||||||
name string |
|
||||||
version string |
|
||||||
metadata map[string]string |
|
||||||
endpoints []string |
|
||||||
}{ |
|
||||||
id: "1", |
|
||||||
name: "kratos", |
|
||||||
version: "v1.0.0", |
|
||||||
metadata: map[string]string{ |
|
||||||
"a": "1", |
|
||||||
"b": "2", |
|
||||||
}, |
|
||||||
endpoints: []string{"https://go-kratos.dev", "localhost"}, |
|
||||||
} |
|
||||||
var endpoints []*url.URL |
|
||||||
for _, urlStr := range want.endpoints { |
|
||||||
if endpoint, err := url.Parse(urlStr); err != nil { |
|
||||||
t.Errorf("invalid endpoint:%v", urlStr) |
|
||||||
} else { |
|
||||||
endpoints = append(endpoints, endpoint) |
|
||||||
} |
|
||||||
} |
|
||||||
app := New( |
|
||||||
ID(want.id), |
|
||||||
Name(want.name), |
|
||||||
Version(want.version), |
|
||||||
Metadata(want.metadata), |
|
||||||
Endpoint(endpoints...), |
|
||||||
) |
|
||||||
if got, err := app.buildInstance(); err != nil { |
|
||||||
t.Error("build got failed") |
|
||||||
} else { |
|
||||||
if got.ID != want.id { |
|
||||||
t.Errorf("ID() = %v, want %v", got.ID, want.id) |
|
||||||
} |
|
||||||
if got.Name != want.name { |
|
||||||
t.Errorf("Name() = %v, want %v", got.Name, want.name) |
|
||||||
} |
|
||||||
if got.Version != want.version { |
|
||||||
t.Errorf("Version() = %v, want %v", got.Version, want.version) |
|
||||||
} |
|
||||||
if !reflect.DeepEqual(got.Endpoints, want.endpoints) { |
|
||||||
t.Errorf("Endpoint() = %v, want %v", got.Endpoints, want.endpoints) |
|
||||||
} |
|
||||||
if !reflect.DeepEqual(got.Metadata, want.metadata) { |
|
||||||
t.Errorf("Metadata() = %v, want %v", got.Metadata, want.metadata) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestApp_Context(t *testing.T) { |
|
||||||
type fields struct { |
|
||||||
id string |
|
||||||
version string |
|
||||||
name string |
|
||||||
instance *registry.ServiceInstance |
|
||||||
metadata map[string]string |
|
||||||
want struct { |
|
||||||
id string |
|
||||||
version string |
|
||||||
name string |
|
||||||
endpoint []string |
|
||||||
metadata map[string]string |
|
||||||
} |
|
||||||
} |
|
||||||
tests := []fields{ |
|
||||||
{ |
|
||||||
id: "1", |
|
||||||
name: "kratos-v1", |
|
||||||
instance: ®istry.ServiceInstance{Endpoints: []string{"https://go-kratos.dev", "localhost"}}, |
|
||||||
metadata: map[string]string{}, |
|
||||||
version: "v1", |
|
||||||
want: struct { |
|
||||||
id string |
|
||||||
version string |
|
||||||
name string |
|
||||||
endpoint []string |
|
||||||
metadata map[string]string |
|
||||||
}{ |
|
||||||
id: "1", version: "v1", name: "kratos-v1", endpoint: []string{"https://go-kratos.dev", "localhost"}, |
|
||||||
metadata: map[string]string{}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
id: "2", |
|
||||||
name: "kratos-v2", |
|
||||||
instance: ®istry.ServiceInstance{Endpoints: []string{"test"}}, |
|
||||||
metadata: map[string]string{"kratos": "https://github.com/go-kratos/kratos"}, |
|
||||||
version: "v2", |
|
||||||
want: struct { |
|
||||||
id string |
|
||||||
version string |
|
||||||
name string |
|
||||||
endpoint []string |
|
||||||
metadata map[string]string |
|
||||||
}{ |
|
||||||
id: "2", version: "v2", name: "kratos-v2", endpoint: []string{"test"}, |
|
||||||
metadata: map[string]string{"kratos": "https://github.com/go-kratos/kratos"}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
id: "3", |
|
||||||
name: "kratos-v3", |
|
||||||
instance: nil, |
|
||||||
metadata: make(map[string]string), |
|
||||||
version: "v3", |
|
||||||
want: struct { |
|
||||||
id string |
|
||||||
version string |
|
||||||
name string |
|
||||||
endpoint []string |
|
||||||
metadata map[string]string |
|
||||||
}{ |
|
||||||
id: "3", version: "v3", name: "kratos-v3", endpoint: nil, |
|
||||||
metadata: map[string]string{}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
for _, tt := range tests { |
|
||||||
t.Run(tt.name, func(t *testing.T) { |
|
||||||
a := &App{ |
|
||||||
opts: options{id: tt.id, name: tt.name, metadata: tt.metadata, version: tt.version}, |
|
||||||
ctx: context.Background(), |
|
||||||
cancel: nil, |
|
||||||
instance: tt.instance, |
|
||||||
} |
|
||||||
|
|
||||||
ctx := NewContext(context.Background(), a) |
|
||||||
|
|
||||||
if got, ok := FromContext(ctx); ok { |
|
||||||
if got.ID() != tt.want.id { |
|
||||||
t.Errorf("ID() = %v, want %v", got.ID(), tt.want.id) |
|
||||||
} |
|
||||||
if got.Name() != tt.want.name { |
|
||||||
t.Errorf("Name() = %v, want %v", got.Name(), tt.want.name) |
|
||||||
} |
|
||||||
if got.Version() != tt.want.version { |
|
||||||
t.Errorf("Version() = %v, want %v", got.Version(), tt.want.version) |
|
||||||
} |
|
||||||
if !reflect.DeepEqual(got.Endpoint(), tt.want.endpoint) { |
|
||||||
t.Errorf("Endpoint() = %v, want %v", got.Endpoint(), tt.want.endpoint) |
|
||||||
} |
|
||||||
if !reflect.DeepEqual(got.Metadata(), tt.want.metadata) { |
|
||||||
t.Errorf("Metadata() = %v, want %v", got.Metadata(), tt.want.metadata) |
|
||||||
} |
|
||||||
} else { |
|
||||||
t.Errorf("ok() = %v, want %v", ok, true) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,15 +0,0 @@ |
|||||||
module github.com/go-kratos/kratos/cmd/kratos/v2 |
|
||||||
|
|
||||||
go 1.16 |
|
||||||
|
|
||||||
require ( |
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7 |
|
||||||
github.com/emicklei/proto v1.10.0 |
|
||||||
github.com/fatih/color v1.13.0 |
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect |
|
||||||
github.com/spf13/cobra v1.4.0 |
|
||||||
github.com/stretchr/testify v1.7.0 // indirect |
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 |
|
||||||
golang.org/x/text v0.4.0 |
|
||||||
gopkg.in/yaml.v3 v3.0.0 // indirect |
|
||||||
) |
|
@ -1,79 +0,0 @@ |
|||||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= |
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= |
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= |
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= |
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= |
|
||||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= |
|
||||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= |
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|
||||||
github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw= |
|
||||||
github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= |
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= |
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= |
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= |
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= |
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= |
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= |
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= |
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= |
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= |
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= |
|
||||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= |
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= |
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= |
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= |
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= |
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= |
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= |
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= |
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
|
||||||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= |
|
||||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= |
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= |
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= |
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= |
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= |
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= |
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= |
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= |
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= |
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= |
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= |
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= |
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= |
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= |
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= |
|
||||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= |
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= |
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= |
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= |
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
|
||||||
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= |
|
||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
|
@ -1,28 +0,0 @@ |
|||||||
//go:build go1.17
|
|
||||||
// +build go1.17
|
|
||||||
|
|
||||||
package base |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"os/exec" |
|
||||||
"strings" |
|
||||||
) |
|
||||||
|
|
||||||
// GoInstall go get path.
|
|
||||||
func GoInstall(path ...string) error { |
|
||||||
for _, p := range path { |
|
||||||
if !strings.Contains(p, "@") { |
|
||||||
p += "@latest" |
|
||||||
} |
|
||||||
fmt.Printf("go install %s\n", p) |
|
||||||
cmd := exec.Command("go", "install", p) |
|
||||||
cmd.Stdout = os.Stdout |
|
||||||
cmd.Stderr = os.Stderr |
|
||||||
if err := cmd.Run(); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
//go:build !go1.17
|
|
||||||
// +build !go1.17
|
|
||||||
|
|
||||||
package base |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"os/exec" |
|
||||||
) |
|
||||||
|
|
||||||
// GoInstall go get path.
|
|
||||||
func GoInstall(path ...string) error { |
|
||||||
for _, p := range path { |
|
||||||
fmt.Printf("go get -u %s\n", p) |
|
||||||
cmd := exec.Command("go", "get", "-u", p) |
|
||||||
cmd.Stdout = os.Stdout |
|
||||||
cmd.Stderr = os.Stderr |
|
||||||
if err := cmd.Run(); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
@ -1,62 +0,0 @@ |
|||||||
package base |
|
||||||
|
|
||||||
import ( |
|
||||||
"bufio" |
|
||||||
"bytes" |
|
||||||
"os" |
|
||||||
"os/exec" |
|
||||||
"path/filepath" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"golang.org/x/mod/modfile" |
|
||||||
) |
|
||||||
|
|
||||||
// ModulePath returns go module path.
|
|
||||||
func ModulePath(filename string) (string, error) { |
|
||||||
modBytes, err := os.ReadFile(filename) |
|
||||||
if err != nil { |
|
||||||
return "", err |
|
||||||
} |
|
||||||
return modfile.ModulePath(modBytes), nil |
|
||||||
} |
|
||||||
|
|
||||||
// ModuleVersion returns module version.
|
|
||||||
func ModuleVersion(path string) (string, error) { |
|
||||||
stdout := &bytes.Buffer{} |
|
||||||
fd := exec.Command("go", "mod", "graph") |
|
||||||
fd.Stdout = stdout |
|
||||||
fd.Stderr = stdout |
|
||||||
if err := fd.Run(); err != nil { |
|
||||||
return "", err |
|
||||||
} |
|
||||||
rd := bufio.NewReader(stdout) |
|
||||||
for { |
|
||||||
line, _, err := rd.ReadLine() |
|
||||||
if err != nil { |
|
||||||
return "", err |
|
||||||
} |
|
||||||
str := string(line) |
|
||||||
i := strings.Index(str, "@") |
|
||||||
if strings.Contains(str, path+"@") && i != -1 { |
|
||||||
return path + str[i:], nil |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// KratosMod returns kratos mod.
|
|
||||||
func KratosMod() string { |
|
||||||
// go 1.15+ read from env GOMODCACHE
|
|
||||||
cacheOut, _ := exec.Command("go", "env", "GOMODCACHE").Output() |
|
||||||
cachePath := strings.Trim(string(cacheOut), "\n") |
|
||||||
pathOut, _ := exec.Command("go", "env", "GOPATH").Output() |
|
||||||
gopath := strings.Trim(string(pathOut), "\n") |
|
||||||
if cachePath == "" { |
|
||||||
cachePath = filepath.Join(gopath, "pkg", "mod") |
|
||||||
} |
|
||||||
if path, err := ModuleVersion("github.com/go-kratos/kratos/v2"); err == nil { |
|
||||||
// $GOPATH/pkg/mod/github.com/go-kratos/kratos@v2
|
|
||||||
return filepath.Join(cachePath, path) |
|
||||||
} |
|
||||||
// $GOPATH/src/github.com/go-kratos/kratos
|
|
||||||
return filepath.Join(gopath, "src", "github.com", "go-kratos", "kratos") |
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
package base |
|
||||||
|
|
||||||
import ( |
|
||||||
"os" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestModuleVersion(t *testing.T) { |
|
||||||
v, err := ModuleVersion("golang.org/x/mod") |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Log(v) |
|
||||||
} |
|
||||||
|
|
||||||
func TestModulePath(t *testing.T) { |
|
||||||
if err := os.Mkdir("/tmp/test_mod", os.ModePerm); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
f, err := os.Create("/tmp/test_mod/go.mod") |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
mod := `module github.com/go-kratos/kratos/v2 |
|
||||||
|
|
||||||
go 1.16` |
|
||||||
_, err = f.WriteString(mod) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
p, err := ModulePath("/tmp/test_mod/go.mod") |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
if p != "github.com/go-kratos/kratos/v2" { |
|
||||||
t.Fatalf("want: %s, got: %s", "github.com/go-kratos/kratos/v2", p) |
|
||||||
} |
|
||||||
|
|
||||||
t.Cleanup(func() { os.RemoveAll("/tmp/test_mod") }) |
|
||||||
} |
|
@ -1,108 +0,0 @@ |
|||||||
package base |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"fmt" |
|
||||||
"log" |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"github.com/fatih/color" |
|
||||||
) |
|
||||||
|
|
||||||
func kratosHome() string { |
|
||||||
dir, err := os.UserHomeDir() |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
home := filepath.Join(dir, ".kratos") |
|
||||||
if _, err := os.Stat(home); os.IsNotExist(err) { |
|
||||||
if err := os.MkdirAll(home, 0o700); err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
} |
|
||||||
return home |
|
||||||
} |
|
||||||
|
|
||||||
func kratosHomeWithDir(dir string) string { |
|
||||||
home := filepath.Join(kratosHome(), dir) |
|
||||||
if _, err := os.Stat(home); os.IsNotExist(err) { |
|
||||||
if err := os.MkdirAll(home, 0o700); err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
} |
|
||||||
return home |
|
||||||
} |
|
||||||
|
|
||||||
func copyFile(src, dst string, replaces []string) error { |
|
||||||
srcinfo, err := os.Stat(src) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
buf, err := os.ReadFile(src) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
var old string |
|
||||||
for i, next := range replaces { |
|
||||||
if i%2 == 0 { |
|
||||||
old = next |
|
||||||
continue |
|
||||||
} |
|
||||||
buf = bytes.ReplaceAll(buf, []byte(old), []byte(next)) |
|
||||||
} |
|
||||||
return os.WriteFile(dst, buf, srcinfo.Mode()) |
|
||||||
} |
|
||||||
|
|
||||||
func copyDir(src, dst string, replaces, ignores []string) error { |
|
||||||
srcinfo, err := os.Stat(src) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
err = os.MkdirAll(dst, srcinfo.Mode()) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
fds, err := os.ReadDir(src) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
for _, fd := range fds { |
|
||||||
if hasSets(fd.Name(), ignores) { |
|
||||||
continue |
|
||||||
} |
|
||||||
srcfp := filepath.Join(src, fd.Name()) |
|
||||||
dstfp := filepath.Join(dst, fd.Name()) |
|
||||||
var e error |
|
||||||
if fd.IsDir() { |
|
||||||
e = copyDir(srcfp, dstfp, replaces, ignores) |
|
||||||
} else { |
|
||||||
e = copyFile(srcfp, dstfp, replaces) |
|
||||||
} |
|
||||||
if e != nil { |
|
||||||
return e |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func hasSets(name string, sets []string) bool { |
|
||||||
for _, ig := range sets { |
|
||||||
if ig == name { |
|
||||||
return true |
|
||||||
} |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
func Tree(path string, dir string) { |
|
||||||
_ = filepath.Walk(path, func(path string, info os.FileInfo, err error) error { |
|
||||||
if err == nil && info != nil && !info.IsDir() { |
|
||||||
fmt.Printf("%s %s (%v bytes)\n", color.GreenString("CREATED"), strings.Replace(path, dir+"/", "", -1), info.Size()) |
|
||||||
} |
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
|
@ -1,126 +0,0 @@ |
|||||||
package base |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"fmt" |
|
||||||
"net" |
|
||||||
"os" |
|
||||||
"os/exec" |
|
||||||
"path" |
|
||||||
"path/filepath" |
|
||||||
"strings" |
|
||||||
) |
|
||||||
|
|
||||||
var unExpandVarPath = []string{"~", ".", ".."} |
|
||||||
|
|
||||||
// Repo is git repository manager.
|
|
||||||
type Repo struct { |
|
||||||
url string |
|
||||||
home string |
|
||||||
branch string |
|
||||||
} |
|
||||||
|
|
||||||
func repoDir(url string) string { |
|
||||||
vcsURL, err := ParseVCSUrl(url) |
|
||||||
if err != nil { |
|
||||||
return url |
|
||||||
} |
|
||||||
// check host contains port
|
|
||||||
host, _, err := net.SplitHostPort(vcsURL.Host) |
|
||||||
if err != nil { |
|
||||||
host = vcsURL.Host |
|
||||||
} |
|
||||||
for _, p := range unExpandVarPath { |
|
||||||
host = strings.TrimLeft(host, p) |
|
||||||
} |
|
||||||
dir := path.Base(path.Dir(vcsURL.Path)) |
|
||||||
url = fmt.Sprintf("%s/%s", host, dir) |
|
||||||
return url |
|
||||||
} |
|
||||||
|
|
||||||
// NewRepo new a repository manager.
|
|
||||||
func NewRepo(url string, branch string) *Repo { |
|
||||||
return &Repo{ |
|
||||||
url: url, |
|
||||||
home: kratosHomeWithDir("repo/" + repoDir(url)), |
|
||||||
branch: branch, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Path returns the repository cache path.
|
|
||||||
func (r *Repo) Path() string { |
|
||||||
start := strings.LastIndex(r.url, "/") |
|
||||||
end := strings.LastIndex(r.url, ".git") |
|
||||||
if end == -1 { |
|
||||||
end = len(r.url) |
|
||||||
} |
|
||||||
var branch string |
|
||||||
if r.branch == "" { |
|
||||||
branch = "@main" |
|
||||||
} else { |
|
||||||
branch = "@" + r.branch |
|
||||||
} |
|
||||||
return path.Join(r.home, r.url[start+1:end]+branch) |
|
||||||
} |
|
||||||
|
|
||||||
// Pull fetch the repository from remote url.
|
|
||||||
func (r *Repo) Pull(ctx context.Context) error { |
|
||||||
cmd := exec.CommandContext(ctx, "git", "symbolic-ref", "HEAD") |
|
||||||
cmd.Dir = r.Path() |
|
||||||
_, err := cmd.CombinedOutput() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
cmd = exec.CommandContext(ctx, "git", "pull") |
|
||||||
cmd.Dir = r.Path() |
|
||||||
out, err := cmd.CombinedOutput() |
|
||||||
fmt.Println(string(out)) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// Clone clones the repository to cache path.
|
|
||||||
func (r *Repo) Clone(ctx context.Context) error { |
|
||||||
if _, err := os.Stat(r.Path()); !os.IsNotExist(err) { |
|
||||||
return r.Pull(ctx) |
|
||||||
} |
|
||||||
var cmd *exec.Cmd |
|
||||||
if r.branch == "" { |
|
||||||
cmd = exec.CommandContext(ctx, "git", "clone", r.url, r.Path()) |
|
||||||
} else { |
|
||||||
cmd = exec.CommandContext(ctx, "git", "clone", "-b", r.branch, r.url, r.Path()) |
|
||||||
} |
|
||||||
out, err := cmd.CombinedOutput() |
|
||||||
fmt.Println(string(out)) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// CopyTo copies the repository to project path.
|
|
||||||
func (r *Repo) CopyTo(ctx context.Context, to string, modPath string, ignores []string) error { |
|
||||||
if err := r.Clone(ctx); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
mod, err := ModulePath(filepath.Join(r.Path(), "go.mod")) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
return copyDir(r.Path(), to, []string{mod, modPath}, ignores) |
|
||||||
} |
|
||||||
|
|
||||||
// CopyToV2 copies the repository to project path
|
|
||||||
func (r *Repo) CopyToV2(ctx context.Context, to string, modPath string, ignores, replaces []string) error { |
|
||||||
if err := r.Clone(ctx); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
mod, err := ModulePath(filepath.Join(r.Path(), "go.mod")) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
replaces = append([]string{mod, modPath}, replaces...) |
|
||||||
return copyDir(r.Path(), to, replaces, ignores) |
|
||||||
} |
|
@ -1,51 +0,0 @@ |
|||||||
package base |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"os" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestRepo(t *testing.T) { |
|
||||||
urls := []string{ |
|
||||||
// ssh://[user@]host.xz[:port]/path/to/repo.git/
|
|
||||||
"ssh://git@github.com:7875/go-kratos/kratos.git", |
|
||||||
// git://host.xz[:port]/path/to/repo.git/
|
|
||||||
"git://github.com:7875/go-kratos/kratos.git", |
|
||||||
// http[s]://host.xz[:port]/path/to/repo.git/
|
|
||||||
"https://github.com:7875/go-kratos/kratos.git", |
|
||||||
// ftp[s]://host.xz[:port]/path/to/repo.git/
|
|
||||||
"ftps://github.com:7875/go-kratos/kratos.git", |
|
||||||
//[user@]host.xz:path/to/repo.git/
|
|
||||||
"git@github.com:go-kratos/kratos.git", |
|
||||||
// ssh://[user@]host.xz[:port]/~[user]/path/to/repo.git/
|
|
||||||
"ssh://git@github.com:7875/go-kratos/kratos.git", |
|
||||||
// git://host.xz[:port]/~[user]/path/to/repo.git/
|
|
||||||
"git://github.com:7875/go-kratos/kratos.git", |
|
||||||
//[user@]host.xz:/~[user]/path/to/repo.git/
|
|
||||||
"git@github.com:go-kratos/kratos.git", |
|
||||||
///path/to/repo.git/
|
|
||||||
"//github.com/go-kratos/kratos.git", |
|
||||||
// file:///path/to/repo.git/
|
|
||||||
"file://./github.com/go-kratos/kratos.git", |
|
||||||
} |
|
||||||
for _, url := range urls { |
|
||||||
dir := repoDir(url) |
|
||||||
if dir != "github.com/go-kratos" && dir != "/go-kratos" { |
|
||||||
t.Fatal(url, "repoDir test failed", dir) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestRepoClone(t *testing.T) { |
|
||||||
r := NewRepo("https://github.com/go-kratos/service-layout.git", "") |
|
||||||
if err := r.Clone(context.Background()); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
if err := r.CopyTo(context.Background(), "/tmp/test_repo", "github.com/go-kratos/kratos-layout", nil); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
t.Cleanup(func() { |
|
||||||
os.RemoveAll("/tmp/test_repo") |
|
||||||
}) |
|
||||||
} |
|
@ -1,58 +0,0 @@ |
|||||||
package base |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"net/url" |
|
||||||
"regexp" |
|
||||||
"strings" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
scpSyntaxRe = regexp.MustCompile(`^(\w+)@([\w.-]+):(.*)$`) |
|
||||||
scheme = []string{"git", "https", "http", "git+ssh", "ssh", "file", "ftp", "ftps"} |
|
||||||
) |
|
||||||
|
|
||||||
// ParseVCSUrl ref https://github.com/golang/go/blob/master/src/cmd/go/internal/vcs/vcs.go
|
|
||||||
// see https://go-review.googlesource.com/c/go/+/12226/
|
|
||||||
// git url define https://git-scm.com/docs/git-clone#_git_urls
|
|
||||||
func ParseVCSUrl(repo string) (*url.URL, error) { |
|
||||||
var ( |
|
||||||
repoURL *url.URL |
|
||||||
err error |
|
||||||
) |
|
||||||
|
|
||||||
if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil { |
|
||||||
// Match SCP-like syntax and convert it to a URL.
|
|
||||||
// Eg, "git@github.com:user/repo" becomes
|
|
||||||
// "ssh://git@github.com/user/repo".
|
|
||||||
repoURL = &url.URL{ |
|
||||||
Scheme: "ssh", |
|
||||||
User: url.User(m[1]), |
|
||||||
Host: m[2], |
|
||||||
Path: m[3], |
|
||||||
} |
|
||||||
} else { |
|
||||||
if !strings.Contains(repo, "//") { |
|
||||||
repo = "//" + repo |
|
||||||
} |
|
||||||
if strings.HasPrefix(repo, "//git@") { |
|
||||||
repo = "ssh:" + repo |
|
||||||
} else if strings.HasPrefix(repo, "//") { |
|
||||||
repo = "https:" + repo |
|
||||||
} |
|
||||||
repoURL, err = url.Parse(repo) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Iterate over insecure schemes too, because this function simply
|
|
||||||
// reports the state of the repo. If we can't see insecure schemes then
|
|
||||||
// we can't report the actual repo URL.
|
|
||||||
for _, s := range scheme { |
|
||||||
if repoURL.Scheme == s { |
|
||||||
return repoURL, nil |
|
||||||
} |
|
||||||
} |
|
||||||
return nil, errors.New("unable to parse repo url") |
|
||||||
} |
|
@ -1,55 +0,0 @@ |
|||||||
package base |
|
||||||
|
|
||||||
import ( |
|
||||||
"net" |
|
||||||
"strings" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestParseVCSUrl(t *testing.T) { |
|
||||||
repos := []string{ |
|
||||||
// ssh://[user@]host.xz[:port]/path/to/repo.git/
|
|
||||||
"ssh://git@github.com:7875/go-kratos/kratos.git", |
|
||||||
// git://host.xz[:port]/path/to/repo.git/
|
|
||||||
"git://github.com:7875/go-kratos/kratos.git", |
|
||||||
// http[s]://host.xz[:port]/path/to/repo.git/
|
|
||||||
"https://github.com:7875/go-kratos/kratos.git", |
|
||||||
// ftp[s]://host.xz[:port]/path/to/repo.git/
|
|
||||||
"ftps://github.com:7875/go-kratos/kratos.git", |
|
||||||
//[user@]host.xz:path/to/repo.git/
|
|
||||||
"git@github.com:go-kratos/kratos.git", |
|
||||||
// ssh://[user@]host.xz[:port]/~[user]/path/to/repo.git/
|
|
||||||
"ssh://git@github.com:7875/go-kratos/kratos.git", |
|
||||||
// git://host.xz[:port]/~[user]/path/to/repo.git/
|
|
||||||
"git://github.com:7875/go-kratos/kratos.git", |
|
||||||
//[user@]host.xz:/~[user]/path/to/repo.git/
|
|
||||||
"git@github.com:go-kratos/kratos.git", |
|
||||||
///path/to/repo.git/
|
|
||||||
"~/go-kratos/kratos.git", |
|
||||||
// file:///path/to/repo.git/
|
|
||||||
"file://~/go-kratos/kratos.git", |
|
||||||
} |
|
||||||
for _, repo := range repos { |
|
||||||
url, err := ParseVCSUrl(repo) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(repo, err) |
|
||||||
} |
|
||||||
urlPath := strings.TrimLeft(url.Path, "/") |
|
||||||
if urlPath != "go-kratos/kratos.git" { |
|
||||||
t.Fatal(repo, "parse url failed", urlPath) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestParseSsh(t *testing.T) { |
|
||||||
repo := "ssh://git@github.com:7875/go-kratos/kratos.git" |
|
||||||
url, err := ParseVCSUrl(repo) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
host, _, err := net.SplitHostPort(url.Host) |
|
||||||
if err != nil { |
|
||||||
host = url.Host |
|
||||||
} |
|
||||||
t.Log(host, url.Path) |
|
||||||
} |
|
@ -1,45 +0,0 @@ |
|||||||
package change |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
|
|
||||||
"github.com/spf13/cobra" |
|
||||||
) |
|
||||||
|
|
||||||
// CmdChange is kratos change log tool
|
|
||||||
var CmdChange = &cobra.Command{ |
|
||||||
Use: "changelog", |
|
||||||
Short: "Get a kratos change log", |
|
||||||
Long: "Get a kratos release or commits info. Example: kratos changelog dev or kratos changelog {version}", |
|
||||||
Run: run, |
|
||||||
} |
|
||||||
|
|
||||||
var ( |
|
||||||
token string |
|
||||||
repoURL string |
|
||||||
) |
|
||||||
|
|
||||||
func init() { |
|
||||||
if repoURL = os.Getenv("KRATOS_REPO"); repoURL == "" { |
|
||||||
repoURL = "https://github.com/go-kratos/kratos.git" |
|
||||||
} |
|
||||||
CmdChange.Flags().StringVarP(&repoURL, "repo-url", "r", repoURL, "github repo") |
|
||||||
token = os.Getenv("GITHUB_TOKEN") |
|
||||||
} |
|
||||||
|
|
||||||
func run(_ *cobra.Command, args []string) { |
|
||||||
owner, repo := ParseGithubURL(repoURL) |
|
||||||
api := GithubAPI{Owner: owner, Repo: repo, Token: token} |
|
||||||
version := "latest" |
|
||||||
if len(args) > 0 { |
|
||||||
version = args[0] |
|
||||||
} |
|
||||||
if version == "dev" { |
|
||||||
info := api.GetCommitsInfo() |
|
||||||
fmt.Print(ParseCommitsInfo(info)) |
|
||||||
return |
|
||||||
} |
|
||||||
info := api.GetReleaseInfo(version) |
|
||||||
fmt.Print(ParseReleaseInfo(info)) |
|
||||||
} |
|
@ -1,213 +0,0 @@ |
|||||||
package change |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/json" |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"net/http" |
|
||||||
"os" |
|
||||||
"regexp" |
|
||||||
"strings" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
type ReleaseInfo struct { |
|
||||||
Author struct { |
|
||||||
Login string `json:"login"` |
|
||||||
} `json:"author"` |
|
||||||
PublishedAt string `json:"published_at"` |
|
||||||
Body string `json:"body"` |
|
||||||
HTMLURL string `json:"html_url"` |
|
||||||
} |
|
||||||
|
|
||||||
type CommitInfo struct { |
|
||||||
Commit struct { |
|
||||||
Message string `json:"message"` |
|
||||||
} `json:"commit"` |
|
||||||
} |
|
||||||
|
|
||||||
type ErrorInfo struct { |
|
||||||
Message string |
|
||||||
} |
|
||||||
|
|
||||||
type GithubAPI struct { |
|
||||||
Owner string |
|
||||||
Repo string |
|
||||||
Token string |
|
||||||
} |
|
||||||
|
|
||||||
// GetReleaseInfo for getting kratos release info.
|
|
||||||
func (g *GithubAPI) GetReleaseInfo(version string) ReleaseInfo { |
|
||||||
api := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", g.Owner, g.Repo) |
|
||||||
if version != "latest" { |
|
||||||
api = fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", g.Owner, g.Repo, version) |
|
||||||
} |
|
||||||
resp, code := requestGithubAPI(api, http.MethodGet, nil, g.Token) |
|
||||||
if code != http.StatusOK { |
|
||||||
printGithubErrorInfo(resp) |
|
||||||
} |
|
||||||
releaseInfo := ReleaseInfo{} |
|
||||||
err := json.Unmarshal(resp, &releaseInfo) |
|
||||||
if err != nil { |
|
||||||
fatal(err) |
|
||||||
} |
|
||||||
return releaseInfo |
|
||||||
} |
|
||||||
|
|
||||||
// GetCommitsInfo for getting kratos commits info.
|
|
||||||
func (g *GithubAPI) GetCommitsInfo() []CommitInfo { |
|
||||||
info := g.GetReleaseInfo("latest") |
|
||||||
page := 1 |
|
||||||
prePage := 100 |
|
||||||
var list []CommitInfo |
|
||||||
for { |
|
||||||
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/commits?pre_page=%d&page=%d&since=%s", g.Owner, g.Repo, prePage, page, info.PublishedAt) |
|
||||||
resp, code := requestGithubAPI(url, http.MethodGet, nil, g.Token) |
|
||||||
if code != http.StatusOK { |
|
||||||
printGithubErrorInfo(resp) |
|
||||||
} |
|
||||||
var res []CommitInfo |
|
||||||
err := json.Unmarshal(resp, &res) |
|
||||||
if err != nil { |
|
||||||
fatal(err) |
|
||||||
} |
|
||||||
list = append(list, res...) |
|
||||||
if len(res) < prePage { |
|
||||||
break |
|
||||||
} |
|
||||||
page++ |
|
||||||
} |
|
||||||
return list |
|
||||||
} |
|
||||||
|
|
||||||
func printGithubErrorInfo(body []byte) { |
|
||||||
errorInfo := &ErrorInfo{} |
|
||||||
err := json.Unmarshal(body, errorInfo) |
|
||||||
if err != nil { |
|
||||||
fatal(err) |
|
||||||
} |
|
||||||
fatal(errors.New(errorInfo.Message)) |
|
||||||
} |
|
||||||
|
|
||||||
func requestGithubAPI(url string, method string, body io.Reader, token string) ([]byte, int) { |
|
||||||
cli := &http.Client{Timeout: 60 * time.Second} |
|
||||||
request, err := http.NewRequest(method, url, body) |
|
||||||
if err != nil { |
|
||||||
fatal(err) |
|
||||||
} |
|
||||||
if token != "" { |
|
||||||
request.Header.Add("Authorization", token) |
|
||||||
} |
|
||||||
resp, err := cli.Do(request) |
|
||||||
if err != nil { |
|
||||||
fatal(err) |
|
||||||
} |
|
||||||
defer resp.Body.Close() |
|
||||||
resBody, err := io.ReadAll(resp.Body) |
|
||||||
if err != nil { |
|
||||||
fatal(err) |
|
||||||
} |
|
||||||
return resBody, resp.StatusCode |
|
||||||
} |
|
||||||
|
|
||||||
func ParseCommitsInfo(info []CommitInfo) string { |
|
||||||
group := map[string][]string{ |
|
||||||
"fix": {}, |
|
||||||
"feat": {}, |
|
||||||
"deps": {}, |
|
||||||
"break": {}, |
|
||||||
"chore": {}, |
|
||||||
"other": {}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, commitInfo := range info { |
|
||||||
msg := commitInfo.Commit.Message |
|
||||||
index := strings.Index(fmt.Sprintf("%q", msg), `\n`) |
|
||||||
if index != -1 { |
|
||||||
msg = msg[:index-1] |
|
||||||
} |
|
||||||
prefix := []string{"fix", "feat", "deps", "break", "chore"} |
|
||||||
var matched bool |
|
||||||
for _, v := range prefix { |
|
||||||
msg = strings.TrimPrefix(msg, " ") |
|
||||||
if strings.HasPrefix(msg, v) { |
|
||||||
group[v] = append(group[v], msg) |
|
||||||
matched = true |
|
||||||
} |
|
||||||
} |
|
||||||
if !matched { |
|
||||||
group["other"] = append(group["other"], msg) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
md := make(map[string]string) |
|
||||||
for key, value := range group { |
|
||||||
var text string |
|
||||||
switch key { |
|
||||||
case "break": |
|
||||||
text = "### Breaking Changes\n" |
|
||||||
case "deps": |
|
||||||
text = "### Dependencies\n" |
|
||||||
case "feat": |
|
||||||
text = "### New Features\n" |
|
||||||
case "fix": |
|
||||||
text = "### Bug Fixes\n" |
|
||||||
case "chore": |
|
||||||
text = "### Chores\n" |
|
||||||
case "other": |
|
||||||
text = "### Others\n" |
|
||||||
} |
|
||||||
if len(value) == 0 { |
|
||||||
continue |
|
||||||
} |
|
||||||
md[key] += text |
|
||||||
for _, value := range value { |
|
||||||
md[key] += fmt.Sprintf("- %s\n", value) |
|
||||||
} |
|
||||||
} |
|
||||||
return fmt.Sprint(md["break"], md["deps"], md["feat"], md["fix"], md["chore"], md["other"]) |
|
||||||
} |
|
||||||
|
|
||||||
func ParseReleaseInfo(info ReleaseInfo) string { |
|
||||||
reg := regexp.MustCompile(`(?m)^\s*$[\r\n]*|[\r\n]+\s+\z|<[\S\s]+?>`) |
|
||||||
body := reg.ReplaceAll([]byte(info.Body), []byte("")) |
|
||||||
if string(body) == "" { |
|
||||||
body = []byte("no release info") |
|
||||||
} |
|
||||||
splitters := "--------------------------------------------" |
|
||||||
return fmt.Sprintf( |
|
||||||
"Author: %s\nDate: %s\nUrl: %s\n\n%s\n\n%s\n\n%s\n", |
|
||||||
info.Author.Login, |
|
||||||
info.PublishedAt, |
|
||||||
info.HTMLURL, |
|
||||||
splitters, |
|
||||||
body, |
|
||||||
splitters, |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
func ParseGithubURL(url string) (owner string, repo string) { |
|
||||||
var start int |
|
||||||
start = strings.Index(url, "//") |
|
||||||
if start == -1 { |
|
||||||
start = strings.Index(url, ":") + 1 |
|
||||||
} else { |
|
||||||
start += 2 |
|
||||||
} |
|
||||||
end := strings.LastIndex(url, "/") |
|
||||||
gitIndex := strings.LastIndex(url, ".git") |
|
||||||
if gitIndex == -1 { |
|
||||||
repo = url[strings.LastIndex(url, "/")+1:] |
|
||||||
} else { |
|
||||||
repo = url[strings.LastIndex(url, "/")+1 : gitIndex] |
|
||||||
} |
|
||||||
tmp := url[start:end] |
|
||||||
owner = tmp[strings.Index(tmp, "/")+1:] |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
func fatal(err error) { |
|
||||||
fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", err) |
|
||||||
os.Exit(1) |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
package change |
|
||||||
|
|
||||||
import "testing" |
|
||||||
|
|
||||||
func TestParseGithubURL(t *testing.T) { |
|
||||||
urls := []struct { |
|
||||||
url string |
|
||||||
owner string |
|
||||||
repo string |
|
||||||
}{ |
|
||||||
{"https://github.com/go-kratos/kratos.git", "go-kratos", "kratos"}, |
|
||||||
{"https://github.com/go-kratos/kratos", "go-kratos", "kratos"}, |
|
||||||
{"git@github.com:go-kratos/kratos.git", "go-kratos", "kratos"}, |
|
||||||
{"https://github.com/go-kratos/go-kratos.dev.git", "go-kratos", "go-kratos.dev"}, |
|
||||||
} |
|
||||||
for _, url := range urls { |
|
||||||
owner, repo := ParseGithubURL(url.url) |
|
||||||
if owner != url.owner { |
|
||||||
t.Fatalf("owner want: %s, got: %s", owner, url.owner) |
|
||||||
} |
|
||||||
if repo != url.repo { |
|
||||||
t.Fatalf("repo want: %s, got: %s", repo, url.repo) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,67 +0,0 @@ |
|||||||
package project |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2" |
|
||||||
"github.com/fatih/color" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base" |
|
||||||
) |
|
||||||
|
|
||||||
var repoAddIgnores = []string{ |
|
||||||
".git", ".github", "api", "README.md", "LICENSE", "go.mod", "go.sum", "third_party", "openapi.yaml", ".gitignore", |
|
||||||
} |
|
||||||
|
|
||||||
func (p *Project) Add(ctx context.Context, dir string, layout string, branch string, mod string) error { |
|
||||||
to := filepath.Join(dir, p.Name) |
|
||||||
|
|
||||||
if _, err := os.Stat(to); !os.IsNotExist(err) { |
|
||||||
fmt.Printf("🚫 %s already exists\n", p.Name) |
|
||||||
override := false |
|
||||||
prompt := &survey.Confirm{ |
|
||||||
Message: "📂 Do you want to override the folder ?", |
|
||||||
Help: "Delete the existing folder and create the project.", |
|
||||||
} |
|
||||||
e := survey.AskOne(prompt, &override) |
|
||||||
if e != nil { |
|
||||||
return e |
|
||||||
} |
|
||||||
if !override { |
|
||||||
return err |
|
||||||
} |
|
||||||
os.RemoveAll(to) |
|
||||||
} |
|
||||||
|
|
||||||
fmt.Printf("🚀 Add service %s, layout repo is %s, please wait a moment.\n\n", p.Name, layout) |
|
||||||
|
|
||||||
repo := base.NewRepo(layout, branch) |
|
||||||
|
|
||||||
if err := repo.CopyToV2(ctx, to, filepath.Join(mod, p.Path), repoAddIgnores, []string{filepath.Join(p.Path, "api"), "api"}); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
e := os.Rename( |
|
||||||
filepath.Join(to, "cmd", "server"), |
|
||||||
filepath.Join(to, "cmd", p.Name), |
|
||||||
) |
|
||||||
if e != nil { |
|
||||||
return e |
|
||||||
} |
|
||||||
|
|
||||||
base.Tree(to, dir) |
|
||||||
|
|
||||||
fmt.Printf("\n🍺 Repository creation succeeded %s\n", color.GreenString(p.Name)) |
|
||||||
fmt.Print("💻 Use the following command to add a project 👇:\n\n") |
|
||||||
|
|
||||||
fmt.Println(color.WhiteString("$ cd %s", p.Name)) |
|
||||||
fmt.Println(color.WhiteString("$ go generate ./...")) |
|
||||||
fmt.Println(color.WhiteString("$ go build -o ./bin/ ./... ")) |
|
||||||
fmt.Println(color.WhiteString("$ ./bin/%s -conf ./configs\n", p.Name)) |
|
||||||
fmt.Println(" 🤝 Thanks for using Kratos") |
|
||||||
fmt.Println(" 📚 Tutorial: https://go-kratos.dev/docs/getting-started/start") |
|
||||||
return nil |
|
||||||
} |
|
@ -1,64 +0,0 @@ |
|||||||
package project |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2" |
|
||||||
"github.com/fatih/color" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base" |
|
||||||
) |
|
||||||
|
|
||||||
// Project is a project template.
|
|
||||||
type Project struct { |
|
||||||
Name string |
|
||||||
Path string |
|
||||||
} |
|
||||||
|
|
||||||
// New new a project from remote repo.
|
|
||||||
func (p *Project) New(ctx context.Context, dir string, layout string, branch string) error { |
|
||||||
to := filepath.Join(dir, p.Name) |
|
||||||
if _, err := os.Stat(to); !os.IsNotExist(err) { |
|
||||||
fmt.Printf("🚫 %s already exists\n", p.Name) |
|
||||||
prompt := &survey.Confirm{ |
|
||||||
Message: "📂 Do you want to override the folder ?", |
|
||||||
Help: "Delete the existing folder and create the project.", |
|
||||||
} |
|
||||||
var override bool |
|
||||||
e := survey.AskOne(prompt, &override) |
|
||||||
if e != nil { |
|
||||||
return e |
|
||||||
} |
|
||||||
if !override { |
|
||||||
return err |
|
||||||
} |
|
||||||
os.RemoveAll(to) |
|
||||||
} |
|
||||||
fmt.Printf("🚀 Creating service %s, layout repo is %s, please wait a moment.\n\n", p.Name, layout) |
|
||||||
repo := base.NewRepo(layout, branch) |
|
||||||
if err := repo.CopyTo(ctx, to, p.Name, []string{".git", ".github"}); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
e := os.Rename( |
|
||||||
filepath.Join(to, "cmd", "server"), |
|
||||||
filepath.Join(to, "cmd", p.Name), |
|
||||||
) |
|
||||||
if e != nil { |
|
||||||
return e |
|
||||||
} |
|
||||||
base.Tree(to, dir) |
|
||||||
|
|
||||||
fmt.Printf("\n🍺 Project creation succeeded %s\n", color.GreenString(p.Name)) |
|
||||||
fmt.Print("💻 Use the following command to start the project 👇:\n\n") |
|
||||||
|
|
||||||
fmt.Println(color.WhiteString("$ cd %s", p.Name)) |
|
||||||
fmt.Println(color.WhiteString("$ go generate ./...")) |
|
||||||
fmt.Println(color.WhiteString("$ go build -o ./bin/ ./... ")) |
|
||||||
fmt.Println(color.WhiteString("$ ./bin/%s -conf ./configs\n", p.Name)) |
|
||||||
fmt.Println(" 🤝 Thanks for using Kratos") |
|
||||||
fmt.Println(" 📚 Tutorial: https://go-kratos.dev/docs/getting-started/start") |
|
||||||
return nil |
|
||||||
} |
|
@ -1,149 +0,0 @@ |
|||||||
package project |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
"strings" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2" |
|
||||||
"github.com/spf13/cobra" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base" |
|
||||||
) |
|
||||||
|
|
||||||
// CmdNew represents the new command.
|
|
||||||
var CmdNew = &cobra.Command{ |
|
||||||
Use: "new", |
|
||||||
Short: "Create a service template", |
|
||||||
Long: "Create a service project using the repository template. Example: kratos new helloworld", |
|
||||||
Run: run, |
|
||||||
} |
|
||||||
|
|
||||||
var ( |
|
||||||
repoURL string |
|
||||||
branch string |
|
||||||
timeout string |
|
||||||
nomod bool |
|
||||||
) |
|
||||||
|
|
||||||
func init() { |
|
||||||
if repoURL = os.Getenv("KRATOS_LAYOUT_REPO"); repoURL == "" { |
|
||||||
repoURL = "https://github.com/go-kratos/kratos-layout.git" |
|
||||||
} |
|
||||||
timeout = "60s" |
|
||||||
CmdNew.Flags().StringVarP(&repoURL, "repo-url", "r", repoURL, "layout repo") |
|
||||||
CmdNew.Flags().StringVarP(&branch, "branch", "b", branch, "repo branch") |
|
||||||
CmdNew.Flags().StringVarP(&timeout, "timeout", "t", timeout, "time out") |
|
||||||
CmdNew.Flags().BoolVarP(&nomod, "nomod", "", nomod, "retain go mod") |
|
||||||
} |
|
||||||
|
|
||||||
func run(_ *cobra.Command, args []string) { |
|
||||||
wd, err := os.Getwd() |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
t, err := time.ParseDuration(timeout) |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), t) |
|
||||||
defer cancel() |
|
||||||
name := "" |
|
||||||
if len(args) == 0 { |
|
||||||
prompt := &survey.Input{ |
|
||||||
Message: "What is project name ?", |
|
||||||
Help: "Created project name.", |
|
||||||
} |
|
||||||
err = survey.AskOne(prompt, &name) |
|
||||||
if err != nil || name == "" { |
|
||||||
return |
|
||||||
} |
|
||||||
} else { |
|
||||||
name = args[0] |
|
||||||
} |
|
||||||
projectName, workingDir := processProjectParams(name, wd) |
|
||||||
p := &Project{Name: projectName} |
|
||||||
done := make(chan error, 1) |
|
||||||
go func() { |
|
||||||
if !nomod { |
|
||||||
done <- p.New(ctx, workingDir, repoURL, branch) |
|
||||||
return |
|
||||||
} |
|
||||||
projectRoot := getgomodProjectRoot(workingDir) |
|
||||||
if gomodIsNotExistIn(projectRoot) { |
|
||||||
done <- fmt.Errorf("🚫 go.mod don't exists in %s", projectRoot) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
p.Path, err = filepath.Rel(projectRoot, filepath.Join(workingDir, projectName)) |
|
||||||
if err != nil { |
|
||||||
done <- fmt.Errorf("🚫 failed to get relative path: %v", err) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
mod, e := base.ModulePath(filepath.Join(projectRoot, "go.mod")) |
|
||||||
if e != nil { |
|
||||||
done <- fmt.Errorf("🚫 failed to parse `go.mod`: %v", e) |
|
||||||
return |
|
||||||
} |
|
||||||
// Get the relative path for adding a project based on Go modules
|
|
||||||
p.Path = filepath.Join(strings.TrimPrefix(workingDir, projectRoot+"/"), p.Name) |
|
||||||
done <- p.Add(ctx, workingDir, repoURL, branch, mod) |
|
||||||
}() |
|
||||||
select { |
|
||||||
case <-ctx.Done(): |
|
||||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) { |
|
||||||
fmt.Fprint(os.Stderr, "\033[31mERROR: project creation timed out\033[m\n") |
|
||||||
return |
|
||||||
} |
|
||||||
fmt.Fprintf(os.Stderr, "\033[31mERROR: failed to create project(%s)\033[m\n", ctx.Err().Error()) |
|
||||||
case err = <-done: |
|
||||||
if err != nil { |
|
||||||
fmt.Fprintf(os.Stderr, "\033[31mERROR: Failed to create project(%s)\033[m\n", err.Error()) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func processProjectParams(projectName string, workingDir string) (projectNameResult, workingDirResult string) { |
|
||||||
_projectDir := projectName |
|
||||||
_workingDir := workingDir |
|
||||||
// Process ProjectName with system variable
|
|
||||||
if strings.HasPrefix(projectName, "~") { |
|
||||||
homeDir, err := os.UserHomeDir() |
|
||||||
if err != nil { |
|
||||||
// cannot get user home return fallback place dir
|
|
||||||
return _projectDir, _workingDir |
|
||||||
} |
|
||||||
_projectDir = filepath.Join(homeDir, projectName[2:]) |
|
||||||
} |
|
||||||
|
|
||||||
// check path is relative
|
|
||||||
if !filepath.IsAbs(projectName) { |
|
||||||
absPath, err := filepath.Abs(projectName) |
|
||||||
if err != nil { |
|
||||||
return _projectDir, _workingDir |
|
||||||
} |
|
||||||
_projectDir = absPath |
|
||||||
} |
|
||||||
|
|
||||||
return filepath.Base(_projectDir), filepath.Dir(_projectDir) |
|
||||||
} |
|
||||||
|
|
||||||
func getgomodProjectRoot(dir string) string { |
|
||||||
if dir == filepath.Dir(dir) { |
|
||||||
return dir |
|
||||||
} |
|
||||||
if gomodIsNotExistIn(dir) { |
|
||||||
return getgomodProjectRoot(filepath.Dir(dir)) |
|
||||||
} |
|
||||||
return dir |
|
||||||
} |
|
||||||
|
|
||||||
func gomodIsNotExistIn(dir string) bool { |
|
||||||
_, e := os.Stat(filepath.Join(dir, "go.mod")) |
|
||||||
return os.IsNotExist(e) |
|
||||||
} |
|
@ -1,29 +0,0 @@ |
|||||||
//go:build linux
|
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package project |
|
||||||
|
|
||||||
import ( |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func Test_processProjectParams(t *testing.T) { |
|
||||||
type args struct { |
|
||||||
projectName string |
|
||||||
fallbackPlaceDir string |
|
||||||
} |
|
||||||
tests := []struct { |
|
||||||
name string |
|
||||||
args args |
|
||||||
want string |
|
||||||
}{ |
|
||||||
{"absLinux", args{projectName: "/home/kratos/awesome/go/demo", fallbackPlaceDir: ""}, "/home/kratos/awesome/go"}, |
|
||||||
} |
|
||||||
for _, tt := range tests { |
|
||||||
t.Run(tt.name, func(t *testing.T) { |
|
||||||
if _, got := processProjectParams(tt.args.projectName, tt.args.fallbackPlaceDir); got != tt.want { |
|
||||||
t.Errorf("processProjectParams() = %v, want %v", got, tt.want) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,144 +0,0 @@ |
|||||||
package project |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"go/parser" |
|
||||||
"go/token" |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
"testing" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base" |
|
||||||
) |
|
||||||
|
|
||||||
// TestCmdNew tests the `kratos new` command.
|
|
||||||
func TestCmdNew(t *testing.T) { |
|
||||||
cwd := changeCurrentDir(t) |
|
||||||
projectName := "helloworld" |
|
||||||
|
|
||||||
// create a new project
|
|
||||||
CmdNew.SetArgs([]string{projectName}) |
|
||||||
if err := CmdNew.Execute(); err != nil { |
|
||||||
t.Fatalf("executing command: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
// check that the expected files were created
|
|
||||||
for _, file := range []string{ |
|
||||||
"go.mod", |
|
||||||
"go.sum", |
|
||||||
"README.md", |
|
||||||
"cmd/helloworld/main.go", |
|
||||||
} { |
|
||||||
if _, err := os.Stat(filepath.Join(cwd, projectName, file)); err != nil { |
|
||||||
t.Errorf("expected file %s to exist", file) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// check that the go.mod file contains the expected module name
|
|
||||||
assertGoMod(t, filepath.Join(cwd, projectName, "go.mod"), projectName) |
|
||||||
|
|
||||||
assertImportsInclude(t, filepath.Join(cwd, projectName, "cmd", projectName, "wire.go"), fmt.Sprintf(`"%s/internal/biz"`, projectName)) |
|
||||||
} |
|
||||||
|
|
||||||
// TestCmdNewNoMod tests the `kratos new` command with the --nomod flag.
|
|
||||||
func TestCmdNewNoMod(t *testing.T) { |
|
||||||
cwd := changeCurrentDir(t) |
|
||||||
|
|
||||||
// create a new project
|
|
||||||
CmdNew.SetArgs([]string{"project"}) |
|
||||||
if err := CmdNew.Execute(); err != nil { |
|
||||||
t.Fatalf("executing command: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
// add new app with --nomod flag
|
|
||||||
CmdNew.SetArgs([]string{"--nomod", "project/app/user"}) |
|
||||||
if err := CmdNew.Execute(); err != nil { |
|
||||||
t.Fatalf("executing command: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
// check that the expected files were created
|
|
||||||
for _, file := range []string{ |
|
||||||
"go.mod", |
|
||||||
"go.sum", |
|
||||||
"README.md", |
|
||||||
"cmd/project/main.go", |
|
||||||
"app/user/cmd/user/main.go", |
|
||||||
} { |
|
||||||
if _, err := os.Stat(filepath.Join(cwd, "project", file)); err != nil { |
|
||||||
t.Errorf("expected file %s to exist", file) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
assertImportsInclude(t, filepath.Join(cwd, "project/app/user/cmd/user/wire.go"), `"project/app/user/internal/biz"`) |
|
||||||
} |
|
||||||
|
|
||||||
// assertImportsInclude checks that the file at path contains the expected import.
|
|
||||||
func assertImportsInclude(t *testing.T, path, expected string) { |
|
||||||
t.Helper() |
|
||||||
|
|
||||||
got, err := imports(path) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("getting imports: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
for _, imp := range got { |
|
||||||
if imp == expected { |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
t.Errorf("expected imports to include %s, got %v", expected, got) |
|
||||||
} |
|
||||||
|
|
||||||
// imports returns the imports in the file at path.
|
|
||||||
func imports(path string) ([]string, error) { |
|
||||||
fset := token.NewFileSet() |
|
||||||
f, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
imports := make([]string, 0, len(f.Imports)) |
|
||||||
for _, s := range f.Imports { |
|
||||||
imports = append(imports, s.Path.Value) |
|
||||||
} |
|
||||||
|
|
||||||
return imports, nil |
|
||||||
} |
|
||||||
|
|
||||||
// assertGoMod checks that the go.mod file contains the expected module name.
|
|
||||||
func assertGoMod(t *testing.T, path, expected string) { |
|
||||||
t.Helper() |
|
||||||
|
|
||||||
got, err := base.ModulePath(path) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("getting module path: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
if got != expected { |
|
||||||
t.Errorf("expected module name %s, got %s", expected, got) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// change the working directory to the tempdir
|
|
||||||
func changeCurrentDir(t *testing.T) string { |
|
||||||
t.Helper() |
|
||||||
|
|
||||||
tmp := t.TempDir() |
|
||||||
|
|
||||||
oldCWD, err := os.Getwd() |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("getting working directory: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
if err := os.Chdir(tmp); err != nil { |
|
||||||
t.Fatalf("changing working directory: %v", err) |
|
||||||
} |
|
||||||
t.Cleanup(func() { |
|
||||||
if err := os.Chdir(oldCWD); err != nil { |
|
||||||
t.Fatalf("restoring working directory: %v", err) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
return tmp |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package project |
|
||||||
|
|
||||||
import ( |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func Test_processProjectParams(t *testing.T) { |
|
||||||
type args struct { |
|
||||||
projectName string |
|
||||||
fallbackPlaceDir string |
|
||||||
} |
|
||||||
tests := []struct { |
|
||||||
name string |
|
||||||
args args |
|
||||||
want string |
|
||||||
}{ |
|
||||||
{"absWindows", args{projectName: "c:\\kratos\\awesome\\go\\demo", fallbackPlaceDir: ""}, "c:\\kratos\\awesome\\go"}, |
|
||||||
//{"relativeWindows", args{projectName: "/home/kratos/awesome/go/demo", fallbackPlaceDir: ""}, "/home/kratos/awesome/go"},
|
|
||||||
} |
|
||||||
for _, tt := range tests { |
|
||||||
t.Run(tt.name, func(t *testing.T) { |
|
||||||
if _, got := processProjectParams(tt.args.projectName, tt.args.fallbackPlaceDir); got != tt.want { |
|
||||||
t.Errorf("getProjectPlaceDir() = %v, want %v", got, tt.want) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,78 +0,0 @@ |
|||||||
package add |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"github.com/spf13/cobra" |
|
||||||
"golang.org/x/mod/modfile" |
|
||||||
"golang.org/x/text/cases" |
|
||||||
"golang.org/x/text/language" |
|
||||||
) |
|
||||||
|
|
||||||
// CmdAdd represents the add command.
|
|
||||||
var CmdAdd = &cobra.Command{ |
|
||||||
Use: "add", |
|
||||||
Short: "Add a proto API template", |
|
||||||
Long: "Add a proto API template. Example: kratos proto add helloworld/v1/hello.proto", |
|
||||||
Run: run, |
|
||||||
} |
|
||||||
|
|
||||||
func run(_ *cobra.Command, args []string) { |
|
||||||
if len(args) == 0 { |
|
||||||
fmt.Println("Please enter the proto file or directory") |
|
||||||
return |
|
||||||
} |
|
||||||
input := args[0] |
|
||||||
n := strings.LastIndex(input, "/") |
|
||||||
if n == -1 { |
|
||||||
fmt.Println("The proto path needs to be hierarchical.") |
|
||||||
return |
|
||||||
} |
|
||||||
path := input[:n] |
|
||||||
fileName := input[n+1:] |
|
||||||
pkgName := strings.ReplaceAll(path, "/", ".") |
|
||||||
|
|
||||||
p := &Proto{ |
|
||||||
Name: fileName, |
|
||||||
Path: path, |
|
||||||
Package: pkgName, |
|
||||||
GoPackage: goPackage(path), |
|
||||||
JavaPackage: javaPackage(pkgName), |
|
||||||
Service: serviceName(fileName), |
|
||||||
} |
|
||||||
if err := p.Generate(); err != nil { |
|
||||||
fmt.Println(err) |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func modName() string { |
|
||||||
modBytes, err := os.ReadFile("go.mod") |
|
||||||
if err != nil { |
|
||||||
if modBytes, err = os.ReadFile("../go.mod"); err != nil { |
|
||||||
return "" |
|
||||||
} |
|
||||||
} |
|
||||||
return modfile.ModulePath(modBytes) |
|
||||||
} |
|
||||||
|
|
||||||
func goPackage(path string) string { |
|
||||||
s := strings.Split(path, "/") |
|
||||||
return modName() + "/" + path + ";" + s[len(s)-1] |
|
||||||
} |
|
||||||
|
|
||||||
func javaPackage(name string) string { |
|
||||||
return name |
|
||||||
} |
|
||||||
|
|
||||||
func serviceName(name string) string { |
|
||||||
return toUpperCamelCase(strings.Split(name, ".")[0]) |
|
||||||
} |
|
||||||
|
|
||||||
func toUpperCamelCase(s string) string { |
|
||||||
s = strings.ReplaceAll(s, "_", " ") |
|
||||||
s = cases.Title(language.Und, cases.NoLower).String(s) |
|
||||||
return strings.ReplaceAll(s, " ", "") |
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
package add |
|
||||||
|
|
||||||
import "testing" |
|
||||||
|
|
||||||
func TestUnderscoreToUpperCamelCase(t *testing.T) { |
|
||||||
tests := []struct { |
|
||||||
name string |
|
||||||
want string |
|
||||||
}{ |
|
||||||
{ |
|
||||||
name: "hello_world", |
|
||||||
want: "HelloWorld", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "v2_kratos_dev", |
|
||||||
want: "V2KratosDev", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "www_Google_com", |
|
||||||
want: "WwwGoogleCom", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "wwwBaidu_com", |
|
||||||
want: "WwwBaiduCom", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "HelloWorld", |
|
||||||
want: "HelloWorld", |
|
||||||
}, |
|
||||||
} |
|
||||||
for _, tt := range tests { |
|
||||||
t.Run(tt.name, func(t *testing.T) { |
|
||||||
if got := toUpperCamelCase(tt.name); got != tt.want { |
|
||||||
t.Errorf("toUpperCamelCase() = %v, want %v", got, tt.want) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,40 +0,0 @@ |
|||||||
package add |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
) |
|
||||||
|
|
||||||
// Proto is a proto generator.
|
|
||||||
type Proto struct { |
|
||||||
Name string |
|
||||||
Path string |
|
||||||
Service string |
|
||||||
Package string |
|
||||||
GoPackage string |
|
||||||
JavaPackage string |
|
||||||
} |
|
||||||
|
|
||||||
// Generate generate a proto template.
|
|
||||||
func (p *Proto) Generate() error { |
|
||||||
body, err := p.execute() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
wd, err := os.Getwd() |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
to := filepath.Join(wd, p.Path) |
|
||||||
if _, err := os.Stat(to); os.IsNotExist(err) { |
|
||||||
if err := os.MkdirAll(to, 0o700); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
name := filepath.Join(to, p.Name) |
|
||||||
if _, err := os.Stat(name); !os.IsNotExist(err) { |
|
||||||
return fmt.Errorf("%s already exists", p.Name) |
|
||||||
} |
|
||||||
return os.WriteFile(name, body, 0o644) |
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
package add |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"strings" |
|
||||||
"text/template" |
|
||||||
) |
|
||||||
|
|
||||||
const protoTemplate = ` |
|
||||||
syntax = "proto3"; |
|
||||||
|
|
||||||
package {{.Package}}; |
|
||||||
|
|
||||||
option go_package = "{{.GoPackage}}"; |
|
||||||
option java_multiple_files = true; |
|
||||||
option java_package = "{{.JavaPackage}}"; |
|
||||||
|
|
||||||
service {{.Service}} { |
|
||||||
rpc Create{{.Service}} (Create{{.Service}}Request) returns (Create{{.Service}}Reply); |
|
||||||
rpc Update{{.Service}} (Update{{.Service}}Request) returns (Update{{.Service}}Reply); |
|
||||||
rpc Delete{{.Service}} (Delete{{.Service}}Request) returns (Delete{{.Service}}Reply); |
|
||||||
rpc Get{{.Service}} (Get{{.Service}}Request) returns (Get{{.Service}}Reply); |
|
||||||
rpc List{{.Service}} (List{{.Service}}Request) returns (List{{.Service}}Reply); |
|
||||||
} |
|
||||||
|
|
||||||
message Create{{.Service}}Request {} |
|
||||||
message Create{{.Service}}Reply {} |
|
||||||
|
|
||||||
message Update{{.Service}}Request {} |
|
||||||
message Update{{.Service}}Reply {} |
|
||||||
|
|
||||||
message Delete{{.Service}}Request {} |
|
||||||
message Delete{{.Service}}Reply {} |
|
||||||
|
|
||||||
message Get{{.Service}}Request {} |
|
||||||
message Get{{.Service}}Reply {} |
|
||||||
|
|
||||||
message List{{.Service}}Request {} |
|
||||||
message List{{.Service}}Reply {} |
|
||||||
` |
|
||||||
|
|
||||||
func (p *Proto) execute() ([]byte, error) { |
|
||||||
buf := new(bytes.Buffer) |
|
||||||
tmpl, err := template.New("proto").Parse(strings.TrimSpace(protoTemplate)) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if err := tmpl.Execute(buf, p); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return buf.Bytes(), nil |
|
||||||
} |
|
@ -1,130 +0,0 @@ |
|||||||
package client |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"os/exec" |
|
||||||
"path/filepath" |
|
||||||
"regexp" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"github.com/spf13/cobra" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base" |
|
||||||
) |
|
||||||
|
|
||||||
// CmdClient represents the source command.
|
|
||||||
var CmdClient = &cobra.Command{ |
|
||||||
Use: "client", |
|
||||||
Short: "Generate the proto client code", |
|
||||||
Long: "Generate the proto client code. Example: kratos proto client helloworld.proto", |
|
||||||
Run: run, |
|
||||||
} |
|
||||||
|
|
||||||
var protoPath string |
|
||||||
|
|
||||||
func init() { |
|
||||||
if protoPath = os.Getenv("KRATOS_PROTO_PATH"); protoPath == "" { |
|
||||||
protoPath = "./third_party" |
|
||||||
} |
|
||||||
CmdClient.Flags().StringVarP(&protoPath, "proto_path", "p", protoPath, "proto path") |
|
||||||
} |
|
||||||
|
|
||||||
func run(_ *cobra.Command, args []string) { |
|
||||||
if len(args) == 0 { |
|
||||||
fmt.Println("Please enter the proto file or directory") |
|
||||||
return |
|
||||||
} |
|
||||||
var ( |
|
||||||
err error |
|
||||||
proto = strings.TrimSpace(args[0]) |
|
||||||
) |
|
||||||
if err = look("protoc-gen-go", "protoc-gen-go-grpc", "protoc-gen-go-http", "protoc-gen-go-errors", "protoc-gen-openapi"); err != nil { |
|
||||||
// update the kratos plugins
|
|
||||||
cmd := exec.Command("kratos", "upgrade") |
|
||||||
cmd.Stdout = os.Stdout |
|
||||||
cmd.Stderr = os.Stderr |
|
||||||
if err = cmd.Run(); err != nil { |
|
||||||
fmt.Println(err) |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
if strings.HasSuffix(proto, ".proto") { |
|
||||||
err = generate(proto, args) |
|
||||||
} else { |
|
||||||
err = walk(proto, args) |
|
||||||
} |
|
||||||
if err != nil { |
|
||||||
fmt.Println(err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func look(name ...string) error { |
|
||||||
for _, n := range name { |
|
||||||
if _, err := exec.LookPath(n); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func walk(dir string, args []string) error { |
|
||||||
if dir == "" { |
|
||||||
dir = "." |
|
||||||
} |
|
||||||
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { |
|
||||||
if ext := filepath.Ext(path); ext != ".proto" || strings.HasPrefix(path, "third_party") { |
|
||||||
return nil |
|
||||||
} |
|
||||||
return generate(path, args) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// generate is used to execute the generate command for the specified proto file
|
|
||||||
func generate(proto string, args []string) error { |
|
||||||
input := []string{ |
|
||||||
"--proto_path=.", |
|
||||||
} |
|
||||||
if pathExists(protoPath) { |
|
||||||
input = append(input, "--proto_path="+protoPath) |
|
||||||
} |
|
||||||
inputExt := []string{ |
|
||||||
"--proto_path=" + base.KratosMod(), |
|
||||||
"--proto_path=" + filepath.Join(base.KratosMod(), "third_party"), |
|
||||||
"--go_out=paths=source_relative:.", |
|
||||||
"--go-grpc_out=paths=source_relative:.", |
|
||||||
"--go-http_out=paths=source_relative:.", |
|
||||||
"--go-errors_out=paths=source_relative:.", |
|
||||||
"--openapi_out=paths=source_relative:.", |
|
||||||
} |
|
||||||
input = append(input, inputExt...) |
|
||||||
protoBytes, err := os.ReadFile(proto) |
|
||||||
if err == nil && len(protoBytes) > 0 { |
|
||||||
if ok, _ := regexp.Match(`\n[^/]*(import)\s+"validate/validate.proto"`, protoBytes); ok { |
|
||||||
input = append(input, "--validate_out=lang=go,paths=source_relative:.") |
|
||||||
} |
|
||||||
} |
|
||||||
input = append(input, proto) |
|
||||||
for _, a := range args { |
|
||||||
if strings.HasPrefix(a, "-") { |
|
||||||
input = append(input, a) |
|
||||||
} |
|
||||||
} |
|
||||||
fd := exec.Command("protoc", input...) |
|
||||||
fd.Stdout = os.Stdout |
|
||||||
fd.Stderr = os.Stderr |
|
||||||
fd.Dir = "." |
|
||||||
if err := fd.Run(); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
fmt.Printf("proto: %s\n", proto) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func pathExists(path string) bool { |
|
||||||
_, err := os.Stat(path) |
|
||||||
if err != nil { |
|
||||||
return os.IsExist(err) |
|
||||||
} |
|
||||||
return true |
|
||||||
} |
|
@ -1,22 +0,0 @@ |
|||||||
package proto |
|
||||||
|
|
||||||
import ( |
|
||||||
"github.com/spf13/cobra" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/proto/add" |
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/proto/client" |
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/proto/server" |
|
||||||
) |
|
||||||
|
|
||||||
// CmdProto represents the proto command.
|
|
||||||
var CmdProto = &cobra.Command{ |
|
||||||
Use: "proto", |
|
||||||
Short: "Generate the proto files", |
|
||||||
Long: "Generate the proto files.", |
|
||||||
} |
|
||||||
|
|
||||||
func init() { |
|
||||||
CmdProto.AddCommand(add.CmdAdd) |
|
||||||
CmdProto.AddCommand(client.CmdClient) |
|
||||||
CmdProto.AddCommand(server.CmdServer) |
|
||||||
} |
|
@ -1,120 +0,0 @@ |
|||||||
package server |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"log" |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"github.com/emicklei/proto" |
|
||||||
"github.com/spf13/cobra" |
|
||||||
"golang.org/x/text/cases" |
|
||||||
"golang.org/x/text/language" |
|
||||||
) |
|
||||||
|
|
||||||
// CmdServer the service command.
|
|
||||||
var CmdServer = &cobra.Command{ |
|
||||||
Use: "server", |
|
||||||
Short: "Generate the proto server implementations", |
|
||||||
Long: "Generate the proto server implementations. Example: kratos proto server api/xxx.proto --target-dir=internal/service", |
|
||||||
Run: run, |
|
||||||
} |
|
||||||
var targetDir string |
|
||||||
|
|
||||||
func init() { |
|
||||||
CmdServer.Flags().StringVarP(&targetDir, "target-dir", "t", "internal/service", "generate target directory") |
|
||||||
} |
|
||||||
|
|
||||||
func run(_ *cobra.Command, args []string) { |
|
||||||
if len(args) == 0 { |
|
||||||
fmt.Fprintln(os.Stderr, "Please specify the proto file. Example: kratos proto server api/xxx.proto") |
|
||||||
return |
|
||||||
} |
|
||||||
reader, err := os.Open(args[0]) |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
defer reader.Close() |
|
||||||
|
|
||||||
parser := proto.NewParser(reader) |
|
||||||
definition, err := parser.Parse() |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
var ( |
|
||||||
pkg string |
|
||||||
res []*Service |
|
||||||
) |
|
||||||
proto.Walk(definition, |
|
||||||
proto.WithOption(func(o *proto.Option) { |
|
||||||
if o.Name == "go_package" { |
|
||||||
pkg = strings.Split(o.Constant.Source, ";")[0] |
|
||||||
} |
|
||||||
}), |
|
||||||
proto.WithService(func(s *proto.Service) { |
|
||||||
cs := &Service{ |
|
||||||
Package: pkg, |
|
||||||
Service: serviceName(s.Name), |
|
||||||
} |
|
||||||
for _, e := range s.Elements { |
|
||||||
r, ok := e.(*proto.RPC) |
|
||||||
if !ok { |
|
||||||
continue |
|
||||||
} |
|
||||||
cs.Methods = append(cs.Methods, &Method{ |
|
||||||
Service: serviceName(s.Name), Name: serviceName(r.Name), Request: parametersName(r.RequestType), |
|
||||||
Reply: parametersName(r.ReturnsType), Type: getMethodType(r.StreamsRequest, r.StreamsReturns), |
|
||||||
}) |
|
||||||
} |
|
||||||
res = append(res, cs) |
|
||||||
}), |
|
||||||
) |
|
||||||
if _, err := os.Stat(targetDir); os.IsNotExist(err) { |
|
||||||
fmt.Printf("Target directory: %s does not exsit\n", targetDir) |
|
||||||
return |
|
||||||
} |
|
||||||
for _, s := range res { |
|
||||||
to := filepath.Join(targetDir, strings.ToLower(s.Service)+".go") |
|
||||||
if _, err := os.Stat(to); !os.IsNotExist(err) { |
|
||||||
fmt.Fprintf(os.Stderr, "%s already exists: %s\n", s.Service, to) |
|
||||||
continue |
|
||||||
} |
|
||||||
b, err := s.execute() |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
if err := os.WriteFile(to, b, 0o644); err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
fmt.Println(to) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func getMethodType(streamsRequest, streamsReturns bool) MethodType { |
|
||||||
if !streamsRequest && !streamsReturns { |
|
||||||
return unaryType |
|
||||||
} else if streamsRequest && streamsReturns { |
|
||||||
return twoWayStreamsType |
|
||||||
} else if streamsRequest { |
|
||||||
return requestStreamsType |
|
||||||
} else if streamsReturns { |
|
||||||
return returnsStreamsType |
|
||||||
} |
|
||||||
return unaryType |
|
||||||
} |
|
||||||
|
|
||||||
func parametersName(name string) string { |
|
||||||
return strings.ReplaceAll(name, ".", "_") |
|
||||||
} |
|
||||||
|
|
||||||
func serviceName(name string) string { |
|
||||||
return toUpperCamelCase(strings.Split(name, ".")[0]) |
|
||||||
} |
|
||||||
|
|
||||||
func toUpperCamelCase(s string) string { |
|
||||||
s = strings.ReplaceAll(s, "_", " ") |
|
||||||
s = cases.Title(language.Und, cases.NoLower).String(s) |
|
||||||
return strings.ReplaceAll(s, " ", "") |
|
||||||
} |
|
@ -1,102 +0,0 @@ |
|||||||
package server |
|
||||||
|
|
||||||
import "testing" |
|
||||||
|
|
||||||
func Test_serviceName(t *testing.T) { |
|
||||||
type args struct { |
|
||||||
str string |
|
||||||
} |
|
||||||
tests := []struct { |
|
||||||
name string |
|
||||||
args args |
|
||||||
want string |
|
||||||
}{ |
|
||||||
{ |
|
||||||
name: "serviceName on lowercase words", |
|
||||||
args: args{str: "helloworld"}, |
|
||||||
want: "Helloworld", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "serviceName on uppercase words", |
|
||||||
args: args{str: "HELLOWORLD"}, |
|
||||||
want: "HELLOWORLD", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "serviceName on lowercase words with spaces", |
|
||||||
args: args{str: "hello world"}, |
|
||||||
want: "HelloWorld", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "serviceName on uppercase words with spaces", |
|
||||||
args: args{str: "HELLO WORLD"}, |
|
||||||
want: "HELLOWORLD", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "serviceName on Lower Camel Case words", |
|
||||||
args: args{str: "helloWorld"}, |
|
||||||
want: "HelloWorld", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "serviceName on Lower Camel Case words", |
|
||||||
args: args{str: "helloWorld"}, |
|
||||||
want: "HelloWorld", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "serviceName on Upper Camel Case words", |
|
||||||
args: args{str: "HelloWorld"}, |
|
||||||
want: "HelloWorld", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "serviceName on Upper Camel Case words", |
|
||||||
args: args{str: "hello_world"}, |
|
||||||
want: "HelloWorld", |
|
||||||
}, |
|
||||||
} |
|
||||||
for _, tt := range tests { |
|
||||||
t.Run(tt.name, func(t *testing.T) { |
|
||||||
if got := serviceName(tt.args.str); got != tt.want { |
|
||||||
t.Errorf("serviceName() = %v, want %v", got, tt.want) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func Test_parametersName(t *testing.T) { |
|
||||||
type args struct { |
|
||||||
name string |
|
||||||
} |
|
||||||
tests := []struct { |
|
||||||
name string |
|
||||||
args args |
|
||||||
want string |
|
||||||
}{ |
|
||||||
{ |
|
||||||
name: "parametersName on not nested", |
|
||||||
args: args{ |
|
||||||
name: "MessageResponse", |
|
||||||
}, |
|
||||||
want: "MessageResponse", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "parametersName on One layer of nesting", |
|
||||||
args: args{ |
|
||||||
name: "Message.Response", |
|
||||||
}, |
|
||||||
want: "Message_Response", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "parametersName on Two layer of nesting", |
|
||||||
args: args{ |
|
||||||
name: "Message.Message2.Response", |
|
||||||
}, |
|
||||||
want: "Message_Message2_Response", |
|
||||||
}, |
|
||||||
} |
|
||||||
for _, tt := range tests { |
|
||||||
t.Run(tt.name, func(t *testing.T) { |
|
||||||
if got := parametersName(tt.args.name); got != tt.want { |
|
||||||
t.Errorf("parametersName() = %v, want %v", got, tt.want) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,142 +0,0 @@ |
|||||||
package server |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"html/template" |
|
||||||
) |
|
||||||
|
|
||||||
//nolint:lll
|
|
||||||
var serviceTemplate = ` |
|
||||||
{{- /* delete empty line */ -}} |
|
||||||
package service |
|
||||||
|
|
||||||
import ( |
|
||||||
{{- if .UseContext }} |
|
||||||
"context" |
|
||||||
{{- end }} |
|
||||||
{{- if .UseIO }} |
|
||||||
"io" |
|
||||||
{{- end }} |
|
||||||
|
|
||||||
pb "{{ .Package }}" |
|
||||||
{{- if .GoogleEmpty }} |
|
||||||
"google.golang.org/protobuf/types/known/emptypb" |
|
||||||
{{- end }} |
|
||||||
) |
|
||||||
|
|
||||||
type {{ .Service }}Service struct { |
|
||||||
pb.Unimplemented{{ .Service }}Server |
|
||||||
} |
|
||||||
|
|
||||||
func New{{ .Service }}Service() *{{ .Service }}Service { |
|
||||||
return &{{ .Service }}Service{} |
|
||||||
} |
|
||||||
|
|
||||||
{{- $s1 := "google.protobuf.Empty" }} |
|
||||||
{{ range .Methods }} |
|
||||||
{{- if eq .Type 1 }} |
|
||||||
func (s *{{ .Service }}Service) {{ .Name }}(ctx context.Context, req {{ if eq .Request $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Request }}{{ end }}) ({{ if eq .Reply $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Reply }}{{ end }}, error) { |
|
||||||
return {{ if eq .Reply $s1 }}&emptypb.Empty{}{{ else }}&pb.{{ .Reply }}{}{{ end }}, nil |
|
||||||
} |
|
||||||
|
|
||||||
{{- else if eq .Type 2 }} |
|
||||||
func (s *{{ .Service }}Service) {{ .Name }}(conn pb.{{ .Service }}_{{ .Name }}Server) error { |
|
||||||
for { |
|
||||||
req, err := conn.Recv() |
|
||||||
if err == io.EOF { |
|
||||||
return nil |
|
||||||
} |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
err = conn.Send(&pb.{{ .Reply }}{}) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
{{- else if eq .Type 3 }} |
|
||||||
func (s *{{ .Service }}Service) {{ .Name }}(conn pb.{{ .Service }}_{{ .Name }}Server) error { |
|
||||||
for { |
|
||||||
req, err := conn.Recv() |
|
||||||
if err == io.EOF { |
|
||||||
return conn.SendAndClose(&pb.{{ .Reply }}{}) |
|
||||||
} |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
{{- else if eq .Type 4 }} |
|
||||||
func (s *{{ .Service }}Service) {{ .Name }}(req {{ if eq .Request $s1 }}*emptypb.Empty |
|
||||||
{{ else }}*pb.{{ .Request }}{{ end }}, conn pb.{{ .Service }}_{{ .Name }}Server) error { |
|
||||||
for { |
|
||||||
err := conn.Send(&pb.{{ .Reply }}{}) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
{{- end }} |
|
||||||
{{- end }} |
|
||||||
` |
|
||||||
|
|
||||||
type MethodType uint8 |
|
||||||
|
|
||||||
const ( |
|
||||||
unaryType MethodType = 1 |
|
||||||
twoWayStreamsType MethodType = 2 |
|
||||||
requestStreamsType MethodType = 3 |
|
||||||
returnsStreamsType MethodType = 4 |
|
||||||
) |
|
||||||
|
|
||||||
// Service is a proto service.
|
|
||||||
type Service struct { |
|
||||||
Package string |
|
||||||
Service string |
|
||||||
Methods []*Method |
|
||||||
GoogleEmpty bool |
|
||||||
|
|
||||||
UseIO bool |
|
||||||
UseContext bool |
|
||||||
} |
|
||||||
|
|
||||||
// Method is a proto method.
|
|
||||||
type Method struct { |
|
||||||
Service string |
|
||||||
Name string |
|
||||||
Request string |
|
||||||
Reply string |
|
||||||
|
|
||||||
// type: unary or stream
|
|
||||||
Type MethodType |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Service) execute() ([]byte, error) { |
|
||||||
const empty = "google.protobuf.Empty" |
|
||||||
buf := new(bytes.Buffer) |
|
||||||
for _, method := range s.Methods { |
|
||||||
if (method.Type == unaryType && (method.Request == empty || method.Reply == empty)) || |
|
||||||
(method.Type == returnsStreamsType && method.Request == empty) { |
|
||||||
s.GoogleEmpty = true |
|
||||||
} |
|
||||||
if method.Type == twoWayStreamsType || method.Type == requestStreamsType { |
|
||||||
s.UseIO = true |
|
||||||
} |
|
||||||
if method.Type == unaryType { |
|
||||||
s.UseContext = true |
|
||||||
} |
|
||||||
} |
|
||||||
tmpl, err := template.New("service").Parse(serviceTemplate) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if err := tmpl.Execute(buf, s); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return buf.Bytes(), nil |
|
||||||
} |
|
@ -1,145 +0,0 @@ |
|||||||
package run |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"os/exec" |
|
||||||
"path/filepath" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2" |
|
||||||
"github.com/spf13/cobra" |
|
||||||
) |
|
||||||
|
|
||||||
// CmdRun run project command.
|
|
||||||
var CmdRun = &cobra.Command{ |
|
||||||
Use: "run", |
|
||||||
Short: "Run project", |
|
||||||
Long: "Run project. Example: kratos run", |
|
||||||
Run: Run, |
|
||||||
} |
|
||||||
var targetDir string |
|
||||||
|
|
||||||
func init() { |
|
||||||
CmdRun.Flags().StringVarP(&targetDir, "work", "w", "", "target working directory") |
|
||||||
} |
|
||||||
|
|
||||||
// Run run project.
|
|
||||||
func Run(cmd *cobra.Command, args []string) { |
|
||||||
var dir string |
|
||||||
cmdArgs, programArgs := splitArgs(cmd, args) |
|
||||||
if len(cmdArgs) > 0 { |
|
||||||
dir = cmdArgs[0] |
|
||||||
} |
|
||||||
base, err := os.Getwd() |
|
||||||
if err != nil { |
|
||||||
fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", err) |
|
||||||
return |
|
||||||
} |
|
||||||
if dir == "" { |
|
||||||
// find the directory containing the cmd/*
|
|
||||||
cmdPath, err := findCMD(base) |
|
||||||
if err != nil { |
|
||||||
fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", err) |
|
||||||
return |
|
||||||
} |
|
||||||
switch len(cmdPath) { |
|
||||||
case 0: |
|
||||||
fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", "The cmd directory cannot be found in the current directory") |
|
||||||
return |
|
||||||
case 1: |
|
||||||
for _, v := range cmdPath { |
|
||||||
dir = v |
|
||||||
} |
|
||||||
default: |
|
||||||
var cmdPaths []string |
|
||||||
for k := range cmdPath { |
|
||||||
cmdPaths = append(cmdPaths, k) |
|
||||||
} |
|
||||||
prompt := &survey.Select{ |
|
||||||
Message: "Which directory do you want to run?", |
|
||||||
Options: cmdPaths, |
|
||||||
PageSize: 10, |
|
||||||
} |
|
||||||
e := survey.AskOne(prompt, &dir) |
|
||||||
if e != nil || dir == "" { |
|
||||||
return |
|
||||||
} |
|
||||||
dir = cmdPath[dir] |
|
||||||
} |
|
||||||
} |
|
||||||
fd := exec.Command("go", append([]string{"run", dir}, programArgs...)...) |
|
||||||
fd.Stdout = os.Stdout |
|
||||||
fd.Stderr = os.Stderr |
|
||||||
fd.Dir = dir |
|
||||||
changeWorkingDirectory(fd, targetDir) |
|
||||||
if err := fd.Run(); err != nil { |
|
||||||
fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", err.Error()) |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func splitArgs(cmd *cobra.Command, args []string) (cmdArgs, programArgs []string) { |
|
||||||
dashAt := cmd.ArgsLenAtDash() |
|
||||||
if dashAt >= 0 { |
|
||||||
return args[:dashAt], args[dashAt:] |
|
||||||
} |
|
||||||
return args, []string{} |
|
||||||
} |
|
||||||
|
|
||||||
func findCMD(base string) (map[string]string, error) { |
|
||||||
wd, err := os.Getwd() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if !strings.HasSuffix(wd, "/") { |
|
||||||
wd += "/" |
|
||||||
} |
|
||||||
var root bool |
|
||||||
next := func(dir string) (map[string]string, error) { |
|
||||||
cmdPath := make(map[string]string) |
|
||||||
err := filepath.Walk(dir, func(walkPath string, info os.FileInfo, err error) error { |
|
||||||
// multi level directory is not allowed under the cmdPath directory, so it is judged that the path ends with cmdPath.
|
|
||||||
if strings.HasSuffix(walkPath, "cmd") { |
|
||||||
paths, err := os.ReadDir(walkPath) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
for _, fileInfo := range paths { |
|
||||||
if fileInfo.IsDir() { |
|
||||||
abs := filepath.Join(walkPath, fileInfo.Name()) |
|
||||||
cmdPath[strings.TrimPrefix(abs, wd)] = abs |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
if info.Name() == "go.mod" { |
|
||||||
root = true |
|
||||||
} |
|
||||||
return nil |
|
||||||
}) |
|
||||||
return cmdPath, err |
|
||||||
} |
|
||||||
for i := 0; i < 5; i++ { |
|
||||||
tmp := base |
|
||||||
cmd, err := next(tmp) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if len(cmd) > 0 { |
|
||||||
return cmd, nil |
|
||||||
} |
|
||||||
if root { |
|
||||||
break |
|
||||||
} |
|
||||||
_ = filepath.Join(base, "..") |
|
||||||
} |
|
||||||
return map[string]string{"": base}, nil |
|
||||||
} |
|
||||||
|
|
||||||
func changeWorkingDirectory(cmd *exec.Cmd, targetDir string) { |
|
||||||
targetDir = strings.TrimSpace(targetDir) |
|
||||||
if targetDir != "" { |
|
||||||
cmd.Dir = targetDir |
|
||||||
} |
|
||||||
} |
|
@ -1,32 +0,0 @@ |
|||||||
package upgrade |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
|
|
||||||
"github.com/spf13/cobra" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base" |
|
||||||
) |
|
||||||
|
|
||||||
// CmdUpgrade represents the upgrade command.
|
|
||||||
var CmdUpgrade = &cobra.Command{ |
|
||||||
Use: "upgrade", |
|
||||||
Short: "Upgrade the kratos tools", |
|
||||||
Long: "Upgrade the kratos tools. Example: kratos upgrade", |
|
||||||
Run: Run, |
|
||||||
} |
|
||||||
|
|
||||||
// Run upgrade the kratos tools.
|
|
||||||
func Run(_ *cobra.Command, _ []string) { |
|
||||||
err := base.GoInstall( |
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2@latest", |
|
||||||
"github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest", |
|
||||||
"github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2@latest", |
|
||||||
"google.golang.org/protobuf/cmd/protoc-gen-go@latest", |
|
||||||
"google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest", |
|
||||||
"github.com/google/gnostic/cmd/protoc-gen-openapi@latest", |
|
||||||
) |
|
||||||
if err != nil { |
|
||||||
fmt.Println(err) |
|
||||||
} |
|
||||||
} |
|
@ -1,34 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"log" |
|
||||||
|
|
||||||
"github.com/spf13/cobra" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/change" |
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/project" |
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/proto" |
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/run" |
|
||||||
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/upgrade" |
|
||||||
) |
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{ |
|
||||||
Use: "kratos", |
|
||||||
Short: "Kratos: An elegant toolkit for Go microservices.", |
|
||||||
Long: `Kratos: An elegant toolkit for Go microservices.`, |
|
||||||
Version: release, |
|
||||||
} |
|
||||||
|
|
||||||
func init() { |
|
||||||
rootCmd.AddCommand(project.CmdNew) |
|
||||||
rootCmd.AddCommand(proto.CmdProto) |
|
||||||
rootCmd.AddCommand(upgrade.CmdUpgrade) |
|
||||||
rootCmd.AddCommand(change.CmdChange) |
|
||||||
rootCmd.AddCommand(run.CmdRun) |
|
||||||
} |
|
||||||
|
|
||||||
func main() { |
|
||||||
if err := rootCmd.Execute(); err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
} |
|
@ -1,4 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
// release is the current kratos tool version.
|
|
||||||
const release = "v2.6.3" |
|
@ -1,135 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"strings" |
|
||||||
"unicode" |
|
||||||
|
|
||||||
"golang.org/x/text/cases" |
|
||||||
"golang.org/x/text/language" |
|
||||||
|
|
||||||
"google.golang.org/protobuf/compiler/protogen" |
|
||||||
"google.golang.org/protobuf/proto" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2/errors" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
errorsPackage = protogen.GoImportPath("github.com/go-kratos/kratos/v2/errors") |
|
||||||
fmtPackage = protogen.GoImportPath("fmt") |
|
||||||
) |
|
||||||
|
|
||||||
var enCases = cases.Title(language.AmericanEnglish, cases.NoLower) |
|
||||||
|
|
||||||
// generateFile generates a _errors.pb.go file containing kratos errors definitions.
|
|
||||||
func generateFile(gen *protogen.Plugin, file *protogen.File) *protogen.GeneratedFile { |
|
||||||
if len(file.Enums) == 0 { |
|
||||||
return nil |
|
||||||
} |
|
||||||
filename := file.GeneratedFilenamePrefix + "_errors.pb.go" |
|
||||||
g := gen.NewGeneratedFile(filename, file.GoImportPath) |
|
||||||
g.P("// Code generated by protoc-gen-go-errors. DO NOT EDIT.") |
|
||||||
g.P() |
|
||||||
g.P("package ", file.GoPackageName) |
|
||||||
g.P() |
|
||||||
g.QualifiedGoIdent(fmtPackage.Ident("")) |
|
||||||
generateFileContent(gen, file, g) |
|
||||||
return g |
|
||||||
} |
|
||||||
|
|
||||||
// generateFileContent generates the kratos errors definitions, excluding the package statement.
|
|
||||||
func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile) { |
|
||||||
if len(file.Enums) == 0 { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
g.P("// This is a compile-time assertion to ensure that this generated file") |
|
||||||
g.P("// is compatible with the kratos package it is being compiled against.") |
|
||||||
g.P("const _ = ", errorsPackage.Ident("SupportPackageIsVersion1")) |
|
||||||
g.P() |
|
||||||
index := 0 |
|
||||||
for _, enum := range file.Enums { |
|
||||||
if !genErrorsReason(gen, file, g, enum) { |
|
||||||
index++ |
|
||||||
} |
|
||||||
} |
|
||||||
// If all enums do not contain 'errors.code', the current file is skipped
|
|
||||||
if index == 0 { |
|
||||||
g.Skip() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func genErrorsReason(_ *protogen.Plugin, _ *protogen.File, g *protogen.GeneratedFile, enum *protogen.Enum) bool { |
|
||||||
defaultCode := proto.GetExtension(enum.Desc.Options(), errors.E_DefaultCode) |
|
||||||
code := 0 |
|
||||||
if ok := defaultCode.(int32); ok != 0 { |
|
||||||
code = int(ok) |
|
||||||
} |
|
||||||
if code > 600 || code < 0 { |
|
||||||
panic(fmt.Sprintf("Enum '%s' range must be greater than 0 and less than or equal to 600", string(enum.Desc.Name()))) |
|
||||||
} |
|
||||||
var ew errorWrapper |
|
||||||
for _, v := range enum.Values { |
|
||||||
enumCode := code |
|
||||||
eCode := proto.GetExtension(v.Desc.Options(), errors.E_Code) |
|
||||||
if ok := eCode.(int32); ok != 0 { |
|
||||||
enumCode = int(ok) |
|
||||||
} |
|
||||||
// If the current enumeration does not contain 'errors.code'
|
|
||||||
// or the code value exceeds the range, the current enum will be skipped
|
|
||||||
if enumCode > 600 || enumCode < 0 { |
|
||||||
panic(fmt.Sprintf("Enum '%s' range must be greater than 0 and less than or equal to 600", string(v.Desc.Name()))) |
|
||||||
} |
|
||||||
if enumCode == 0 { |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
comment := v.Comments.Leading.String() |
|
||||||
if comment == "" { |
|
||||||
comment = v.Comments.Trailing.String() |
|
||||||
} |
|
||||||
|
|
||||||
err := &errorInfo{ |
|
||||||
Name: string(enum.Desc.Name()), |
|
||||||
Value: string(v.Desc.Name()), |
|
||||||
CamelValue: case2Camel(string(v.Desc.Name())), |
|
||||||
HTTPCode: enumCode, |
|
||||||
Comment: comment, |
|
||||||
HasComment: len(comment) > 0, |
|
||||||
} |
|
||||||
ew.Errors = append(ew.Errors, err) |
|
||||||
} |
|
||||||
if len(ew.Errors) == 0 { |
|
||||||
return true |
|
||||||
} |
|
||||||
g.P(ew.execute()) |
|
||||||
|
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
func case2Camel(name string) string { |
|
||||||
if !strings.Contains(name, "_") { |
|
||||||
if name == strings.ToUpper(name) { |
|
||||||
name = strings.ToLower(name) |
|
||||||
} |
|
||||||
return enCases.String(name) |
|
||||||
} |
|
||||||
strs := strings.Split(name, "_") |
|
||||||
words := make([]string, 0, len(strs)) |
|
||||||
for _, w := range strs { |
|
||||||
hasLower := false |
|
||||||
for _, r := range w { |
|
||||||
if unicode.IsLower(r) { |
|
||||||
hasLower = true |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
if !hasLower { |
|
||||||
w = strings.ToLower(w) |
|
||||||
} |
|
||||||
w = enCases.String(w) |
|
||||||
words = append(words, w) |
|
||||||
} |
|
||||||
|
|
||||||
return strings.Join(words, "") |
|
||||||
} |
|
@ -1,229 +0,0 @@ |
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.26.0
|
|
||||||
// protoc v3.15.7
|
|
||||||
// source: errors.proto
|
|
||||||
|
|
||||||
package errors |
|
||||||
|
|
||||||
import ( |
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect" |
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl" |
|
||||||
descriptorpb "google.golang.org/protobuf/types/descriptorpb" |
|
||||||
reflect "reflect" |
|
||||||
sync "sync" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
// Verify that this generated code is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) |
|
||||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) |
|
||||||
) |
|
||||||
|
|
||||||
type Error struct { |
|
||||||
state protoimpl.MessageState |
|
||||||
sizeCache protoimpl.SizeCache |
|
||||||
unknownFields protoimpl.UnknownFields |
|
||||||
|
|
||||||
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` |
|
||||||
Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` |
|
||||||
Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` |
|
||||||
Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` |
|
||||||
} |
|
||||||
|
|
||||||
func (x *Error) Reset() { |
|
||||||
*x = Error{} |
|
||||||
if protoimpl.UnsafeEnabled { |
|
||||||
mi := &file_errors_proto_msgTypes[0] |
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |
|
||||||
ms.StoreMessageInfo(mi) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (x *Error) String() string { |
|
||||||
return protoimpl.X.MessageStringOf(x) |
|
||||||
} |
|
||||||
|
|
||||||
func (*Error) ProtoMessage() {} |
|
||||||
|
|
||||||
func (x *Error) ProtoReflect() protoreflect.Message { |
|
||||||
mi := &file_errors_proto_msgTypes[0] |
|
||||||
if protoimpl.UnsafeEnabled && x != nil { |
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |
|
||||||
if ms.LoadMessageInfo() == nil { |
|
||||||
ms.StoreMessageInfo(mi) |
|
||||||
} |
|
||||||
return ms |
|
||||||
} |
|
||||||
return mi.MessageOf(x) |
|
||||||
} |
|
||||||
|
|
||||||
// Deprecated: Use Error.ProtoReflect.Descriptor instead.
|
|
||||||
func (*Error) Descriptor() ([]byte, []int) { |
|
||||||
return file_errors_proto_rawDescGZIP(), []int{0} |
|
||||||
} |
|
||||||
|
|
||||||
func (x *Error) GetCode() int32 { |
|
||||||
if x != nil { |
|
||||||
return x.Code |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
func (x *Error) GetReason() string { |
|
||||||
if x != nil { |
|
||||||
return x.Reason |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
func (x *Error) GetMessage() string { |
|
||||||
if x != nil { |
|
||||||
return x.Message |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
func (x *Error) GetMetadata() map[string]string { |
|
||||||
if x != nil { |
|
||||||
return x.Metadata |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
var file_errors_proto_extTypes = []protoimpl.ExtensionInfo{ |
|
||||||
{ |
|
||||||
ExtendedType: (*descriptorpb.EnumOptions)(nil), |
|
||||||
ExtensionType: (*int32)(nil), |
|
||||||
Field: 1108, |
|
||||||
Name: "errors.default_code", |
|
||||||
Tag: "varint,1108,opt,name=default_code", |
|
||||||
Filename: "errors.proto", |
|
||||||
}, |
|
||||||
{ |
|
||||||
ExtendedType: (*descriptorpb.EnumValueOptions)(nil), |
|
||||||
ExtensionType: (*int32)(nil), |
|
||||||
Field: 1109, |
|
||||||
Name: "errors.code", |
|
||||||
Tag: "varint,1109,opt,name=code", |
|
||||||
Filename: "errors.proto", |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
// Extension fields to descriptorpb.EnumOptions.
|
|
||||||
var ( |
|
||||||
// optional int32 default_code = 1108;
|
|
||||||
E_DefaultCode = &file_errors_proto_extTypes[0] |
|
||||||
) |
|
||||||
|
|
||||||
// Extension fields to descriptorpb.EnumValueOptions.
|
|
||||||
var ( |
|
||||||
// optional int32 code = 1109;
|
|
||||||
E_Code = &file_errors_proto_extTypes[1] |
|
||||||
) |
|
||||||
|
|
||||||
var File_errors_proto protoreflect.FileDescriptor |
|
||||||
|
|
||||||
var file_errors_proto_rawDesc = []byte{ |
|
||||||
0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, |
|
||||||
0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, |
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, |
|
||||||
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc3, 0x01, 0x0a, 0x05, 0x45, 0x72, 0x72, |
|
||||||
0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, |
|
||||||
0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, |
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, |
|
||||||
0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, |
|
||||||
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, |
|
||||||
0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x72, 0x72, |
|
||||||
0x6f, 0x72, 0x73, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, |
|
||||||
0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, |
|
||||||
0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, |
|
||||||
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, |
|
||||||
0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, |
|
||||||
0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x3a, 0x40, |
|
||||||
0x0a, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x1c, |
|
||||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, |
|
||||||
0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd4, 0x08, 0x20, |
|
||||||
0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, |
|
||||||
0x3a, 0x36, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, |
|
||||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, |
|
||||||
0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd5, 0x08, 0x20, 0x01, |
|
||||||
0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x42, 0x59, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, |
|
||||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x65, 0x72, |
|
||||||
0x72, 0x6f, 0x72, 0x73, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, |
|
||||||
0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, |
|
||||||
0x74, 0x6f, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x3b, 0x65, 0x72, |
|
||||||
0x72, 0x6f, 0x72, 0x73, 0xa2, 0x02, 0x0c, 0x4b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x45, 0x72, 0x72, |
|
||||||
0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, |
|
||||||
} |
|
||||||
|
|
||||||
var ( |
|
||||||
file_errors_proto_rawDescOnce sync.Once |
|
||||||
file_errors_proto_rawDescData = file_errors_proto_rawDesc |
|
||||||
) |
|
||||||
|
|
||||||
func file_errors_proto_rawDescGZIP() []byte { |
|
||||||
file_errors_proto_rawDescOnce.Do(func() { |
|
||||||
file_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_errors_proto_rawDescData) |
|
||||||
}) |
|
||||||
return file_errors_proto_rawDescData |
|
||||||
} |
|
||||||
|
|
||||||
var file_errors_proto_msgTypes = make([]protoimpl.MessageInfo, 2) |
|
||||||
var file_errors_proto_goTypes = []interface{}{ |
|
||||||
(*Error)(nil), // 0: errors.Error
|
|
||||||
nil, // 1: errors.Error.MetadataEntry
|
|
||||||
(*descriptorpb.EnumOptions)(nil), // 2: google.protobuf.EnumOptions
|
|
||||||
(*descriptorpb.EnumValueOptions)(nil), // 3: google.protobuf.EnumValueOptions
|
|
||||||
} |
|
||||||
var file_errors_proto_depIdxs = []int32{ |
|
||||||
1, // 0: errors.Error.metadata:type_name -> errors.Error.MetadataEntry
|
|
||||||
2, // 1: errors.default_code:extendee -> google.protobuf.EnumOptions
|
|
||||||
3, // 2: errors.code:extendee -> google.protobuf.EnumValueOptions
|
|
||||||
3, // [3:3] is the sub-list for method output_type
|
|
||||||
3, // [3:3] is the sub-list for method input_type
|
|
||||||
3, // [3:3] is the sub-list for extension type_name
|
|
||||||
1, // [1:3] is the sub-list for extension extendee
|
|
||||||
0, // [0:1] is the sub-list for field type_name
|
|
||||||
} |
|
||||||
|
|
||||||
func init() { file_errors_proto_init() } |
|
||||||
func file_errors_proto_init() { |
|
||||||
if File_errors_proto != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
if !protoimpl.UnsafeEnabled { |
|
||||||
file_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { |
|
||||||
switch v := v.(*Error); i { |
|
||||||
case 0: |
|
||||||
return &v.state |
|
||||||
case 1: |
|
||||||
return &v.sizeCache |
|
||||||
case 2: |
|
||||||
return &v.unknownFields |
|
||||||
default: |
|
||||||
return nil |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
type x struct{} |
|
||||||
out := protoimpl.TypeBuilder{ |
|
||||||
File: protoimpl.DescBuilder{ |
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), |
|
||||||
RawDescriptor: file_errors_proto_rawDesc, |
|
||||||
NumEnums: 0, |
|
||||||
NumMessages: 2, |
|
||||||
NumExtensions: 2, |
|
||||||
NumServices: 0, |
|
||||||
}, |
|
||||||
GoTypes: file_errors_proto_goTypes, |
|
||||||
DependencyIndexes: file_errors_proto_depIdxs, |
|
||||||
MessageInfos: file_errors_proto_msgTypes, |
|
||||||
ExtensionInfos: file_errors_proto_extTypes, |
|
||||||
}.Build() |
|
||||||
File_errors_proto = out.File |
|
||||||
file_errors_proto_rawDesc = nil |
|
||||||
file_errors_proto_goTypes = nil |
|
||||||
file_errors_proto_depIdxs = nil |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
syntax = "proto3"; |
|
||||||
|
|
||||||
package errors; |
|
||||||
|
|
||||||
option go_package = "github.com/go-kratos/kratos/v2/errors;errors"; |
|
||||||
option java_multiple_files = true; |
|
||||||
option java_package = "com.github.kratos.errors"; |
|
||||||
option objc_class_prefix = "KratosErrors"; |
|
||||||
|
|
||||||
import "google/protobuf/descriptor.proto"; |
|
||||||
|
|
||||||
message Error { |
|
||||||
int32 code = 1; |
|
||||||
string reason = 2; |
|
||||||
string message = 3; |
|
||||||
map<string, string> metadata = 4; |
|
||||||
}; |
|
||||||
|
|
||||||
extend google.protobuf.EnumOptions { |
|
||||||
int32 default_code = 1108; |
|
||||||
} |
|
||||||
|
|
||||||
extend google.protobuf.EnumValueOptions { |
|
||||||
int32 code = 1109; |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
{{ range .Errors }} |
|
||||||
|
|
||||||
{{ if .HasComment }}{{ .Comment }}{{ end -}} |
|
||||||
func Is{{.CamelValue}}(err error) bool { |
|
||||||
if err == nil { |
|
||||||
return false |
|
||||||
} |
|
||||||
e := errors.FromError(err) |
|
||||||
return e.Reason == {{ .Name }}_{{ .Value }}.String() && e.Code == {{ .HTTPCode }} |
|
||||||
} |
|
||||||
|
|
||||||
{{ if .HasComment }}{{ .Comment }}{{ end -}} |
|
||||||
func Error{{ .CamelValue }}(format string, args ...interface{}) *errors.Error { |
|
||||||
return errors.New({{ .HTTPCode }}, {{ .Name }}_{{ .Value }}.String(), fmt.Sprintf(format, args...)) |
|
||||||
} |
|
||||||
|
|
||||||
{{- end }} |
|
@ -1,62 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import "testing" |
|
||||||
|
|
||||||
func Test_case2Camel(t *testing.T) { |
|
||||||
type args struct { |
|
||||||
name string |
|
||||||
} |
|
||||||
tests := []struct { |
|
||||||
name string |
|
||||||
args args |
|
||||||
want string |
|
||||||
}{ |
|
||||||
{ |
|
||||||
name: "snake1", |
|
||||||
args: args{"SYSTEM_ERROR"}, |
|
||||||
want: "SystemError", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "snake2", |
|
||||||
args: args{"System_Error"}, |
|
||||||
want: "SystemError", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "snake3", |
|
||||||
args: args{"system_error"}, |
|
||||||
want: "SystemError", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "snake4", |
|
||||||
args: args{"System_error"}, |
|
||||||
want: "SystemError", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "upper1", |
|
||||||
args: args{"UNKNOWN"}, |
|
||||||
want: "Unknown", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "camel1", |
|
||||||
args: args{"SystemError"}, |
|
||||||
want: "SystemError", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "camel2", |
|
||||||
args: args{"systemError"}, |
|
||||||
want: "SystemError", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "lower1", |
|
||||||
args: args{"system"}, |
|
||||||
want: "System", |
|
||||||
}, |
|
||||||
} |
|
||||||
for _, tt := range tests { |
|
||||||
t.Run(tt.name, func(t *testing.T) { |
|
||||||
if got := case2Camel(tt.args.name); got != tt.want { |
|
||||||
t.Errorf("case2Camel() = %v, want %v", got, tt.want) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,8 +0,0 @@ |
|||||||
module github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2 |
|
||||||
|
|
||||||
go 1.16 |
|
||||||
|
|
||||||
require ( |
|
||||||
golang.org/x/text v0.3.8 |
|
||||||
google.golang.org/protobuf v1.28.0 |
|
||||||
) |
|
@ -1,33 +0,0 @@ |
|||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= |
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= |
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= |
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= |
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= |
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= |
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= |
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= |
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= |
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= |
|
||||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= |
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= |
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= |
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= |
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= |
|
||||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= |
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= |
|
@ -1,32 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"flag" |
|
||||||
"fmt" |
|
||||||
|
|
||||||
"google.golang.org/protobuf/compiler/protogen" |
|
||||||
"google.golang.org/protobuf/types/pluginpb" |
|
||||||
) |
|
||||||
|
|
||||||
var showVersion = flag.Bool("version", false, "print the version and exit") |
|
||||||
|
|
||||||
func main() { |
|
||||||
flag.Parse() |
|
||||||
if *showVersion { |
|
||||||
fmt.Printf("protoc-gen-go-errors %v\n", release) |
|
||||||
return |
|
||||||
} |
|
||||||
var flags flag.FlagSet |
|
||||||
protogen.Options{ |
|
||||||
ParamFunc: flags.Set, |
|
||||||
}.Run(func(gen *protogen.Plugin) error { |
|
||||||
gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) |
|
||||||
for _, f := range gen.Files { |
|
||||||
if !f.Generate { |
|
||||||
continue |
|
||||||
} |
|
||||||
generateFile(gen, f) |
|
||||||
} |
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
|
@ -1,35 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
_ "embed" |
|
||||||
"text/template" |
|
||||||
) |
|
||||||
|
|
||||||
//go:embed errorsTemplate.tpl
|
|
||||||
var errorsTemplate string |
|
||||||
|
|
||||||
type errorInfo struct { |
|
||||||
Name string |
|
||||||
Value string |
|
||||||
HTTPCode int |
|
||||||
CamelValue string |
|
||||||
Comment string |
|
||||||
HasComment bool |
|
||||||
} |
|
||||||
|
|
||||||
type errorWrapper struct { |
|
||||||
Errors []*errorInfo |
|
||||||
} |
|
||||||
|
|
||||||
func (e *errorWrapper) execute() string { |
|
||||||
buf := new(bytes.Buffer) |
|
||||||
tmpl, err := template.New("errors").Parse(errorsTemplate) |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
if err := tmpl.Execute(buf, e); err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
return buf.String() |
|
||||||
} |
|
@ -1,4 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
// release is the current protoc-gen-go-errors version.
|
|
||||||
const release = "v2.6.3" |
|
@ -1,8 +0,0 @@ |
|||||||
module github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2 |
|
||||||
|
|
||||||
go 1.16 |
|
||||||
|
|
||||||
require ( |
|
||||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd |
|
||||||
google.golang.org/protobuf v1.28.0 |
|
||||||
) |
|
@ -1,130 +0,0 @@ |
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= |
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= |
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= |
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= |
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= |
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= |
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= |
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= |
|
||||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= |
|
||||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= |
|
||||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= |
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= |
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= |
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= |
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= |
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= |
|
||||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= |
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= |
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= |
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= |
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= |
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= |
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= |
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= |
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= |
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= |
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= |
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= |
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= |
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= |
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= |
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= |
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= |
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= |
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= |
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
|
||||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= |
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= |
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= |
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= |
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= |
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= |
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= |
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= |
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= |
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= |
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= |
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= |
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= |
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= |
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= |
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= |
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= |
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= |
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= |
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= |
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= |
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= |
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= |
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= |
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= |
|
||||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= |
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= |
|
||||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= |
|
||||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= |
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= |
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= |
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= |
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= |
|
||||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= |
|
||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= |
|
||||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= |
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= |
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= |
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= |
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= |
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= |
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= |
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= |
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= |
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= |
|
||||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= |
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= |
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= |
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= |
|
@ -1,334 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"net/http" |
|
||||||
"os" |
|
||||||
"regexp" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"google.golang.org/protobuf/reflect/protoreflect" |
|
||||||
|
|
||||||
"google.golang.org/genproto/googleapis/api/annotations" |
|
||||||
"google.golang.org/protobuf/compiler/protogen" |
|
||||||
"google.golang.org/protobuf/proto" |
|
||||||
"google.golang.org/protobuf/types/descriptorpb" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
contextPackage = protogen.GoImportPath("context") |
|
||||||
transportHTTPPackage = protogen.GoImportPath("github.com/go-kratos/kratos/v2/transport/http") |
|
||||||
bindingPackage = protogen.GoImportPath("github.com/go-kratos/kratos/v2/transport/http/binding") |
|
||||||
) |
|
||||||
|
|
||||||
var methodSets = make(map[string]int) |
|
||||||
|
|
||||||
// generateFile generates a _http.pb.go file containing kratos errors definitions.
|
|
||||||
func generateFile(gen *protogen.Plugin, file *protogen.File, omitempty bool, omitemptyPrefix string) *protogen.GeneratedFile { |
|
||||||
if len(file.Services) == 0 || (omitempty && !hasHTTPRule(file.Services)) { |
|
||||||
return nil |
|
||||||
} |
|
||||||
filename := file.GeneratedFilenamePrefix + "_http.pb.go" |
|
||||||
g := gen.NewGeneratedFile(filename, file.GoImportPath) |
|
||||||
g.P("// Code generated by protoc-gen-go-http. DO NOT EDIT.") |
|
||||||
g.P("// versions:") |
|
||||||
g.P(fmt.Sprintf("// - protoc-gen-go-http %s", release)) |
|
||||||
g.P("// - protoc ", protocVersion(gen)) |
|
||||||
if file.Proto.GetOptions().GetDeprecated() { |
|
||||||
g.P("// ", file.Desc.Path(), " is a deprecated file.") |
|
||||||
} else { |
|
||||||
g.P("// source: ", file.Desc.Path()) |
|
||||||
} |
|
||||||
g.P() |
|
||||||
g.P("package ", file.GoPackageName) |
|
||||||
g.P() |
|
||||||
generateFileContent(gen, file, g, omitempty, omitemptyPrefix) |
|
||||||
return g |
|
||||||
} |
|
||||||
|
|
||||||
// generateFileContent generates the kratos errors definitions, excluding the package statement.
|
|
||||||
func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, omitempty bool, omitemptyPrefix string) { |
|
||||||
if len(file.Services) == 0 { |
|
||||||
return |
|
||||||
} |
|
||||||
g.P("// This is a compile-time assertion to ensure that this generated file") |
|
||||||
g.P("// is compatible with the kratos package it is being compiled against.") |
|
||||||
g.P("var _ = new(", contextPackage.Ident("Context"), ")") |
|
||||||
g.P("var _ = ", bindingPackage.Ident("EncodeURL")) |
|
||||||
g.P("const _ = ", transportHTTPPackage.Ident("SupportPackageIsVersion1")) |
|
||||||
g.P() |
|
||||||
|
|
||||||
for _, service := range file.Services { |
|
||||||
genService(gen, file, g, service, omitempty, omitemptyPrefix) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func genService(_ *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, service *protogen.Service, omitempty bool, omitemptyPrefix string) { |
|
||||||
if service.Desc.Options().(*descriptorpb.ServiceOptions).GetDeprecated() { |
|
||||||
g.P("//") |
|
||||||
g.P(deprecationComment) |
|
||||||
} |
|
||||||
// HTTP Server.
|
|
||||||
sd := &serviceDesc{ |
|
||||||
ServiceType: service.GoName, |
|
||||||
ServiceName: string(service.Desc.FullName()), |
|
||||||
Metadata: file.Desc.Path(), |
|
||||||
} |
|
||||||
for _, method := range service.Methods { |
|
||||||
if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { |
|
||||||
continue |
|
||||||
} |
|
||||||
rule, ok := proto.GetExtension(method.Desc.Options(), annotations.E_Http).(*annotations.HttpRule) |
|
||||||
if rule != nil && ok { |
|
||||||
for _, bind := range rule.AdditionalBindings { |
|
||||||
sd.Methods = append(sd.Methods, buildHTTPRule(g, service, method, bind, omitemptyPrefix)) |
|
||||||
} |
|
||||||
sd.Methods = append(sd.Methods, buildHTTPRule(g, service, method, rule, omitemptyPrefix)) |
|
||||||
} else if !omitempty { |
|
||||||
path := fmt.Sprintf("%s/%s/%s", omitemptyPrefix, service.Desc.FullName(), method.Desc.Name()) |
|
||||||
sd.Methods = append(sd.Methods, buildMethodDesc(g, method, http.MethodPost, path)) |
|
||||||
} |
|
||||||
} |
|
||||||
if len(sd.Methods) != 0 { |
|
||||||
g.P(sd.execute()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func hasHTTPRule(services []*protogen.Service) bool { |
|
||||||
for _, service := range services { |
|
||||||
for _, method := range service.Methods { |
|
||||||
if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { |
|
||||||
continue |
|
||||||
} |
|
||||||
rule, ok := proto.GetExtension(method.Desc.Options(), annotations.E_Http).(*annotations.HttpRule) |
|
||||||
if rule != nil && ok { |
|
||||||
return true |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
func buildHTTPRule(g *protogen.GeneratedFile, service *protogen.Service, m *protogen.Method, rule *annotations.HttpRule, omitemptyPrefix string) *methodDesc { |
|
||||||
var ( |
|
||||||
path string |
|
||||||
method string |
|
||||||
body string |
|
||||||
responseBody string |
|
||||||
) |
|
||||||
|
|
||||||
switch pattern := rule.Pattern.(type) { |
|
||||||
case *annotations.HttpRule_Get: |
|
||||||
path = pattern.Get |
|
||||||
method = http.MethodGet |
|
||||||
case *annotations.HttpRule_Put: |
|
||||||
path = pattern.Put |
|
||||||
method = http.MethodPut |
|
||||||
case *annotations.HttpRule_Post: |
|
||||||
path = pattern.Post |
|
||||||
method = http.MethodPost |
|
||||||
case *annotations.HttpRule_Delete: |
|
||||||
path = pattern.Delete |
|
||||||
method = http.MethodDelete |
|
||||||
case *annotations.HttpRule_Patch: |
|
||||||
path = pattern.Patch |
|
||||||
method = http.MethodPatch |
|
||||||
case *annotations.HttpRule_Custom: |
|
||||||
path = pattern.Custom.Path |
|
||||||
method = pattern.Custom.Kind |
|
||||||
} |
|
||||||
if method == "" { |
|
||||||
method = http.MethodPost |
|
||||||
} |
|
||||||
if path == "" { |
|
||||||
path = fmt.Sprintf("%s/%s/%s", omitemptyPrefix, service.Desc.FullName(), m.Desc.Name()) |
|
||||||
} |
|
||||||
body = rule.Body |
|
||||||
responseBody = rule.ResponseBody |
|
||||||
md := buildMethodDesc(g, m, method, path) |
|
||||||
if method == http.MethodGet || method == http.MethodDelete { |
|
||||||
if body != "" { |
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: %s %s body should not be declared.\n", method, path) |
|
||||||
} |
|
||||||
} else { |
|
||||||
if body == "" { |
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: %s %s does not declare a body.\n", method, path) |
|
||||||
} |
|
||||||
} |
|
||||||
if body == "*" { |
|
||||||
md.HasBody = true |
|
||||||
md.Body = "" |
|
||||||
} else if body != "" { |
|
||||||
md.HasBody = true |
|
||||||
md.Body = "." + camelCaseVars(body) |
|
||||||
} else { |
|
||||||
md.HasBody = false |
|
||||||
} |
|
||||||
if responseBody == "*" { |
|
||||||
md.ResponseBody = "" |
|
||||||
} else if responseBody != "" { |
|
||||||
md.ResponseBody = "." + camelCaseVars(responseBody) |
|
||||||
} |
|
||||||
return md |
|
||||||
} |
|
||||||
|
|
||||||
func buildMethodDesc(g *protogen.GeneratedFile, m *protogen.Method, method, path string) *methodDesc { |
|
||||||
defer func() { methodSets[m.GoName]++ }() |
|
||||||
|
|
||||||
vars := buildPathVars(path) |
|
||||||
|
|
||||||
for v, s := range vars { |
|
||||||
fields := m.Input.Desc.Fields() |
|
||||||
|
|
||||||
if s != nil { |
|
||||||
path = replacePath(v, *s, path) |
|
||||||
} |
|
||||||
for _, field := range strings.Split(v, ".") { |
|
||||||
if strings.TrimSpace(field) == "" { |
|
||||||
continue |
|
||||||
} |
|
||||||
if strings.Contains(field, ":") { |
|
||||||
field = strings.Split(field, ":")[0] |
|
||||||
} |
|
||||||
fd := fields.ByName(protoreflect.Name(field)) |
|
||||||
if fd == nil { |
|
||||||
fmt.Fprintf(os.Stderr, "\u001B[31mERROR\u001B[m: The corresponding field '%s' declaration in message could not be found in '%s'\n", v, path) |
|
||||||
os.Exit(2) |
|
||||||
} |
|
||||||
if fd.IsMap() { |
|
||||||
fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: The field in path:'%s' shouldn't be a map.\n", v) |
|
||||||
} else if fd.IsList() { |
|
||||||
fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: The field in path:'%s' shouldn't be a list.\n", v) |
|
||||||
} else if fd.Kind() == protoreflect.MessageKind || fd.Kind() == protoreflect.GroupKind { |
|
||||||
fields = fd.Message().Fields() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
comment := m.Comments.Leading.String() + m.Comments.Trailing.String() |
|
||||||
if comment != "" { |
|
||||||
comment = "// " + m.GoName + strings.TrimPrefix(strings.TrimSuffix(comment, "\n"), "//") |
|
||||||
} |
|
||||||
return &methodDesc{ |
|
||||||
Name: m.GoName, |
|
||||||
OriginalName: string(m.Desc.Name()), |
|
||||||
Num: methodSets[m.GoName], |
|
||||||
Request: g.QualifiedGoIdent(m.Input.GoIdent), |
|
||||||
Reply: g.QualifiedGoIdent(m.Output.GoIdent), |
|
||||||
Comment: comment, |
|
||||||
Path: path, |
|
||||||
Method: method, |
|
||||||
HasVars: len(vars) > 0, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func buildPathVars(path string) (res map[string]*string) { |
|
||||||
if strings.HasSuffix(path, "/") { |
|
||||||
fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: Path %s should not end with \"/\" \n", path) |
|
||||||
} |
|
||||||
pattern := regexp.MustCompile(`(?i){([a-z.0-9_\s]*)=?([^{}]*)}`) |
|
||||||
matches := pattern.FindAllStringSubmatch(path, -1) |
|
||||||
res = make(map[string]*string, len(matches)) |
|
||||||
for _, m := range matches { |
|
||||||
name := strings.TrimSpace(m[1]) |
|
||||||
if len(name) > 1 && len(m[2]) > 0 { |
|
||||||
res[name] = &m[2] |
|
||||||
} else { |
|
||||||
res[name] = nil |
|
||||||
} |
|
||||||
} |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
func replacePath(name string, value string, path string) string { |
|
||||||
pattern := regexp.MustCompile(fmt.Sprintf(`(?i){([\s]*%s[\s]*)=?([^{}]*)}`, name)) |
|
||||||
idx := pattern.FindStringIndex(path) |
|
||||||
if len(idx) > 0 { |
|
||||||
path = fmt.Sprintf("%s{%s:%s}%s", |
|
||||||
path[:idx[0]], // The start of the match
|
|
||||||
name, |
|
||||||
strings.ReplaceAll(value, "*", ".*"), |
|
||||||
path[idx[1]:], |
|
||||||
) |
|
||||||
} |
|
||||||
return path |
|
||||||
} |
|
||||||
|
|
||||||
func camelCaseVars(s string) string { |
|
||||||
subs := strings.Split(s, ".") |
|
||||||
vars := make([]string, 0, len(subs)) |
|
||||||
for _, sub := range subs { |
|
||||||
vars = append(vars, camelCase(sub)) |
|
||||||
} |
|
||||||
return strings.Join(vars, ".") |
|
||||||
} |
|
||||||
|
|
||||||
// camelCase returns the CamelCased name.
|
|
||||||
// If there is an interior underscore followed by a lower case letter,
|
|
||||||
// drop the underscore and convert the letter to upper case.
|
|
||||||
// There is a remote possibility of this rewrite causing a name collision,
|
|
||||||
// but it's so remote we're prepared to pretend it's nonexistent - since the
|
|
||||||
// C++ generator lowercase names, it's extremely unlikely to have two fields
|
|
||||||
// with different capitalization.
|
|
||||||
// In short, _my_field_name_2 becomes XMyFieldName_2.
|
|
||||||
func camelCase(s string) string { |
|
||||||
if s == "" { |
|
||||||
return "" |
|
||||||
} |
|
||||||
t := make([]byte, 0, 32) |
|
||||||
i := 0 |
|
||||||
if s[0] == '_' { |
|
||||||
// Need a capital letter; drop the '_'.
|
|
||||||
t = append(t, 'X') |
|
||||||
i++ |
|
||||||
} |
|
||||||
// Invariant: if the next letter is lower case, it must be converted
|
|
||||||
// to upper case.
|
|
||||||
// That is, we process a word at a time, where words are marked by _ or
|
|
||||||
// upper case letter. Digits are treated as words.
|
|
||||||
for ; i < len(s); i++ { |
|
||||||
c := s[i] |
|
||||||
if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { |
|
||||||
continue // Skip the underscore in s.
|
|
||||||
} |
|
||||||
if isASCIIDigit(c) { |
|
||||||
t = append(t, c) |
|
||||||
continue |
|
||||||
} |
|
||||||
// Assume we have a letter now - if not, it's a bogus identifier.
|
|
||||||
// The next word is a sequence of characters that must start upper case.
|
|
||||||
if isASCIILower(c) { |
|
||||||
c ^= ' ' // Make it a capital letter.
|
|
||||||
} |
|
||||||
t = append(t, c) // Guaranteed not lower case.
|
|
||||||
// Accept lower case sequence that follows.
|
|
||||||
for i+1 < len(s) && isASCIILower(s[i+1]) { |
|
||||||
i++ |
|
||||||
t = append(t, s[i]) |
|
||||||
} |
|
||||||
} |
|
||||||
return string(t) |
|
||||||
} |
|
||||||
|
|
||||||
// Is c an ASCII lower-case letter?
|
|
||||||
func isASCIILower(c byte) bool { |
|
||||||
return 'a' <= c && c <= 'z' |
|
||||||
} |
|
||||||
|
|
||||||
// Is c an ASCII digit?
|
|
||||||
func isASCIIDigit(c byte) bool { |
|
||||||
return '0' <= c && c <= '9' |
|
||||||
} |
|
||||||
|
|
||||||
func protocVersion(gen *protogen.Plugin) string { |
|
||||||
v := gen.Request.GetCompilerVersion() |
|
||||||
if v == nil { |
|
||||||
return "(unknown)" |
|
||||||
} |
|
||||||
var suffix string |
|
||||||
if s := v.GetSuffix(); s != "" { |
|
||||||
suffix = "-" + s |
|
||||||
} |
|
||||||
return fmt.Sprintf("v%d.%d.%d%s", v.GetMajor(), v.GetMinor(), v.GetPatch(), suffix) |
|
||||||
} |
|
||||||
|
|
||||||
const deprecationComment = "// Deprecated: Do not use." |
|
@ -1,93 +0,0 @@ |
|||||||
{{$svrType := .ServiceType}} |
|
||||||
{{$svrName := .ServiceName}} |
|
||||||
|
|
||||||
{{- range .MethodSets}} |
|
||||||
const Operation{{$svrType}}{{.OriginalName}} = "/{{$svrName}}/{{.OriginalName}}" |
|
||||||
{{- end}} |
|
||||||
|
|
||||||
type {{.ServiceType}}HTTPServer interface { |
|
||||||
{{- range .MethodSets}} |
|
||||||
{{- if ne .Comment ""}} |
|
||||||
{{.Comment}} |
|
||||||
{{- end}} |
|
||||||
{{.Name}}(context.Context, *{{.Request}}) (*{{.Reply}}, error) |
|
||||||
{{- end}} |
|
||||||
} |
|
||||||
|
|
||||||
func Register{{.ServiceType}}HTTPServer(s *http.Server, srv {{.ServiceType}}HTTPServer) { |
|
||||||
r := s.Route("/") |
|
||||||
{{- range .Methods}} |
|
||||||
r.{{.Method}}("{{.Path}}", _{{$svrType}}_{{.Name}}{{.Num}}_HTTP_Handler(srv)) |
|
||||||
{{- end}} |
|
||||||
} |
|
||||||
|
|
||||||
{{range .Methods}} |
|
||||||
func _{{$svrType}}_{{.Name}}{{.Num}}_HTTP_Handler(srv {{$svrType}}HTTPServer) func(ctx http.Context) error { |
|
||||||
return func(ctx http.Context) error { |
|
||||||
var in {{.Request}} |
|
||||||
{{- if .HasBody}} |
|
||||||
if err := ctx.Bind(&in{{.Body}}); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
{{- if not (eq .Body "")}} |
|
||||||
if err := ctx.BindQuery(&in); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
{{- end}} |
|
||||||
{{- else}} |
|
||||||
if err := ctx.BindQuery(&in{{.Body}}); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
{{- end}} |
|
||||||
{{- if .HasVars}} |
|
||||||
if err := ctx.BindVars(&in); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
{{- end}} |
|
||||||
http.SetOperation(ctx,Operation{{$svrType}}{{.OriginalName}}) |
|
||||||
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { |
|
||||||
return srv.{{.Name}}(ctx, req.(*{{.Request}})) |
|
||||||
}) |
|
||||||
out, err := h(ctx, &in) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
reply := out.(*{{.Reply}}) |
|
||||||
return ctx.Result(200, reply{{.ResponseBody}}) |
|
||||||
} |
|
||||||
} |
|
||||||
{{end}} |
|
||||||
|
|
||||||
type {{.ServiceType}}HTTPClient interface { |
|
||||||
{{- range .MethodSets}} |
|
||||||
{{.Name}}(ctx context.Context, req *{{.Request}}, opts ...http.CallOption) (rsp *{{.Reply}}, err error) |
|
||||||
{{- end}} |
|
||||||
} |
|
||||||
|
|
||||||
type {{.ServiceType}}HTTPClientImpl struct{ |
|
||||||
cc *http.Client |
|
||||||
} |
|
||||||
|
|
||||||
func New{{.ServiceType}}HTTPClient (client *http.Client) {{.ServiceType}}HTTPClient { |
|
||||||
return &{{.ServiceType}}HTTPClientImpl{client} |
|
||||||
} |
|
||||||
|
|
||||||
{{range .MethodSets}} |
|
||||||
func (c *{{$svrType}}HTTPClientImpl) {{.Name}}(ctx context.Context, in *{{.Request}}, opts ...http.CallOption) (*{{.Reply}}, error) { |
|
||||||
var out {{.Reply}} |
|
||||||
pattern := "{{.Path}}" |
|
||||||
path := binding.EncodeURL(pattern, in, {{not .HasBody}}) |
|
||||||
opts = append(opts, http.Operation(Operation{{$svrType}}{{.OriginalName}})) |
|
||||||
opts = append(opts, http.PathTemplate(pattern)) |
|
||||||
{{if .HasBody -}} |
|
||||||
err := c.cc.Invoke(ctx, "{{.Method}}", path, in{{.Body}}, &out{{.ResponseBody}}, opts...) |
|
||||||
{{else -}} |
|
||||||
err := c.cc.Invoke(ctx, "{{.Method}}", path, nil, &out{{.ResponseBody}}, opts...) |
|
||||||
{{end -}} |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return &out, err |
|
||||||
} |
|
||||||
{{end}} |
|
@ -1,87 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"reflect" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestNoParameters(t *testing.T) { |
|
||||||
path := "/test/noparams" |
|
||||||
m := buildPathVars(path) |
|
||||||
if !reflect.DeepEqual(m, map[string]*string{}) { |
|
||||||
t.Fatalf("Map should be empty") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestSingleParam(t *testing.T) { |
|
||||||
path := "/test/{message.id}" |
|
||||||
m := buildPathVars(path) |
|
||||||
if !reflect.DeepEqual(len(m), 1) { |
|
||||||
t.Fatalf("len(m) not is 1") |
|
||||||
} |
|
||||||
if m["message.id"] != nil { |
|
||||||
t.Fatalf(`m["message.id"] should be empty`) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestTwoParametersReplacement(t *testing.T) { |
|
||||||
path := "/test/{message.id}/{message.name=messages/*}" |
|
||||||
m := buildPathVars(path) |
|
||||||
if len(m) != 2 { |
|
||||||
t.Fatal("len(m) should be 2") |
|
||||||
} |
|
||||||
if m["message.id"] != nil { |
|
||||||
t.Fatal(`m["message.id"] should be nil`) |
|
||||||
} |
|
||||||
if m["message.name"] == nil { |
|
||||||
t.Fatal(`m["message.name"] should not be nil`) |
|
||||||
} |
|
||||||
if *m["message.name"] != "messages/*" { |
|
||||||
t.Fatal(`m["message.name"] should be "messages/*"`) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestNoReplacePath(t *testing.T) { |
|
||||||
path := "/test/{message.id=test}" |
|
||||||
if !reflect.DeepEqual(replacePath("message.id", "test", path), "/test/{message.id:test}") { |
|
||||||
t.Fatal(`replacePath("message.id", "test", path) should be "/test/{message.id:test}"`) |
|
||||||
} |
|
||||||
path = "/test/{message.id=test/*}" |
|
||||||
if !reflect.DeepEqual(replacePath("message.id", "test/*", path), "/test/{message.id:test/.*}") { |
|
||||||
t.Fatal(`replacePath("message.id", "test/*", path) should be "/test/{message.id:test/.*}"`) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestReplacePath(t *testing.T) { |
|
||||||
path := "/test/{message.id}/{message.name=messages/*}" |
|
||||||
newPath := replacePath("message.name", "messages/*", path) |
|
||||||
if !reflect.DeepEqual("/test/{message.id}/{message.name:messages/.*}", newPath) { |
|
||||||
t.Fatal(`replacePath("message.name", "messages/*", path) should be "/test/{message.id}/{message.name:messages/.*}"`) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestIteration(t *testing.T) { |
|
||||||
path := "/test/{message.id}/{message.name=messages/*}" |
|
||||||
vars := buildPathVars(path) |
|
||||||
for v, s := range vars { |
|
||||||
if s != nil { |
|
||||||
path = replacePath(v, *s, path) |
|
||||||
} |
|
||||||
} |
|
||||||
if !reflect.DeepEqual("/test/{message.id}/{message.name:messages/.*}", path) { |
|
||||||
t.Fatal(`replacePath("message.name", "messages/*", path) should be "/test/{message.id}/{message.name:messages/.*}"`) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestIterationMiddle(t *testing.T) { |
|
||||||
path := "/test/{message.name=messages/*}/books" |
|
||||||
vars := buildPathVars(path) |
|
||||||
for v, s := range vars { |
|
||||||
if s != nil { |
|
||||||
path = replacePath(v, *s, path) |
|
||||||
} |
|
||||||
} |
|
||||||
if !reflect.DeepEqual("/test/{message.name:messages/.*}/books", path) { |
|
||||||
t.Fatal(`replacePath("message.name", "messages/*", path) should be "/test/{message.name:messages/.*}/books"`) |
|
||||||
} |
|
||||||
} |
|
@ -1,35 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"flag" |
|
||||||
"fmt" |
|
||||||
|
|
||||||
"google.golang.org/protobuf/compiler/protogen" |
|
||||||
"google.golang.org/protobuf/types/pluginpb" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
showVersion = flag.Bool("version", false, "print the version and exit") |
|
||||||
omitempty = flag.Bool("omitempty", true, "omit if google.api is empty") |
|
||||||
omitemptyPrefix = flag.String("omitempty_prefix", "", "omit if google.api is empty") |
|
||||||
) |
|
||||||
|
|
||||||
func main() { |
|
||||||
flag.Parse() |
|
||||||
if *showVersion { |
|
||||||
fmt.Printf("protoc-gen-go-http %v\n", release) |
|
||||||
return |
|
||||||
} |
|
||||||
protogen.Options{ |
|
||||||
ParamFunc: flag.CommandLine.Set, |
|
||||||
}.Run(func(gen *protogen.Plugin) error { |
|
||||||
gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) |
|
||||||
for _, f := range gen.Files { |
|
||||||
if !f.Generate { |
|
||||||
continue |
|
||||||
} |
|
||||||
generateFile(gen, f, *omitempty, *omitemptyPrefix) |
|
||||||
} |
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
_ "embed" |
|
||||||
"strings" |
|
||||||
"text/template" |
|
||||||
) |
|
||||||
|
|
||||||
//go:embed httpTemplate.tpl
|
|
||||||
var httpTemplate string |
|
||||||
|
|
||||||
type serviceDesc struct { |
|
||||||
ServiceType string // Greeter
|
|
||||||
ServiceName string // helloworld.Greeter
|
|
||||||
Metadata string // api/helloworld/helloworld.proto
|
|
||||||
Methods []*methodDesc |
|
||||||
MethodSets map[string]*methodDesc |
|
||||||
} |
|
||||||
|
|
||||||
type methodDesc struct { |
|
||||||
// method
|
|
||||||
Name string |
|
||||||
OriginalName string // The parsed original name
|
|
||||||
Num int |
|
||||||
Request string |
|
||||||
Reply string |
|
||||||
Comment string |
|
||||||
// http_rule
|
|
||||||
Path string |
|
||||||
Method string |
|
||||||
HasVars bool |
|
||||||
HasBody bool |
|
||||||
Body string |
|
||||||
ResponseBody string |
|
||||||
} |
|
||||||
|
|
||||||
func (s *serviceDesc) execute() string { |
|
||||||
s.MethodSets = make(map[string]*methodDesc) |
|
||||||
for _, m := range s.Methods { |
|
||||||
s.MethodSets[m.Name] = m |
|
||||||
} |
|
||||||
buf := new(bytes.Buffer) |
|
||||||
tmpl, err := template.New("http").Parse(strings.TrimSpace(httpTemplate)) |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
if err := tmpl.Execute(buf, s); err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
return strings.Trim(buf.String(), "\r\n") |
|
||||||
} |
|
@ -1,4 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
// release is the current protoc-gen-go-http version.
|
|
||||||
const release = "v2.6.3" |
|
@ -1,3 +0,0 @@ |
|||||||
ignore: |
|
||||||
- "examples" |
|
||||||
- "**/*.pb.go" |
|
@ -1,25 +0,0 @@ |
|||||||
# Config |
|
||||||
|
|
||||||
## kubernetes |
|
||||||
|
|
||||||
```shell |
|
||||||
go get -u github.com/go-kratos/kratos/contrib/config/kubernetes/v2 |
|
||||||
``` |
|
||||||
|
|
||||||
## apollo |
|
||||||
|
|
||||||
```shell |
|
||||||
go get -u github.com/go-kratos/kratos/contrib/config/apollo/v2 |
|
||||||
``` |
|
||||||
|
|
||||||
## etcd |
|
||||||
|
|
||||||
```shell |
|
||||||
go get -u github.com/go-kratos/kratos/contrib/config/etcd/v2 |
|
||||||
``` |
|
||||||
|
|
||||||
## nacos |
|
||||||
|
|
||||||
```shell |
|
||||||
go get -u github.com/go-kratos/kratos/contrib/config/nacos/v2 |
|
||||||
``` |
|
@ -1,158 +0,0 @@ |
|||||||
package config |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"errors" |
|
||||||
"reflect" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
|
|
||||||
// init encoding
|
|
||||||
_ "github.com/go-kratos/kratos/v2/encoding/json" |
|
||||||
_ "github.com/go-kratos/kratos/v2/encoding/proto" |
|
||||||
_ "github.com/go-kratos/kratos/v2/encoding/xml" |
|
||||||
_ "github.com/go-kratos/kratos/v2/encoding/yaml" |
|
||||||
"github.com/go-kratos/kratos/v2/log" |
|
||||||
) |
|
||||||
|
|
||||||
var _ Config = (*config)(nil) |
|
||||||
|
|
||||||
var ( |
|
||||||
// ErrNotFound is key not found.
|
|
||||||
ErrNotFound = errors.New("key not found") |
|
||||||
// ErrTypeAssert is type assert error.
|
|
||||||
ErrTypeAssert = errors.New("type assert error") |
|
||||||
) |
|
||||||
|
|
||||||
// Observer is config observer.
|
|
||||||
type Observer func(string, Value) |
|
||||||
|
|
||||||
// Config is a config interface.
|
|
||||||
type Config interface { |
|
||||||
Load() error |
|
||||||
Scan(v interface{}) error |
|
||||||
Value(key string) Value |
|
||||||
Watch(key string, o Observer) error |
|
||||||
Close() error |
|
||||||
} |
|
||||||
|
|
||||||
type config struct { |
|
||||||
opts options |
|
||||||
reader Reader |
|
||||||
cached sync.Map |
|
||||||
observers sync.Map |
|
||||||
watchers []Watcher |
|
||||||
} |
|
||||||
|
|
||||||
// New a config with options.
|
|
||||||
func New(opts ...Option) Config { |
|
||||||
o := options{ |
|
||||||
decoder: defaultDecoder, |
|
||||||
resolver: defaultResolver, |
|
||||||
} |
|
||||||
for _, opt := range opts { |
|
||||||
opt(&o) |
|
||||||
} |
|
||||||
return &config{ |
|
||||||
opts: o, |
|
||||||
reader: newReader(o), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (c *config) watch(w Watcher) { |
|
||||||
for { |
|
||||||
kvs, err := w.Next() |
|
||||||
if err != nil { |
|
||||||
if errors.Is(err, context.Canceled) { |
|
||||||
log.Infof("watcher's ctx cancel : %v", err) |
|
||||||
return |
|
||||||
} |
|
||||||
time.Sleep(time.Second) |
|
||||||
log.Errorf("failed to watch next config: %v", err) |
|
||||||
continue |
|
||||||
} |
|
||||||
if err := c.reader.Merge(kvs...); err != nil { |
|
||||||
log.Errorf("failed to merge next config: %v", err) |
|
||||||
continue |
|
||||||
} |
|
||||||
if err := c.reader.Resolve(); err != nil { |
|
||||||
log.Errorf("failed to resolve next config: %v", err) |
|
||||||
continue |
|
||||||
} |
|
||||||
c.cached.Range(func(key, value interface{}) bool { |
|
||||||
k := key.(string) |
|
||||||
v := value.(Value) |
|
||||||
if n, ok := c.reader.Value(k); ok && reflect.TypeOf(n.Load()) == reflect.TypeOf(v.Load()) && !reflect.DeepEqual(n.Load(), v.Load()) { |
|
||||||
v.Store(n.Load()) |
|
||||||
if o, ok := c.observers.Load(k); ok { |
|
||||||
o.(Observer)(k, v) |
|
||||||
} |
|
||||||
} |
|
||||||
return true |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (c *config) Load() error { |
|
||||||
for _, src := range c.opts.sources { |
|
||||||
kvs, err := src.Load() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
for _, v := range kvs { |
|
||||||
log.Debugf("config loaded: %s format: %s", v.Key, v.Format) |
|
||||||
} |
|
||||||
if err = c.reader.Merge(kvs...); err != nil { |
|
||||||
log.Errorf("failed to merge config source: %v", err) |
|
||||||
return err |
|
||||||
} |
|
||||||
w, err := src.Watch() |
|
||||||
if err != nil { |
|
||||||
log.Errorf("failed to watch config source: %v", err) |
|
||||||
return err |
|
||||||
} |
|
||||||
c.watchers = append(c.watchers, w) |
|
||||||
go c.watch(w) |
|
||||||
} |
|
||||||
if err := c.reader.Resolve(); err != nil { |
|
||||||
log.Errorf("failed to resolve config source: %v", err) |
|
||||||
return err |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (c *config) Value(key string) Value { |
|
||||||
if v, ok := c.cached.Load(key); ok { |
|
||||||
return v.(Value) |
|
||||||
} |
|
||||||
if v, ok := c.reader.Value(key); ok { |
|
||||||
c.cached.Store(key, v) |
|
||||||
return v |
|
||||||
} |
|
||||||
return &errValue{err: ErrNotFound} |
|
||||||
} |
|
||||||
|
|
||||||
func (c *config) Scan(v interface{}) error { |
|
||||||
data, err := c.reader.Source() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
return unmarshalJSON(data, v) |
|
||||||
} |
|
||||||
|
|
||||||
func (c *config) Watch(key string, o Observer) error { |
|
||||||
if v := c.Value(key); v.Load() == nil { |
|
||||||
return ErrNotFound |
|
||||||
} |
|
||||||
c.observers.Store(key, o) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (c *config) Close() error { |
|
||||||
for _, w := range c.watchers { |
|
||||||
if err := w.Stop(); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
@ -1,184 +0,0 @@ |
|||||||
package config |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
_testJSON = ` |
|
||||||
{ |
|
||||||
"server":{ |
|
||||||
"http":{ |
|
||||||
"addr":"0.0.0.0", |
|
||||||
"port":80, |
|
||||||
"timeout":0.5, |
|
||||||
"enable_ssl":true |
|
||||||
}, |
|
||||||
"grpc":{ |
|
||||||
"addr":"0.0.0.0", |
|
||||||
"port":10080, |
|
||||||
"timeout":0.2 |
|
||||||
} |
|
||||||
}, |
|
||||||
"data":{ |
|
||||||
"database":{ |
|
||||||
"driver":"mysql", |
|
||||||
"source":"root:root@tcp(127.0.0.1:3306)/karta_id?parseTime=true" |
|
||||||
} |
|
||||||
}, |
|
||||||
"endpoints":[ |
|
||||||
"www.aaa.com", |
|
||||||
"www.bbb.org" |
|
||||||
] |
|
||||||
}` |
|
||||||
) |
|
||||||
|
|
||||||
type testConfigStruct struct { |
|
||||||
Server struct { |
|
||||||
HTTP struct { |
|
||||||
Addr string `json:"addr"` |
|
||||||
Port int `json:"port"` |
|
||||||
Timeout float64 `json:"timeout"` |
|
||||||
EnableSSL bool `json:"enable_ssl"` |
|
||||||
} `json:"http"` |
|
||||||
GRPC struct { |
|
||||||
Addr string `json:"addr"` |
|
||||||
Port int `json:"port"` |
|
||||||
Timeout float64 `json:"timeout"` |
|
||||||
} `json:"grpc"` |
|
||||||
} `json:"server"` |
|
||||||
Data struct { |
|
||||||
Database struct { |
|
||||||
Driver string `json:"driver"` |
|
||||||
Source string `json:"source"` |
|
||||||
} `json:"database"` |
|
||||||
} `json:"data"` |
|
||||||
Endpoints []string `json:"endpoints"` |
|
||||||
} |
|
||||||
|
|
||||||
type testJSONSource struct { |
|
||||||
data string |
|
||||||
sig chan struct{} |
|
||||||
err chan struct{} |
|
||||||
} |
|
||||||
|
|
||||||
func newTestJSONSource(data string) *testJSONSource { |
|
||||||
return &testJSONSource{data: data, sig: make(chan struct{}), err: make(chan struct{})} |
|
||||||
} |
|
||||||
|
|
||||||
func (p *testJSONSource) Load() ([]*KeyValue, error) { |
|
||||||
kv := &KeyValue{ |
|
||||||
Key: "json", |
|
||||||
Value: []byte(p.data), |
|
||||||
Format: "json", |
|
||||||
} |
|
||||||
return []*KeyValue{kv}, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (p *testJSONSource) Watch() (Watcher, error) { |
|
||||||
return newTestWatcher(p.sig, p.err), nil |
|
||||||
} |
|
||||||
|
|
||||||
type testWatcher struct { |
|
||||||
sig chan struct{} |
|
||||||
err chan struct{} |
|
||||||
exit chan struct{} |
|
||||||
} |
|
||||||
|
|
||||||
func newTestWatcher(sig, err chan struct{}) Watcher { |
|
||||||
return &testWatcher{sig: sig, err: err, exit: make(chan struct{})} |
|
||||||
} |
|
||||||
|
|
||||||
func (w *testWatcher) Next() ([]*KeyValue, error) { |
|
||||||
select { |
|
||||||
case <-w.sig: |
|
||||||
return nil, nil |
|
||||||
case <-w.err: |
|
||||||
return nil, errors.New("error") |
|
||||||
case <-w.exit: |
|
||||||
return nil, nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (w *testWatcher) Stop() error { |
|
||||||
close(w.exit) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func TestConfig(t *testing.T) { |
|
||||||
var ( |
|
||||||
err error |
|
||||||
httpAddr = "0.0.0.0" |
|
||||||
httpTimeout = 0.5 |
|
||||||
grpcPort = 10080 |
|
||||||
endpoint1 = "www.aaa.com" |
|
||||||
databaseDriver = "mysql" |
|
||||||
) |
|
||||||
|
|
||||||
c := New( |
|
||||||
WithSource(newTestJSONSource(_testJSON)), |
|
||||||
WithDecoder(defaultDecoder), |
|
||||||
WithResolver(defaultResolver), |
|
||||||
) |
|
||||||
err = c.Close() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
jSource := newTestJSONSource(_testJSON) |
|
||||||
opts := options{ |
|
||||||
sources: []Source{jSource}, |
|
||||||
decoder: defaultDecoder, |
|
||||||
resolver: defaultResolver, |
|
||||||
} |
|
||||||
cf := &config{} |
|
||||||
cf.opts = opts |
|
||||||
cf.reader = newReader(opts) |
|
||||||
|
|
||||||
err = cf.Load() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
driver, err := cf.Value("data.database.driver").String() |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
if databaseDriver != driver { |
|
||||||
t.Fatal("databaseDriver is not equal to val") |
|
||||||
} |
|
||||||
|
|
||||||
err = cf.Watch("endpoints", func(key string, value Value) { |
|
||||||
}) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
jSource.sig <- struct{}{} |
|
||||||
jSource.err <- struct{}{} |
|
||||||
|
|
||||||
var testConf testConfigStruct |
|
||||||
err = cf.Scan(&testConf) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
if httpAddr != testConf.Server.HTTP.Addr { |
|
||||||
t.Errorf("testConf.Server.HTTP.Addr want: %s, got: %s", httpAddr, testConf.Server.HTTP.Addr) |
|
||||||
} |
|
||||||
if httpTimeout != testConf.Server.HTTP.Timeout { |
|
||||||
t.Errorf("testConf.Server.HTTP.Timeout want: %.1f, got: %.1f", httpTimeout, testConf.Server.HTTP.Timeout) |
|
||||||
} |
|
||||||
if !testConf.Server.HTTP.EnableSSL { |
|
||||||
t.Error("testConf.Server.HTTP.EnableSSL is not equal to true") |
|
||||||
} |
|
||||||
if grpcPort != testConf.Server.GRPC.Port { |
|
||||||
t.Errorf("testConf.Server.GRPC.Port want: %d, got: %d", grpcPort, testConf.Server.GRPC.Port) |
|
||||||
} |
|
||||||
if endpoint1 != testConf.Endpoints[0] { |
|
||||||
t.Errorf("testConf.Endpoints[0] want: %s, got: %s", endpoint1, testConf.Endpoints[0]) |
|
||||||
} |
|
||||||
if len(testConf.Endpoints) != 2 { |
|
||||||
t.Error("len(testConf.Endpoints) is not equal to 2") |
|
||||||
} |
|
||||||
} |
|
@ -1,67 +0,0 @@ |
|||||||
package env |
|
||||||
|
|
||||||
import ( |
|
||||||
"os" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/config" |
|
||||||
) |
|
||||||
|
|
||||||
type env struct { |
|
||||||
prefixes []string |
|
||||||
} |
|
||||||
|
|
||||||
func NewSource(prefixes ...string) config.Source { |
|
||||||
return &env{prefixes: prefixes} |
|
||||||
} |
|
||||||
|
|
||||||
func (e *env) Load() (kv []*config.KeyValue, err error) { |
|
||||||
return e.load(os.Environ()), nil |
|
||||||
} |
|
||||||
|
|
||||||
func (e *env) load(envs []string) []*config.KeyValue { |
|
||||||
var kv []*config.KeyValue |
|
||||||
for _, env := range envs { |
|
||||||
var k, v string |
|
||||||
subs := strings.SplitN(env, "=", 2) //nolint:gomnd
|
|
||||||
k = subs[0] |
|
||||||
if len(subs) > 1 { |
|
||||||
v = subs[1] |
|
||||||
} |
|
||||||
|
|
||||||
if len(e.prefixes) > 0 { |
|
||||||
p, ok := matchPrefix(e.prefixes, k) |
|
||||||
if !ok || len(p) == len(k) { |
|
||||||
continue |
|
||||||
} |
|
||||||
// trim prefix
|
|
||||||
k = strings.TrimPrefix(k, p) |
|
||||||
k = strings.TrimPrefix(k, "_") |
|
||||||
} |
|
||||||
|
|
||||||
if len(k) != 0 { |
|
||||||
kv = append(kv, &config.KeyValue{ |
|
||||||
Key: k, |
|
||||||
Value: []byte(v), |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
return kv |
|
||||||
} |
|
||||||
|
|
||||||
func (e *env) Watch() (config.Watcher, error) { |
|
||||||
w, err := NewWatcher() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return w, nil |
|
||||||
} |
|
||||||
|
|
||||||
func matchPrefix(prefixes []string, s string) (string, bool) { |
|
||||||
for _, p := range prefixes { |
|
||||||
if strings.HasPrefix(s, p) { |
|
||||||
return p, true |
|
||||||
} |
|
||||||
} |
|
||||||
return "", false |
|
||||||
} |
|
@ -1,429 +0,0 @@ |
|||||||
package env |
|
||||||
|
|
||||||
import ( |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
"reflect" |
|
||||||
"testing" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/config" |
|
||||||
"github.com/go-kratos/kratos/v2/config/file" |
|
||||||
) |
|
||||||
|
|
||||||
const _testJSON = ` |
|
||||||
{ |
|
||||||
"test":{ |
|
||||||
"server":{ |
|
||||||
"name":"${SERVICE_NAME}", |
|
||||||
"addr":"${ADDR:127.0.0.1}", |
|
||||||
"port":"${PORT:8080}" |
|
||||||
} |
|
||||||
}, |
|
||||||
"foo":[ |
|
||||||
{ |
|
||||||
"name":"Tom", |
|
||||||
"age":"${AGE}" |
|
||||||
} |
|
||||||
] |
|
||||||
}` |
|
||||||
|
|
||||||
func TestEnvWithPrefix(t *testing.T) { |
|
||||||
var ( |
|
||||||
path = filepath.Join(t.TempDir(), "test_config") |
|
||||||
filename = filepath.Join(path, "test.json") |
|
||||||
data = []byte(_testJSON) |
|
||||||
) |
|
||||||
defer os.Remove(path) |
|
||||||
if err := os.MkdirAll(path, 0o700); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
if err := os.WriteFile(filename, data, 0o666); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
|
|
||||||
// set env
|
|
||||||
prefix1, prefix2 := "KRATOS_", "FOO" |
|
||||||
envs := map[string]string{ |
|
||||||
prefix1 + "SERVICE_NAME": "kratos_app", |
|
||||||
prefix2 + "ADDR": "192.168.0.1", |
|
||||||
prefix1 + "AGE": "20", |
|
||||||
// only prefix
|
|
||||||
prefix2: "foo", |
|
||||||
prefix2 + "_": "foo_", |
|
||||||
} |
|
||||||
|
|
||||||
for k, v := range envs { |
|
||||||
os.Setenv(k, v) |
|
||||||
} |
|
||||||
|
|
||||||
c := config.New(config.WithSource( |
|
||||||
file.NewSource(path), |
|
||||||
NewSource(prefix1, prefix2), |
|
||||||
)) |
|
||||||
|
|
||||||
if err := c.Load(); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
tests := []struct { |
|
||||||
name string |
|
||||||
path string |
|
||||||
expect interface{} |
|
||||||
}{ |
|
||||||
{ |
|
||||||
name: "test $KEY", |
|
||||||
path: "test.server.name", |
|
||||||
expect: "kratos_app", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test ${KEY:DEFAULT} without default", |
|
||||||
path: "test.server.addr", |
|
||||||
expect: "192.168.0.1", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test ${KEY:DEFAULT} with default", |
|
||||||
path: "test.server.port", |
|
||||||
expect: "8080", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test ${KEY} in array", |
|
||||||
path: "foo", |
|
||||||
expect: []interface{}{ |
|
||||||
map[string]interface{}{ |
|
||||||
"name": "Tom", |
|
||||||
"age": "20", |
|
||||||
}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, test := range tests { |
|
||||||
t.Run(test.name, func(t *testing.T) { |
|
||||||
var err error |
|
||||||
v := c.Value(test.path) |
|
||||||
if v.Load() != nil { |
|
||||||
var actual interface{} |
|
||||||
switch test.expect.(type) { |
|
||||||
case int: |
|
||||||
if actual, err = v.Int(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect.(int), int(actual.(int64))) { |
|
||||||
t.Errorf("expect %v, actual %v", test.expect, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
case string: |
|
||||||
if actual, err = v.String(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect.(string), actual.(string)) { |
|
||||||
t.Errorf(`expect %v, actual %v`, test.expect, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
case bool: |
|
||||||
if actual, err = v.Bool(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect.(bool), actual.(bool)) { |
|
||||||
t.Errorf(`expect %v, actual %v`, test.expect, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
case float64: |
|
||||||
if actual, err = v.Float(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect.(float64), actual.(float64)) { |
|
||||||
t.Errorf(`expect %v, actual %v`, test.expect, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
default: |
|
||||||
actual = v.Load() |
|
||||||
if !reflect.DeepEqual(test.expect, actual) { |
|
||||||
t.Logf("\nexpect: %#v\nactural: %#v", test.expect, actual) |
|
||||||
t.Fail() |
|
||||||
} |
|
||||||
} |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
} else { |
|
||||||
t.Error("value path not found") |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestEnvWithoutPrefix(t *testing.T) { |
|
||||||
var ( |
|
||||||
path = filepath.Join(t.TempDir(), "test_config") |
|
||||||
filename = filepath.Join(path, "test.json") |
|
||||||
data = []byte(_testJSON) |
|
||||||
) |
|
||||||
defer os.Remove(path) |
|
||||||
if err := os.MkdirAll(path, 0o700); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
if err := os.WriteFile(filename, data, 0o666); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
|
|
||||||
// set env
|
|
||||||
envs := map[string]string{ |
|
||||||
"SERVICE_NAME": "kratos_app", |
|
||||||
"ADDR": "192.168.0.1", |
|
||||||
"AGE": "20", |
|
||||||
} |
|
||||||
|
|
||||||
for k, v := range envs { |
|
||||||
os.Setenv(k, v) |
|
||||||
} |
|
||||||
|
|
||||||
c := config.New(config.WithSource( |
|
||||||
NewSource(), |
|
||||||
file.NewSource(path), |
|
||||||
)) |
|
||||||
|
|
||||||
if err := c.Load(); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
tests := []struct { |
|
||||||
name string |
|
||||||
path string |
|
||||||
expect interface{} |
|
||||||
}{ |
|
||||||
{ |
|
||||||
name: "test $KEY", |
|
||||||
path: "test.server.name", |
|
||||||
expect: "kratos_app", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test ${KEY:DEFAULT} without default", |
|
||||||
path: "test.server.addr", |
|
||||||
expect: "192.168.0.1", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test ${KEY:DEFAULT} with default", |
|
||||||
path: "test.server.port", |
|
||||||
expect: "8080", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test ${KEY} in array", |
|
||||||
path: "foo", |
|
||||||
expect: []interface{}{ |
|
||||||
map[string]interface{}{ |
|
||||||
"name": "Tom", |
|
||||||
"age": "20", |
|
||||||
}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, test := range tests { |
|
||||||
t.Run(test.name, func(t *testing.T) { |
|
||||||
var err error |
|
||||||
v := c.Value(test.path) |
|
||||||
if v.Load() != nil { |
|
||||||
var actual interface{} |
|
||||||
switch test.expect.(type) { |
|
||||||
case int: |
|
||||||
if actual, err = v.Int(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect.(int), int(actual.(int64))) { |
|
||||||
t.Errorf("expect %v, actual %v", test.expect, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
case string: |
|
||||||
if actual, err = v.String(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect.(string), actual.(string)) { |
|
||||||
t.Errorf(`expect %v, actual %v`, test.expect, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
case bool: |
|
||||||
if actual, err = v.Bool(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect.(bool), actual.(bool)) { |
|
||||||
t.Errorf(`expect %v, actual %v`, test.expect, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
case float64: |
|
||||||
if actual, err = v.Float(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect.(float64), actual.(float64)) { |
|
||||||
t.Errorf(`expect %v, actual %v`, test.expect, actual) |
|
||||||
} |
|
||||||
} |
|
||||||
default: |
|
||||||
actual = v.Load() |
|
||||||
if !reflect.DeepEqual(test.expect, actual) { |
|
||||||
t.Logf("\nexpect: %#v\nactural: %#v", test.expect, actual) |
|
||||||
t.Fail() |
|
||||||
} |
|
||||||
} |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
} else { |
|
||||||
t.Error("value path not found") |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func Test_env_load(t *testing.T) { |
|
||||||
type fields struct { |
|
||||||
prefixes []string |
|
||||||
} |
|
||||||
type args struct { |
|
||||||
envStrings []string |
|
||||||
} |
|
||||||
tests := []struct { |
|
||||||
name string |
|
||||||
fields fields |
|
||||||
args args |
|
||||||
want []*config.KeyValue |
|
||||||
}{ |
|
||||||
{ |
|
||||||
name: "without prefixes", |
|
||||||
fields: fields{ |
|
||||||
prefixes: nil, |
|
||||||
}, |
|
||||||
args: args{ |
|
||||||
envStrings: []string{ |
|
||||||
"SERVICE_NAME=kratos_app", |
|
||||||
"ADDR=192.168.0.1", |
|
||||||
"AGE=20", |
|
||||||
}, |
|
||||||
}, |
|
||||||
want: []*config.KeyValue{ |
|
||||||
{Key: "SERVICE_NAME", Value: []byte("kratos_app"), Format: ""}, |
|
||||||
{Key: "ADDR", Value: []byte("192.168.0.1"), Format: ""}, |
|
||||||
{Key: "AGE", Value: []byte("20"), Format: ""}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
|
|
||||||
{ |
|
||||||
name: "empty prefix", |
|
||||||
fields: fields{ |
|
||||||
prefixes: []string{""}, |
|
||||||
}, |
|
||||||
args: args{ |
|
||||||
envStrings: []string{ |
|
||||||
"__SERVICE_NAME=kratos_app", |
|
||||||
"__ADDR=192.168.0.1", |
|
||||||
"__AGE=20", |
|
||||||
}, |
|
||||||
}, |
|
||||||
want: []*config.KeyValue{ |
|
||||||
{Key: "_SERVICE_NAME", Value: []byte("kratos_app"), Format: ""}, |
|
||||||
{Key: "_ADDR", Value: []byte("192.168.0.1"), Format: ""}, |
|
||||||
{Key: "_AGE", Value: []byte("20"), Format: ""}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
|
|
||||||
{ |
|
||||||
name: "underscore prefix", |
|
||||||
fields: fields{ |
|
||||||
prefixes: []string{"_"}, |
|
||||||
}, |
|
||||||
args: args{ |
|
||||||
envStrings: []string{ |
|
||||||
"__SERVICE_NAME=kratos_app", |
|
||||||
"__ADDR=192.168.0.1", |
|
||||||
"__AGE=20", |
|
||||||
}, |
|
||||||
}, |
|
||||||
want: []*config.KeyValue{ |
|
||||||
{Key: "SERVICE_NAME", Value: []byte("kratos_app"), Format: ""}, |
|
||||||
{Key: "ADDR", Value: []byte("192.168.0.1"), Format: ""}, |
|
||||||
{Key: "AGE", Value: []byte("20"), Format: ""}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
|
|
||||||
{ |
|
||||||
name: "with prefixes", |
|
||||||
fields: fields{ |
|
||||||
prefixes: []string{"KRATOS_", "FOO"}, |
|
||||||
}, |
|
||||||
args: args{ |
|
||||||
envStrings: []string{ |
|
||||||
"KRATOS_SERVICE_NAME=kratos_app", |
|
||||||
"KRATOS_ADDR=192.168.0.1", |
|
||||||
"FOO_AGE=20", |
|
||||||
}, |
|
||||||
}, |
|
||||||
want: []*config.KeyValue{ |
|
||||||
{Key: "SERVICE_NAME", Value: []byte("kratos_app"), Format: ""}, |
|
||||||
{Key: "ADDR", Value: []byte("192.168.0.1"), Format: ""}, |
|
||||||
{Key: "AGE", Value: []byte("20"), Format: ""}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
|
|
||||||
{ |
|
||||||
name: "should not panic #1", |
|
||||||
fields: fields{ |
|
||||||
prefixes: []string{"FOO"}, |
|
||||||
}, |
|
||||||
args: args{ |
|
||||||
envStrings: []string{ |
|
||||||
"FOO=123", |
|
||||||
}, |
|
||||||
}, |
|
||||||
want: nil, |
|
||||||
}, |
|
||||||
|
|
||||||
{ |
|
||||||
name: "should not panic #2", |
|
||||||
fields: fields{ |
|
||||||
prefixes: []string{"FOO=1"}, |
|
||||||
}, |
|
||||||
args: args{ |
|
||||||
envStrings: []string{ |
|
||||||
"FOO=123", |
|
||||||
}, |
|
||||||
}, |
|
||||||
want: nil, |
|
||||||
}, |
|
||||||
} |
|
||||||
for _, tt := range tests { |
|
||||||
t.Run(tt.name, func(t *testing.T) { |
|
||||||
e := &env{ |
|
||||||
prefixes: tt.fields.prefixes, |
|
||||||
} |
|
||||||
got := e.load(tt.args.envStrings) |
|
||||||
if !reflect.DeepEqual(tt.want, got) { |
|
||||||
t.Errorf("env.load() = %v, want %v", got, tt.want) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func Test_matchPrefix(t *testing.T) { |
|
||||||
type args struct { |
|
||||||
prefixes []string |
|
||||||
s string |
|
||||||
} |
|
||||||
tests := []struct { |
|
||||||
name string |
|
||||||
args args |
|
||||||
want string |
|
||||||
wantOk bool |
|
||||||
}{ |
|
||||||
{args: args{prefixes: nil, s: "foo=123"}, want: "", wantOk: false}, |
|
||||||
{args: args{prefixes: []string{""}, s: "foo=123"}, want: "", wantOk: true}, |
|
||||||
{args: args{prefixes: []string{"foo"}, s: "foo=123"}, want: "foo", wantOk: true}, |
|
||||||
{args: args{prefixes: []string{"foo=1"}, s: "foo=123"}, want: "foo=1", wantOk: true}, |
|
||||||
{args: args{prefixes: []string{"foo=1234"}, s: "foo=123"}, want: "", wantOk: false}, |
|
||||||
{args: args{prefixes: []string{"bar"}, s: "foo=123"}, want: "", wantOk: false}, |
|
||||||
} |
|
||||||
for _, tt := range tests { |
|
||||||
t.Run(tt.name, func(t *testing.T) { |
|
||||||
got, gotOk := matchPrefix(tt.args.prefixes, tt.args.s) |
|
||||||
if got != tt.want { |
|
||||||
t.Errorf("matchPrefix() got = %v, want %v", got, tt.want) |
|
||||||
} |
|
||||||
if gotOk != tt.wantOk { |
|
||||||
t.Errorf("matchPrefix() gotOk = %v, wantOk %v", gotOk, tt.wantOk) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func Test_env_watch(t *testing.T) { |
|
||||||
prefixes := []string{"BAR", "FOO"} |
|
||||||
source := NewSource(prefixes...) |
|
||||||
w, err := source.Watch() |
|
||||||
if err != nil { |
|
||||||
t.Errorf("expect no err, got %v", err) |
|
||||||
} |
|
||||||
_ = w.Stop() |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
package env |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/config" |
|
||||||
) |
|
||||||
|
|
||||||
var _ config.Watcher = (*watcher)(nil) |
|
||||||
|
|
||||||
type watcher struct { |
|
||||||
ctx context.Context |
|
||||||
cancel context.CancelFunc |
|
||||||
} |
|
||||||
|
|
||||||
func NewWatcher() (config.Watcher, error) { |
|
||||||
ctx, cancel := context.WithCancel(context.Background()) |
|
||||||
return &watcher{ctx: ctx, cancel: cancel}, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Next will be blocked until the Stop method is called
|
|
||||||
func (w *watcher) Next() ([]*config.KeyValue, error) { |
|
||||||
<-w.ctx.Done() |
|
||||||
return nil, w.ctx.Err() |
|
||||||
} |
|
||||||
|
|
||||||
func (w *watcher) Stop() error { |
|
||||||
w.cancel() |
|
||||||
return nil |
|
||||||
} |
|
@ -1,32 +0,0 @@ |
|||||||
package env |
|
||||||
|
|
||||||
import ( |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func Test_watcher_next(t *testing.T) { |
|
||||||
t.Run("next after stop should return err", func(t *testing.T) { |
|
||||||
w, err := NewWatcher() |
|
||||||
if err != nil { |
|
||||||
t.Errorf("expect no error, got %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
_ = w.Stop() |
|
||||||
_, err = w.Next() |
|
||||||
if err == nil { |
|
||||||
t.Error("expect error, actual nil") |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func Test_watcher_stop(t *testing.T) { |
|
||||||
t.Run("stop multiple times should not panic", func(t *testing.T) { |
|
||||||
w, err := NewWatcher() |
|
||||||
if err != nil { |
|
||||||
t.Errorf("expect no error, got %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
_ = w.Stop() |
|
||||||
_ = w.Stop() |
|
||||||
}) |
|
||||||
} |
|
@ -1,80 +0,0 @@ |
|||||||
package file |
|
||||||
|
|
||||||
import ( |
|
||||||
"io" |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/config" |
|
||||||
) |
|
||||||
|
|
||||||
var _ config.Source = (*file)(nil) |
|
||||||
|
|
||||||
type file struct { |
|
||||||
path string |
|
||||||
} |
|
||||||
|
|
||||||
// NewSource new a file source.
|
|
||||||
func NewSource(path string) config.Source { |
|
||||||
return &file{path: path} |
|
||||||
} |
|
||||||
|
|
||||||
func (f *file) loadFile(path string) (*config.KeyValue, error) { |
|
||||||
file, err := os.Open(path) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
defer file.Close() |
|
||||||
data, err := io.ReadAll(file) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
info, err := file.Stat() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return &config.KeyValue{ |
|
||||||
Key: info.Name(), |
|
||||||
Format: format(info.Name()), |
|
||||||
Value: data, |
|
||||||
}, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (f *file) loadDir(path string) (kvs []*config.KeyValue, err error) { |
|
||||||
files, err := os.ReadDir(path) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
for _, file := range files { |
|
||||||
// ignore hidden files
|
|
||||||
if file.IsDir() || strings.HasPrefix(file.Name(), ".") { |
|
||||||
continue |
|
||||||
} |
|
||||||
kv, err := f.loadFile(filepath.Join(path, file.Name())) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
kvs = append(kvs, kv) |
|
||||||
} |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
func (f *file) Load() (kvs []*config.KeyValue, err error) { |
|
||||||
fi, err := os.Stat(f.path) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if fi.IsDir() { |
|
||||||
return f.loadDir(f.path) |
|
||||||
} |
|
||||||
kv, err := f.loadFile(f.path) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return []*config.KeyValue{kv}, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (f *file) Watch() (config.Watcher, error) { |
|
||||||
return newWatcher(f) |
|
||||||
} |
|
@ -1,341 +0,0 @@ |
|||||||
package file |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
"reflect" |
|
||||||
"sync" |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/config" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
_testJSON = ` |
|
||||||
{ |
|
||||||
"test":{ |
|
||||||
"settings":{ |
|
||||||
"int_key":1000, |
|
||||||
"float_key":1000.1, |
|
||||||
"duration_key":10000, |
|
||||||
"string_key":"string_value" |
|
||||||
}, |
|
||||||
"server":{ |
|
||||||
"addr":"127.0.0.1", |
|
||||||
"port":8000 |
|
||||||
} |
|
||||||
}, |
|
||||||
"foo":[ |
|
||||||
{ |
|
||||||
"name":"nihao", |
|
||||||
"age":18 |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name":"nihao", |
|
||||||
"age":18 |
|
||||||
} |
|
||||||
] |
|
||||||
}` |
|
||||||
|
|
||||||
_testJSONUpdate = ` |
|
||||||
{ |
|
||||||
"test":{ |
|
||||||
"settings":{ |
|
||||||
"int_key":1000, |
|
||||||
"float_key":1000.1, |
|
||||||
"duration_key":10000, |
|
||||||
"string_key":"string_value" |
|
||||||
}, |
|
||||||
"server":{ |
|
||||||
"addr":"127.0.0.1", |
|
||||||
"port":8000 |
|
||||||
} |
|
||||||
}, |
|
||||||
"foo":[ |
|
||||||
{ |
|
||||||
"name":"nihao", |
|
||||||
"age":18 |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name":"nihao", |
|
||||||
"age":18 |
|
||||||
} |
|
||||||
], |
|
||||||
"bar":{ |
|
||||||
"event":"update" |
|
||||||
} |
|
||||||
}` |
|
||||||
|
|
||||||
// _testYaml = `
|
|
||||||
//Foo:
|
|
||||||
// bar :
|
|
||||||
// - {name: nihao,age: 1}
|
|
||||||
// - {name: nihao,age: 1}
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//`
|
|
||||||
) |
|
||||||
|
|
||||||
//func TestScan(t *testing.T) {
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
func TestFile(t *testing.T) { |
|
||||||
var ( |
|
||||||
path = filepath.Join(t.TempDir(), "test_config") |
|
||||||
file = filepath.Join(path, "test.json") |
|
||||||
data = []byte(_testJSON) |
|
||||||
) |
|
||||||
defer os.Remove(path) |
|
||||||
if err := os.MkdirAll(path, 0o700); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
if err := os.WriteFile(file, data, 0o666); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
testSource(t, file, data) |
|
||||||
testSource(t, path, data) |
|
||||||
testWatchFile(t, file) |
|
||||||
testWatchDir(t, path, file) |
|
||||||
} |
|
||||||
|
|
||||||
func testWatchFile(t *testing.T, path string) { |
|
||||||
t.Log(path) |
|
||||||
|
|
||||||
s := NewSource(path) |
|
||||||
watch, err := s.Watch() |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
|
|
||||||
f, err := os.OpenFile(path, os.O_RDWR, 0) |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
defer f.Close() |
|
||||||
_, err = f.WriteString(_testJSONUpdate) |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
kvs, err := watch.Next() |
|
||||||
if err != nil { |
|
||||||
t.Errorf("watch.Next() error(%v)", err) |
|
||||||
} |
|
||||||
if !reflect.DeepEqual(string(kvs[0].Value), _testJSONUpdate) { |
|
||||||
t.Errorf("string(kvs[0].Value(%v) is not equal to _testJSONUpdate(%v)", kvs[0].Value, _testJSONUpdate) |
|
||||||
} |
|
||||||
|
|
||||||
newFilepath := filepath.Join(filepath.Dir(path), "test1.json") |
|
||||||
if err = os.Rename(path, newFilepath); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
kvs, err = watch.Next() |
|
||||||
if err == nil { |
|
||||||
t.Errorf("watch.Next() error(%v)", err) |
|
||||||
} |
|
||||||
if kvs != nil { |
|
||||||
t.Errorf("watch.Next() error(%v)", err) |
|
||||||
} |
|
||||||
|
|
||||||
err = watch.Stop() |
|
||||||
if err != nil { |
|
||||||
t.Errorf("watch.Stop() error(%v)", err) |
|
||||||
} |
|
||||||
|
|
||||||
if err := os.Rename(newFilepath, path); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func testWatchDir(t *testing.T, path, file string) { |
|
||||||
t.Log(path) |
|
||||||
t.Log(file) |
|
||||||
|
|
||||||
s := NewSource(path) |
|
||||||
watch, err := s.Watch() |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
|
|
||||||
f, err := os.OpenFile(file, os.O_RDWR, 0) |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
defer f.Close() |
|
||||||
_, err = f.WriteString(_testJSONUpdate) |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
|
|
||||||
kvs, err := watch.Next() |
|
||||||
if err != nil { |
|
||||||
t.Errorf("watch.Next() error(%v)", err) |
|
||||||
} |
|
||||||
if !reflect.DeepEqual(string(kvs[0].Value), _testJSONUpdate) { |
|
||||||
t.Errorf("string(kvs[0].Value(%s) is not equal to _testJSONUpdate(%v)", kvs[0].Value, _testJSONUpdate) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func testSource(t *testing.T, path string, data []byte) { |
|
||||||
t.Log(path) |
|
||||||
|
|
||||||
s := NewSource(path) |
|
||||||
kvs, err := s.Load() |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
if string(kvs[0].Value) != string(data) { |
|
||||||
t.Errorf("no expected: %s, but got: %s", kvs[0].Value, data) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestConfig(t *testing.T) { |
|
||||||
path := filepath.Join(t.TempDir(), "test_config.json") |
|
||||||
defer os.Remove(path) |
|
||||||
if err := os.WriteFile(path, []byte(_testJSON), 0o666); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
c := config.New(config.WithSource( |
|
||||||
NewSource(path), |
|
||||||
)) |
|
||||||
testScan(t, c) |
|
||||||
|
|
||||||
testConfig(t, c) |
|
||||||
} |
|
||||||
|
|
||||||
func testConfig(t *testing.T, c config.Config) { |
|
||||||
expected := map[string]interface{}{ |
|
||||||
"test.settings.int_key": int64(1000), |
|
||||||
"test.settings.float_key": 1000.1, |
|
||||||
"test.settings.string_key": "string_value", |
|
||||||
"test.settings.duration_key": time.Duration(10000), |
|
||||||
"test.server.addr": "127.0.0.1", |
|
||||||
"test.server.port": int64(8000), |
|
||||||
} |
|
||||||
if err := c.Load(); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
for key, value := range expected { |
|
||||||
switch value.(type) { |
|
||||||
case int64: |
|
||||||
if v, err := c.Value(key).Int(); err != nil { |
|
||||||
t.Error(key, value, err) |
|
||||||
} else if v != value { |
|
||||||
t.Errorf("no expect key: %s value: %v, but got: %v", key, value, v) |
|
||||||
} |
|
||||||
case float64: |
|
||||||
if v, err := c.Value(key).Float(); err != nil { |
|
||||||
t.Error(key, value, err) |
|
||||||
} else if v != value { |
|
||||||
t.Errorf("no expect key: %s value: %v, but got: %v", key, value, v) |
|
||||||
} |
|
||||||
case string: |
|
||||||
if v, err := c.Value(key).String(); err != nil { |
|
||||||
t.Error(key, value, err) |
|
||||||
} else if v != value { |
|
||||||
t.Errorf("no expect key: %s value: %v, but got: %v", key, value, v) |
|
||||||
} |
|
||||||
case time.Duration: |
|
||||||
if v, err := c.Value(key).Duration(); err != nil { |
|
||||||
t.Error(key, value, err) |
|
||||||
} else if v != value { |
|
||||||
t.Errorf("no expect key: %s value: %v, but got: %v", key, value, v) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
// scan
|
|
||||||
var settings struct { |
|
||||||
IntKey int64 `json:"int_key"` |
|
||||||
FloatKey float64 `json:"float_key"` |
|
||||||
StringKey string `json:"string_key"` |
|
||||||
DurationKey time.Duration `json:"duration_key"` |
|
||||||
} |
|
||||||
if err := c.Value("test.settings").Scan(&settings); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
if v := expected["test.settings.int_key"]; settings.IntKey != v { |
|
||||||
t.Errorf("no expect int_key value: %v, but got: %v", settings.IntKey, v) |
|
||||||
} |
|
||||||
if v := expected["test.settings.float_key"]; settings.FloatKey != v { |
|
||||||
t.Errorf("no expect float_key value: %v, but got: %v", settings.FloatKey, v) |
|
||||||
} |
|
||||||
if v := expected["test.settings.string_key"]; settings.StringKey != v { |
|
||||||
t.Errorf("no expect string_key value: %v, but got: %v", settings.StringKey, v) |
|
||||||
} |
|
||||||
if v := expected["test.settings.duration_key"]; settings.DurationKey != v { |
|
||||||
t.Errorf("no expect duration_key value: %v, but got: %v", settings.DurationKey, v) |
|
||||||
} |
|
||||||
|
|
||||||
// not found
|
|
||||||
if _, err := c.Value("not_found_key").Bool(); errors.Is(err, config.ErrNotFound) { |
|
||||||
t.Logf("not_found_key not match: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func testScan(t *testing.T, c config.Config) { |
|
||||||
type TestJSON struct { |
|
||||||
Test struct { |
|
||||||
Settings struct { |
|
||||||
IntKey int `json:"int_key"` |
|
||||||
FloatKey float64 `json:"float_key"` |
|
||||||
DurationKey int `json:"duration_key"` |
|
||||||
StringKey string `json:"string_key"` |
|
||||||
} `json:"settings"` |
|
||||||
Server struct { |
|
||||||
Addr string `json:"addr"` |
|
||||||
Port int `json:"port"` |
|
||||||
} `json:"server"` |
|
||||||
} `json:"test"` |
|
||||||
Foo []struct { |
|
||||||
Name string `json:"name"` |
|
||||||
Age int `json:"age"` |
|
||||||
} `json:"foo"` |
|
||||||
} |
|
||||||
var conf TestJSON |
|
||||||
if err := c.Load(); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
if err := c.Scan(&conf); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
t.Log(conf) |
|
||||||
} |
|
||||||
|
|
||||||
func TestMergeDataRace(t *testing.T) { |
|
||||||
path := filepath.Join(t.TempDir(), "test_config.json") |
|
||||||
defer os.Remove(path) |
|
||||||
if err := os.WriteFile(path, []byte(_testJSON), 0o666); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
c := config.New(config.WithSource( |
|
||||||
NewSource(path), |
|
||||||
)) |
|
||||||
const count = 80 |
|
||||||
wg := &sync.WaitGroup{} |
|
||||||
wg.Add(2) |
|
||||||
startCh := make(chan struct{}) |
|
||||||
go func() { |
|
||||||
defer wg.Done() |
|
||||||
<-startCh |
|
||||||
for i := 0; i < count; i++ { |
|
||||||
var conf struct{} |
|
||||||
if err := c.Scan(&conf); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
} |
|
||||||
}() |
|
||||||
|
|
||||||
go func() { |
|
||||||
defer wg.Done() |
|
||||||
<-startCh |
|
||||||
for i := 0; i < count; i++ { |
|
||||||
if err := c.Load(); err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
} |
|
||||||
}() |
|
||||||
close(startCh) |
|
||||||
wg.Wait() |
|
||||||
} |
|
@ -1,10 +0,0 @@ |
|||||||
package file |
|
||||||
|
|
||||||
import "strings" |
|
||||||
|
|
||||||
func format(name string) string { |
|
||||||
if p := strings.Split(name, "."); len(p) > 1 { |
|
||||||
return p[len(p)-1] |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
package file |
|
||||||
|
|
||||||
import ( |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestFormat(t *testing.T) { |
|
||||||
tests := []struct { |
|
||||||
input string |
|
||||||
expect string |
|
||||||
}{ |
|
||||||
{ |
|
||||||
input: "", |
|
||||||
expect: "", |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: " ", |
|
||||||
expect: "", |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: ".", |
|
||||||
expect: "", |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "a.", |
|
||||||
expect: "", |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: ".b", |
|
||||||
expect: "b", |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "a.b", |
|
||||||
expect: "b", |
|
||||||
}, |
|
||||||
} |
|
||||||
for _, v := range tests { |
|
||||||
content := format(v.input) |
|
||||||
if got, want := content, v.expect; got != want { |
|
||||||
t.Errorf("expect %v,got %v", want, got) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,68 +0,0 @@ |
|||||||
package file |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"os" |
|
||||||
"path/filepath" |
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/config" |
|
||||||
) |
|
||||||
|
|
||||||
var _ config.Watcher = (*watcher)(nil) |
|
||||||
|
|
||||||
type watcher struct { |
|
||||||
f *file |
|
||||||
fw *fsnotify.Watcher |
|
||||||
|
|
||||||
ctx context.Context |
|
||||||
cancel context.CancelFunc |
|
||||||
} |
|
||||||
|
|
||||||
func newWatcher(f *file) (config.Watcher, error) { |
|
||||||
fw, err := fsnotify.NewWatcher() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if err := fw.Add(f.path); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
ctx, cancel := context.WithCancel(context.Background()) |
|
||||||
return &watcher{f: f, fw: fw, ctx: ctx, cancel: cancel}, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (w *watcher) Next() ([]*config.KeyValue, error) { |
|
||||||
select { |
|
||||||
case <-w.ctx.Done(): |
|
||||||
return nil, w.ctx.Err() |
|
||||||
case event := <-w.fw.Events: |
|
||||||
if event.Op == fsnotify.Rename { |
|
||||||
if _, err := os.Stat(event.Name); err == nil || os.IsExist(err) { |
|
||||||
if err := w.fw.Add(event.Name); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
fi, err := os.Stat(w.f.path) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
path := w.f.path |
|
||||||
if fi.IsDir() { |
|
||||||
path = filepath.Join(w.f.path, filepath.Base(event.Name)) |
|
||||||
} |
|
||||||
kv, err := w.f.loadFile(path) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return []*config.KeyValue{kv}, nil |
|
||||||
case err := <-w.fw.Errors: |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (w *watcher) Stop() error { |
|
||||||
w.cancel() |
|
||||||
return w.fw.Close() |
|
||||||
} |
|
@ -1,133 +0,0 @@ |
|||||||
package config |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"regexp" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/encoding" |
|
||||||
"github.com/go-kratos/kratos/v2/log" |
|
||||||
) |
|
||||||
|
|
||||||
// Decoder is config decoder.
|
|
||||||
type Decoder func(*KeyValue, map[string]interface{}) error |
|
||||||
|
|
||||||
// Resolver resolve placeholder in config.
|
|
||||||
type Resolver func(map[string]interface{}) error |
|
||||||
|
|
||||||
// Option is config option.
|
|
||||||
type Option func(*options) |
|
||||||
|
|
||||||
type options struct { |
|
||||||
sources []Source |
|
||||||
decoder Decoder |
|
||||||
resolver Resolver |
|
||||||
} |
|
||||||
|
|
||||||
// WithSource with config source.
|
|
||||||
func WithSource(s ...Source) Option { |
|
||||||
return func(o *options) { |
|
||||||
o.sources = s |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// WithDecoder with config decoder.
|
|
||||||
// DefaultDecoder behavior:
|
|
||||||
// If KeyValue.Format is non-empty, then KeyValue.Value will be deserialized into map[string]interface{}
|
|
||||||
// and stored in the config cache(map[string]interface{})
|
|
||||||
// if KeyValue.Format is empty,{KeyValue.Key : KeyValue.Value} will be stored in config cache(map[string]interface{})
|
|
||||||
func WithDecoder(d Decoder) Option { |
|
||||||
return func(o *options) { |
|
||||||
o.decoder = d |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// WithResolver with config resolver.
|
|
||||||
func WithResolver(r Resolver) Option { |
|
||||||
return func(o *options) { |
|
||||||
o.resolver = r |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// WithLogger with config logger.
|
|
||||||
// Deprecated: use global logger instead.
|
|
||||||
func WithLogger(_ log.Logger) Option { |
|
||||||
return func(o *options) {} |
|
||||||
} |
|
||||||
|
|
||||||
// defaultDecoder decode config from source KeyValue
|
|
||||||
// to target map[string]interface{} using src.Format codec.
|
|
||||||
func defaultDecoder(src *KeyValue, target map[string]interface{}) error { |
|
||||||
if src.Format == "" { |
|
||||||
// expand key "aaa.bbb" into map[aaa]map[bbb]interface{}
|
|
||||||
keys := strings.Split(src.Key, ".") |
|
||||||
for i, k := range keys { |
|
||||||
if i == len(keys)-1 { |
|
||||||
target[k] = src.Value |
|
||||||
} else { |
|
||||||
sub := make(map[string]interface{}) |
|
||||||
target[k] = sub |
|
||||||
target = sub |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
if codec := encoding.GetCodec(src.Format); codec != nil { |
|
||||||
return codec.Unmarshal(src.Value, &target) |
|
||||||
} |
|
||||||
return fmt.Errorf("unsupported key: %s format: %s", src.Key, src.Format) |
|
||||||
} |
|
||||||
|
|
||||||
// defaultResolver resolve placeholder in map value,
|
|
||||||
// placeholder format in ${key:default}.
|
|
||||||
func defaultResolver(input map[string]interface{}) error { |
|
||||||
mapper := func(name string) string { |
|
||||||
args := strings.SplitN(strings.TrimSpace(name), ":", 2) //nolint:gomnd
|
|
||||||
if v, has := readValue(input, args[0]); has { |
|
||||||
s, _ := v.String() |
|
||||||
return s |
|
||||||
} else if len(args) > 1 { // default value
|
|
||||||
return args[1] |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
var resolve func(map[string]interface{}) error |
|
||||||
resolve = func(sub map[string]interface{}) error { |
|
||||||
for k, v := range sub { |
|
||||||
switch vt := v.(type) { |
|
||||||
case string: |
|
||||||
sub[k] = expand(vt, mapper) |
|
||||||
case map[string]interface{}: |
|
||||||
if err := resolve(vt); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
case []interface{}: |
|
||||||
for i, iface := range vt { |
|
||||||
switch it := iface.(type) { |
|
||||||
case string: |
|
||||||
vt[i] = expand(it, mapper) |
|
||||||
case map[string]interface{}: |
|
||||||
if err := resolve(it); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
sub[k] = vt |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
return resolve(input) |
|
||||||
} |
|
||||||
|
|
||||||
func expand(s string, mapping func(string) string) string { |
|
||||||
r := regexp.MustCompile(`\${(.*?)}`) |
|
||||||
re := r.FindAllStringSubmatch(s, -1) |
|
||||||
for _, i := range re { |
|
||||||
if len(i) == 2 { //nolint:gomnd
|
|
||||||
s = strings.ReplaceAll(s, i[0], mapping(i[1])) |
|
||||||
} |
|
||||||
} |
|
||||||
return s |
|
||||||
} |
|
@ -1,228 +0,0 @@ |
|||||||
package config |
|
||||||
|
|
||||||
import ( |
|
||||||
"reflect" |
|
||||||
"strings" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestDefaultDecoder(t *testing.T) { |
|
||||||
src := &KeyValue{ |
|
||||||
Key: "service", |
|
||||||
Value: []byte("config"), |
|
||||||
Format: "", |
|
||||||
} |
|
||||||
target := make(map[string]interface{}) |
|
||||||
err := defaultDecoder(src, target) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
if !reflect.DeepEqual(target, map[string]interface{}{"service": []byte("config")}) { |
|
||||||
t.Fatal(`target is not equal to map[string]interface{}{"service": "config"}`) |
|
||||||
} |
|
||||||
|
|
||||||
src = &KeyValue{ |
|
||||||
Key: "service.name.alias", |
|
||||||
Value: []byte("2233"), |
|
||||||
Format: "", |
|
||||||
} |
|
||||||
target = make(map[string]interface{}) |
|
||||||
err = defaultDecoder(src, target) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
if !reflect.DeepEqual(map[string]interface{}{ |
|
||||||
"service": map[string]interface{}{ |
|
||||||
"name": map[string]interface{}{ |
|
||||||
"alias": []byte("2233"), |
|
||||||
}, |
|
||||||
}, |
|
||||||
}, target) { |
|
||||||
t.Fatal(`target is not equal to map[string]interface{}{"service": map[string]interface{}{"name": map[string]interface{}{"alias": []byte("2233")}}}`) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestDefaultResolver(t *testing.T) { |
|
||||||
var ( |
|
||||||
portString = "8080" |
|
||||||
countInt = 10 |
|
||||||
rateFloat = 0.9 |
|
||||||
) |
|
||||||
|
|
||||||
data := map[string]interface{}{ |
|
||||||
"foo": map[string]interface{}{ |
|
||||||
"bar": map[string]interface{}{ |
|
||||||
"notexist": "${NOTEXIST:100}", |
|
||||||
"port": "${PORT:8081}", |
|
||||||
"count": "${COUNT:0}", |
|
||||||
"enable": "${ENABLE:false}", |
|
||||||
"rate": "${RATE}", |
|
||||||
"empty": "${EMPTY:foobar}", |
|
||||||
"url": "${URL:http://example.com}", |
|
||||||
"array": []interface{}{ |
|
||||||
"${PORT}", |
|
||||||
map[string]interface{}{"foobar": "${NOTEXIST:8081}"}, |
|
||||||
}, |
|
||||||
"value1": "${test.value}", |
|
||||||
"value2": "$PORT", |
|
||||||
"value3": "abc${PORT}foo${COUNT}bar", |
|
||||||
"value4": "${foo${bar}}", |
|
||||||
}, |
|
||||||
}, |
|
||||||
"test": map[string]interface{}{ |
|
||||||
"value": "foobar", |
|
||||||
}, |
|
||||||
"PORT": "8080", |
|
||||||
"COUNT": "10", |
|
||||||
"ENABLE": "true", |
|
||||||
"RATE": "0.9", |
|
||||||
"EMPTY": "", |
|
||||||
} |
|
||||||
|
|
||||||
tests := []struct { |
|
||||||
name string |
|
||||||
path string |
|
||||||
expect interface{} |
|
||||||
}{ |
|
||||||
{ |
|
||||||
name: "test not exist int env with default", |
|
||||||
path: "foo.bar.notexist", |
|
||||||
expect: 100, |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test string with default", |
|
||||||
path: "foo.bar.port", |
|
||||||
expect: portString, |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test int with default", |
|
||||||
path: "foo.bar.count", |
|
||||||
expect: countInt, |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test bool with default", |
|
||||||
path: "foo.bar.enable", |
|
||||||
expect: true, |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test float without default", |
|
||||||
path: "foo.bar.rate", |
|
||||||
expect: rateFloat, |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test empty value with default", |
|
||||||
path: "foo.bar.empty", |
|
||||||
expect: "", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test url with default", |
|
||||||
path: "foo.bar.url", |
|
||||||
expect: "http://example.com", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test array", |
|
||||||
path: "foo.bar.array", |
|
||||||
expect: []interface{}{portString, map[string]interface{}{"foobar": "8081"}}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test ${test.value}", |
|
||||||
path: "foo.bar.value1", |
|
||||||
expect: "foobar", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test $PORT", |
|
||||||
path: "foo.bar.value2", |
|
||||||
expect: "$PORT", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test abc${PORT}foo${COUNT}bar", |
|
||||||
path: "foo.bar.value3", |
|
||||||
expect: "abc8080foo10bar", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "test ${foo${bar}}", |
|
||||||
path: "foo.bar.value4", |
|
||||||
expect: "}", |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, test := range tests { |
|
||||||
t.Run(test.name, func(t *testing.T) { |
|
||||||
err := defaultResolver(data) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
rd := reader{ |
|
||||||
values: data, |
|
||||||
} |
|
||||||
if v, ok := rd.Value(test.path); ok { |
|
||||||
var actual interface{} |
|
||||||
switch test.expect.(type) { |
|
||||||
case int: |
|
||||||
if actual, err = v.Int(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect.(int), int(actual.(int64))) { |
|
||||||
t.Fatal("expect is not equal to actual") |
|
||||||
} |
|
||||||
} |
|
||||||
case string: |
|
||||||
if actual, err = v.String(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect, actual) { |
|
||||||
t.Fatal("expect is not equal to actual") |
|
||||||
} |
|
||||||
} |
|
||||||
case bool: |
|
||||||
if actual, err = v.Bool(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect, actual) { |
|
||||||
t.Fatal("expect is not equal to actual") |
|
||||||
} |
|
||||||
} |
|
||||||
case float64: |
|
||||||
if actual, err = v.Float(); err == nil { |
|
||||||
if !reflect.DeepEqual(test.expect, actual) { |
|
||||||
t.Fatal("expect is not equal to actual") |
|
||||||
} |
|
||||||
} |
|
||||||
default: |
|
||||||
actual = v.Load() |
|
||||||
if !reflect.DeepEqual(test.expect, actual) { |
|
||||||
t.Logf("expect: %#v, actural: %#v", test.expect, actual) |
|
||||||
t.Fail() |
|
||||||
} |
|
||||||
} |
|
||||||
if err != nil { |
|
||||||
t.Error(err) |
|
||||||
} |
|
||||||
} else { |
|
||||||
t.Error("value path not found") |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestExpand(t *testing.T) { |
|
||||||
tests := []struct { |
|
||||||
input string |
|
||||||
mapping func(string) string |
|
||||||
want string |
|
||||||
}{ |
|
||||||
{ |
|
||||||
input: "${a}", |
|
||||||
mapping: func(s string) string { |
|
||||||
return strings.ToUpper(s) |
|
||||||
}, |
|
||||||
want: "A", |
|
||||||
}, |
|
||||||
{ |
|
||||||
input: "a", |
|
||||||
mapping: func(s string) string { |
|
||||||
return strings.ToUpper(s) |
|
||||||
}, |
|
||||||
want: "a", |
|
||||||
}, |
|
||||||
} |
|
||||||
for _, tt := range tests { |
|
||||||
if got := expand(tt.input, tt.mapping); got != tt.want { |
|
||||||
t.Errorf("expand() want: %s, got: %s", tt.want, got) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue